cc-os/plugins/os-doc-hygiene/examples/golden/classifier
jared 7b9afb1f6a os-doc-hygiene: lifecycle-aware hygiene implementation + calibration pass #1 (map #31)
Implements the lifecycle-aware-doc-hygiene change (ADR-0038..0041):
rulebook loader (global rulebook.json + per-project .dochygiene-rules.json,
add-only merge, two-axis precedence, IGNORE-sentinel zero-emission prune),
scanner lifecycle signals + temporary/delete-once-served tiers, report
schema delete/extract-then-delete ops with git-state tier derivation,
promotion_candidates from conventions.json, patch-applier deletion ops with
apply-time git re-verification, and the /os-doc-hygiene:calibrate skill.

Calibration pass #1 (cc-os) PASSED: protected-set hard gate + recall floor
9/10 with all 4 mandatory rows; 4 human-approved rules persisted to
repo-root .dochygiene-rules.json. invariants.md #3 (.cc-os/dochygiene per
ADR-0027) and #9 (rulebook IGNORE as third ignore mechanism) corrected
with explicit human approval per the META-RULE.

test_no_global_index_created now runs against an isolated HOME (the real
~/.cc-os legitimately exists since os-backlog's projects registry).
Suite: 408 passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LAw7YxP71Fk6K9AHNaYPj2
2026-07-15 07:41:59 -04:00
..
1-orphaned os-doc-hygiene: lifecycle-aware hygiene implementation + calibration pass #1 (map #31) 2026-07-15 07:41:59 -04:00
2-superseded os-doc-hygiene: lifecycle-aware hygiene implementation + calibration pass #1 (map #31) 2026-07-15 07:41:59 -04:00
3-completed-in-place os-doc-hygiene: lifecycle-aware hygiene implementation + calibration pass #1 (map #31) 2026-07-15 07:41:59 -04:00
4-duplicated os-doc-hygiene: lifecycle-aware hygiene implementation + calibration pass #1 (map #31) 2026-07-15 07:41:59 -04:00
5-distill os-doc-hygiene: lifecycle-aware hygiene implementation + calibration pass #1 (map #31) 2026-07-15 07:41:59 -04:00
README.md Align os-doc-hygiene skill naming and invocation with marketplace convention 2026-07-03 11:23:24 -04:00

README.md

Classifier golden examples

Layer-2 reversion-protection fixtures for the classifier (design D7, openspec/changes/add-check/design.md). Each case is a small static fixture doc-tree (input/, stable bytes → stable sha256) paired with expected.json, a full schema-valid machine report representing the expected classification of that tree. These are distinct from the schema-shape fixtures one level up (../valid_report.json / ../invalid_report.json), which only exercise validate_report.py.

Adding or changing a classifier golden is human-gated per the META-RULE in invariants.md.

Cases

Dir class / subtype op_type exact_edit.kind tier scanner signal
1-orphaned stale / orphaned deterministic delete-range confirm broken_reference
2-superseded stale / superseded deterministic move-to-archive auto stale_name_location
3-completed-in-place stale / completed-in-place deterministic insert-frontmatter auto archive_to_live_ratio
4-duplicated stale / duplicated deterministic dedupe auto stale_name_location (see note)
5-distill bloat / distill generative (none) confirm archive_to_live_ratio

Each case directory holds:

  • input/ — the fixture doc-tree (the bytes the scanner and the model see).
  • proposals.json — the hand-authored model-judgment proposal(s) used to generate expected.json (kept so the computed fields are reproducible).
  • expected.json — the full schema-valid machine report (the frozen expectation). Computed fields (expected_sha256, raw_tokens, derived safety_tier) are real: generated by piping a real scanner.py artifact + proposals.json through report_builder.py. scan.project_root is normalized to <input> for portability; generated_at timestamps are wall-clock and intentionally not asserted on.
  • notes.md — the one taxonomy edge + scanner signal the case pins.

Note (case 4): the scanner has no dedicated duplication signal. Case 4 rides a co-occurring stale_name_location ("legacy") as its deterministic trigger; the duplicated judgment itself is the model's. See 4-duplicated/notes.md.

Two harnesses

Hermetic unit harness (in pytest — tests/test_classifier_golden.py)

Runs with NO network and NO model. For each case it asserts only the deterministic / stable parts: scanner signals on the expected paths, validate_report.py accepts each expected.json, and internal consistency of the stable classification fields (closed enums, op_type, exact_edit.kind, the derived safety_tier recomputed, and expected_sha256 re-verified against the fixture bytes). This is the regression gate that runs in CI.

Live model-classification regression (OUTSIDE pytest — manual / agent-invoked)

This is the model-dependent check; it is intentionally NOT part of the unit suite because it calls a live model. Run it manually (or have an agent run it) when you want to confirm the classifier still reaches the expected judgment.

For each case directory:

  1. Run an actual classification over input/ — either /os-doc-hygiene:check --scope <case>/input or by invoking the check skill against that tree. This produces a fresh machine report.

    Confirm the invocation once the check command lands. --scope is a filter on a scan rooted at the resolved project root, not a re-root — to classify a case in isolation you want the check rooted at <case>/input (as the hermetic suite roots the scanner). Verify the exact flag/rooting against the shipped skills/check/SKILL.md before relying on it.

  2. Diff the produced classification's stable fields against expected.json, per entry:

    • category.class
    • category.subtype
    • op_type
    • the derived safety_tier
    • exact_edit.kind

    A mismatch on any of these is a regression — investigate before accepting.

  3. Advisory only (flag for human review, do not auto-fail):

    • op prose wording.
    • exact anchor line numbers (exact_edit.anchor.start_line / end_line) and reducible_range.

    These are model-authored and may drift slightly without indicating a real classification regression; surface them for a human to eyeball rather than gating on them.

Because expected.json encodes a captured model judgment, any intended change to the expected classification is a golden change → human-gated per the META-RULE.

Regenerating an expected.json (after an approved fixture change)

python3 scripts/scanner.py --root examples/golden/classifier/<case>/input > /tmp/scan.json
python3 scripts/report_builder.py \
    --scan /tmp/scan.json \
    --proposals examples/golden/classifier/<case>/proposals.json \
    --root examples/golden/classifier/<case>/input \
    --out-json examples/golden/classifier/<case>/expected.json
# then normalize scan.project_root to "<input>" and re-run the hermetic suite.

raw_tokens reflect whichever token-estimator backend was active at generation time (the zero-dependency heuristic here, since no tiktoken vocab is vendored). The hermetic suite does not assert on raw_tokens, so regenerating on a tiktoken-equipped machine may shift those counts harmlessly — don't mistake that diff for a regression.