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 |
||
|---|---|---|
| .. | ||
| 1-orphaned | ||
| 2-superseded | ||
| 3-completed-in-place | ||
| 4-duplicated | ||
| 5-distill | ||
| README.md | ||
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 generateexpected.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, derivedsafety_tier) are real: generated by piping a realscanner.pyartifact +proposals.jsonthroughreport_builder.py.scan.project_rootis normalized to<input>for portability;generated_attimestamps 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. See4-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:
-
Run an actual classification over
input/— either/os-doc-hygiene:check --scope <case>/inputor by invoking thecheckskill against that tree. This produces a fresh machine report.Confirm the invocation once the check command lands.
--scopeis 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 shippedskills/check/SKILL.mdbefore relying on it. -
Diff the produced classification's stable fields against
expected.json, per entry:category.classcategory.subtypeop_type- the derived
safety_tier exact_edit.kind
A mismatch on any of these is a regression — investigate before accepting.
-
Advisory only (flag for human review, do not auto-fail):
opprose wording.- exact anchor line numbers (
exact_edit.anchor.start_line/end_line) andreducible_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.