diff --git a/docs/adr/0038-per-project-doc-hygiene-rulebook-override-lives-at-repo-root-dochygiene-rules-json-not-in-cc-os.md b/docs/adr/0038-per-project-doc-hygiene-rulebook-override-lives-at-repo-root-dochygiene-rules-json-not-in-cc-os.md new file mode 100644 index 0000000..32f7329 --- /dev/null +++ b/docs/adr/0038-per-project-doc-hygiene-rulebook-override-lives-at-repo-root-dochygiene-rules-json-not-in-cc-os.md @@ -0,0 +1,27 @@ +--- +id: "0038" +date: 2026-07-14 +status: Accepted +supersedes: +superseded-by: +affected-paths: [plugins/os-doc-hygiene/rulebook.json, plugins/os-doc-hygiene/lifecycle-spec.md, plugins/os-doc-hygiene/scripts/] +affected-components: [os-doc-hygiene] +--- + +# 0038 — Per-project doc-hygiene rulebook override lives at repo-root .dochygiene-rules.json, not in .cc-os/ + +## Context + +The lifecycle-aware doc hygiene design (wayfinder map #31, ticket #40) gives os-doc-hygiene a global rulebook shipped in the plugin (plugins/os-doc-hygiene/rulebook.json) plus a thin per-project override for exceptions (e.g. repos where specs are the product). cc-os also has a gitignored .cc-os/ directory used for local machine config (e.g. tracker keys), which raised the question of where the override belongs. The override participates in deletion decisions, so it must be reviewable, versioned, and shared by every collaborator and agent working the repo. + +## Decision + +The per-project override is a committed repo-root dotfile: .dochygiene-rules.json. It follows the existing .dochygiene-ignore precedent. Envelope {"schema_version": 1, "rules": [...]}; merged add-only over the global rulebook with source-then-specificity precedence (project file-rule > project directory-rule > global file-rule > global directory-rule; ties by longest pattern, then last-defined). A project neutralizes a global rule by shadowing it with lifetime "keep" — no rule-removal mechanism. + +## Consequences + +Rule overrides are visible in review and travel with the repo — a rule that can authorize deletion is never invisible local state. One more root dotfile per opted-in project. Because the merge is add-only, a bad global rule is neutralized by shadowing, not deleted, keeping merge semantics trivially predictable. Full schema: plugins/os-doc-hygiene/lifecycle-spec.md. + +## Alternatives rejected + +(1) .cc-os/dochygiene-rules.json — rejected: .cc-os/ is gitignored local config; deletion-authorizing rules must be committed, reviewable, and shared, not per-machine. (2) Global rulebook only, no override — rejected: repos where specs/plans are the product need exceptions (ticket #34). (3) YAML/TOML with comments — rejected in #40: JSON parses with stdlib in both the Python pipeline and any tooling; a per-rule note field substitutes for comments. diff --git a/docs/adr/0039-doc-hygiene-deletion-autonomy-is-tiered-on-evidence-quality-and-recoverability-not-file-type.md b/docs/adr/0039-doc-hygiene-deletion-autonomy-is-tiered-on-evidence-quality-and-recoverability-not-file-type.md new file mode 100644 index 0000000..69656ea --- /dev/null +++ b/docs/adr/0039-doc-hygiene-deletion-autonomy-is-tiered-on-evidence-quality-and-recoverability-not-file-type.md @@ -0,0 +1,27 @@ +--- +id: "0039" +date: 2026-07-14 +status: Accepted +supersedes: +superseded-by: +affected-paths: [plugins/os-doc-hygiene/lifecycle-spec.md, plugins/os-doc-hygiene/scripts/, plugins/os-doc-hygiene/skills/] +affected-components: [os-doc-hygiene] +--- + +# 0039 — Doc-hygiene deletion autonomy is tiered on evidence quality and recoverability, not file type + +## Context + +Lifecycle rules let os-doc-hygiene:clean delete files (true git deletion in a dedicated hygiene commit; git history is the archive — ticket #35). The question (ticket #43) was what deletes silently versus what requires confirmation. Re-confirming every run would reduce the rulebook to an advisory list and strip :calibrate of its payoff; but an LLM judgment must never silently destroy a file, and untracked or dirty files have no history to recover from. /os-adr:find confirmed no existing ADR governed clean's safety tiers. + +## Decision + +Rule-backed deletes are auto — a rule confirmed per the strong-model gate (#39) is the standing consent. The auto/confirm line is drawn on evidence quality plus recoverability: tracked + clean worktree + deterministic signal = auto; tracked-but-dirty or untracked = confirm (verified at runtime via git ls-files + dirty check, never trusting the rule); classifier-judged served_when signals are ALWAYS forced to confirm regardless of tracked status, while scanner-proven served_when_path signals may delete silently. The temporary tier defaults to retain_recent 3 + max_age_days 3, age measured from git commit time falling back to mtime for untracked entries (directory-inode mtime, no recursive walk — #48). graphify-out/** and .dochygiene/** form an explicit IGNORE surface that is never walked — the ignore surface is its own list, never inferred from .gitignore (gitignored ≠ deletable, gitignored ≠ keepable). A per-rule confirm:true escape hatch exists but is human-settable only; a model-proposed rule may only ask a human to set it. + +## Consequences + +Routine hygiene runs unattended on the safe population while every non-recoverable or judgment-based deletion stops at a human. The auto/confirm line moves exactly where evidence quality changes, so improving a rule's evidence (see the determinism-promotion ADR) directly buys silence. 3-day/retain-3 retention is aggressive by design; the newest 3 entries per rule always survive to show current trajectory. Full tier matrix: plugins/os-doc-hygiene/lifecycle-spec.md. + +## Alternatives rejected + +(1) Confirm everything — rejected: rulebook becomes advisory; confirm-fatigue makes the human rubber-stamp. (2) Tier by file type or a recoverable_via/regenerate class — rejected: recoverability is a property of git state at runtime, not of the rule's claim. (3) 90-day retention windows — rejected as far too slow in the age of AI. (4) mtime-only age — rejected: clone/branch-switch resets every mtime, silently putting the whole rulebook to sleep. (5) Model-settable confirm:true — rejected: used too liberally, everything drifts back to always-confirm. diff --git a/docs/adr/0040-doc-hygiene-rules-never-write-other-tools-ignore-surfaces-no-propagate-ignore.md b/docs/adr/0040-doc-hygiene-rules-never-write-other-tools-ignore-surfaces-no-propagate-ignore.md new file mode 100644 index 0000000..0287106 --- /dev/null +++ b/docs/adr/0040-doc-hygiene-rules-never-write-other-tools-ignore-surfaces-no-propagate-ignore.md @@ -0,0 +1,27 @@ +--- +id: "0040" +date: 2026-07-14 +status: Accepted +supersedes: +superseded-by: +affected-paths: [plugins/os-doc-hygiene/lifecycle-spec.md, plugins/os-vault/, docs/adr/0017-project-graph-onboarding-assesses-the-repo-and-writes-graphifyignore-before-extracting.md] +affected-components: [os-doc-hygiene, os-vault, graphify] +--- + +# 0040 — Doc-hygiene rules never write other tools' ignore surfaces (no propagate_ignore) + +## Context + +Ticket #44 asked whether a lifecycle rule marking files temporary/delete-once-served should also propagate into .graphifyignore (keeping disposable docs out of the session-start knowledge graph) and the scanner's own .dochygiene-ignore. The #40 rulebook schema had reserved a propagate_ignore field pending this decision. + +## Decision + +No propagation, ever: a lifecycle rule never writes into any other tool's ignore/config surface, and the propagate_ignore field is removed from the rulebook schema entirely (not left as an empty slot). .dochygiene-ignore remains a hand-authored, human-only escape hatch unmanaged by the rulebook. os-doc-hygiene stays in its own lane — separation of concerns. + +## Consequences + +The glob-dialect mismatch problem (rulebook glob.translate vs gitignore vs fnmatch) dissolves — no rule glob is ever translated into a foreign dialect. Delete-once-served files leave the knowledge graph at the next rebuild anyway, so the lost payoff was only the narrow temporary-but-not-yet-served window. If disposable files polluting the knowledge graph later proves painful, the fix belongs to graphify or os-vault:onboard-project, not a hygiene rule reaching across the seam. Noted while verifying: ADR-0017's premise that graphify does not honor .gitignore is stale — graphifyy 0.8.31 (detect.py:707-711) falls back to .gitignore per-directory where no .graphifyignore exists; this does not change ADR-0017's decision. + +## Alternatives rejected + +(1) Propagate into .dochygiene-ignore — moot, not rejected on principle: the rulebook itself is already the scanner's skip instruction (directory-rule matches prune the walk), so propagation would create two sources of truth for one fact in a weaker dialect (fnmatch) with no precedence rule. (2) Append to .graphifyignore — rejected on ownership and on ADR-0017: onboard-project generates that file per-project WITH user confirmation, and ADR-0017 explicitly rejected auto-writing it without confirmation; automatic appends would reinstate the rejected alternative and add a second writer with no arbiter. (3) Propagate to memsearch — not applicable: memsearch indexes its own ~/.memsearch/memory/ daily files and has no ignore surface for project docs. diff --git a/docs/adr/0041-determinism-promotion-hygiene-nudges-projects-toward-structurally-obvious-completion-conventions.md b/docs/adr/0041-determinism-promotion-hygiene-nudges-projects-toward-structurally-obvious-completion-conventions.md new file mode 100644 index 0000000..2852b3b --- /dev/null +++ b/docs/adr/0041-determinism-promotion-hygiene-nudges-projects-toward-structurally-obvious-completion-conventions.md @@ -0,0 +1,27 @@ +--- +id: "0041" +date: 2026-07-14 +status: Accepted +supersedes: +superseded-by: +affected-paths: [plugins/os-doc-hygiene/conventions.json, plugins/os-doc-hygiene/lifecycle-spec.md, plugins/os-doc-hygiene/skills/] +affected-components: [os-doc-hygiene] +--- + +# 0041 — Determinism promotion: hygiene nudges projects toward structurally-obvious completion conventions + +## Context + +The deletion-autonomy tiers force every classifier-judged served_when signal to confirm (the LLM may propose, never silently destroy). Left there, projects with subjective completion signals would accumulate permanent confirm-fatigue, and the human would rubber-stamp gates. Tickets #43 (item 7) and #47 asked how the tool should respond when a rule's served signal is subjective. + +## Decision + +When a rule's served signal is classifier-judged, the tool does not merely downgrade it to confirm: it names the subjectivity and recommends a concrete structural convention that would graduate the rule to a scanner-proven served_when_path and make it silent. Conventions live in a global-only, machine-readable catalog at plugins/os-doc-hygiene/conventions.json so the deterministic pipeline emits nudges without an LLM; v1 holds exactly two entries: archive-bucket (done = file moved to a sibling archive/ dir, precedent: openspec changes) and status-frontmatter (done = a status: shipped|done frontmatter key). Surfacing splits by capability: :check names promotion candidates in every report; :calibrate may draft the adoption (graduated rule + file moves) for human approval but never applies it unasked. + +## Consequences + +Confirm-fatigue is converted into the incentive to fix the convention rather than a cost to endure — adopting a convention directly buys silent automation. The catalog only recommends; adoption lands in the project's own rulebook, so no per-project catalog override is needed. A two-entry catalog that gets adopted beats a taxonomy; successor-artifact checks stay out until a calibration pass demands one. The :check report schema gains a promotion-candidates section. + +## Alternatives rejected + +(1) Just downgrade subjective rules to confirm and stop — rejected: permanent confirm-fatigue with no exit path. (2) A rich taxonomy of completion conventions — rejected: adoption beats coverage; entries are added when calibration passes demand them. (3) Per-project catalog override — rejected: the catalog only recommends, so there is nothing project-specific to override. (4) :calibrate applies adoptions automatically — rejected: violates the model-proposes/human-disposes constraint from #42. diff --git a/docs/adr/README.md b/docs/adr/README.md index 81d87fa..3056fef 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -43,4 +43,9 @@ One file per decision, `NNNN-kebab-title.md`, created via `/os-adr:create`. | 0034 | [Cross-project filing: file-dont-fix with a derived global project index](0034-cross-project-filing-file-dont-fix-with-a-derived-global-project-index.md) | Accepted | 2026-07-13 | | 0035 | [Issue-triggered project AI wakeup via polling, not webhooks](0035-issue-triggered-project-ai-wakeup-via-polling-not-webhooks.md) | Accepted | 2026-07-13 | | 0036 | [tmux session convention for AI-run sessions: cc--](0036-tmux-session-convention-for-ai-run-sessions-cc-project-purpose.md) | Accepted | 2026-07-13 | +| 0037 | [os-sdlc lives inside cc-os as a new plugin, not a separate cc-sdlc marketplace](0037-os-sdlc-lives-inside-cc-os-as-a-new-plugin-not-a-separate-cc-sdlc-marketplace.md) | Accepted | 2026-07-14 | +| 0038 | [Per-project doc-hygiene rulebook override lives at repo-root .dochygiene-rules.json, not in .cc-os/](0038-per-project-doc-hygiene-rulebook-override-lives-at-repo-root-dochygiene-rules-json-not-in-cc-os.md) | Accepted | 2026-07-14 | +| 0039 | [Doc-hygiene deletion autonomy is tiered on evidence quality and recoverability, not file type](0039-doc-hygiene-deletion-autonomy-is-tiered-on-evidence-quality-and-recoverability-not-file-type.md) | Accepted | 2026-07-14 | +| 0040 | [Doc-hygiene rules never write other tools' ignore surfaces (no propagate_ignore)](0040-doc-hygiene-rules-never-write-other-tools-ignore-surfaces-no-propagate-ignore.md) | Accepted | 2026-07-14 | +| 0041 | [Determinism promotion: hygiene nudges projects toward structurally-obvious completion conventions](0041-determinism-promotion-hygiene-nudges-projects-toward-structurally-obvious-completion-conventions.md) | Accepted | 2026-07-14 | diff --git a/plugins/os-doc-hygiene/lifecycle-spec.md b/plugins/os-doc-hygiene/lifecycle-spec.md new file mode 100644 index 0000000..9ddac8e --- /dev/null +++ b/plugins/os-doc-hygiene/lifecycle-spec.md @@ -0,0 +1,300 @@ +# Lifecycle-aware doc hygiene — spec + +_Status: Locked design (wayfinder map [#31](https://forgejo.swansoncloud.com/jared/cc-os/issues/31)); assembled 2026-07-14 from decision tickets #32–#48._ +_Last updated: 2026-07-14_ + +Extends os-doc-hygiene from stale/bloat monitoring to lifecycle management: a +rulebook assigns every managed file a **lifetime**, `:check`/`:clean` gain +delete and extract-then-delete behavior, and a new `:calibrate` skill learns +rules per project. Validated against calibration project #1 (cc-os) per the +criteria in [Calibration](#calibration-pass-1-cc-os). + +ADR set: [ADR-0038](../../docs/adr/) (rulebook location), ADR-0039 (deletion +autonomy tiers), ADR-0040 (no ignore-surface propagation), ADR-0041 +(determinism-promotion principle). + +## 1. Lifetime taxonomy (#33) + +Three lifetimes, plus one modifier: + +- **keep** — indefinite; scanned and reported, never deleted. +- **temporary** — age-triggered deletion (see [Temporary tier](#4-temporary-tier-43-48)). +- **delete-once-served** — purpose-triggered deletion (see [Served signals](#5-delete-once-served-served-signal-split-43)). +- **extract** (modifier on deletion, not a fourth lifetime) — distill durable + content before deleting. Extraction reuses existing knowledge routing (#36): + repo-durable residue → ADR/CLAUDE.md/docs; cross-repo lessons → SecondBrain + via `/os-vault:write`. No new destinations, no "retired specs" pile. + +Distinct from all lifetimes: an **IGNORE surface** — paths the scanner never +walks at all (vs `keep` = walked and reported, never deleted). Seed members: +`graphify-out/**`, `.dochygiene/**`. The ignore surface is an explicit list, +never inferred from `.gitignore` (gitignored ≠ deletable AND gitignored ≠ +keepable — #43). + +## 2. Rulebook (#34, #38, #40, #44) + +### Locations + +- **Global:** `plugins/os-doc-hygiene/rulebook.json`, resolved relative to + plugin scripts. Ships everywhere. +- **Per-project override:** repo-root `.dochygiene-rules.json`, committed + (matches the `.dochygiene-ignore` precedent; deliberately NOT in gitignored + `.cc-os/` — ADR-0038). + +Both use envelope `{"schema_version": 1, "rules": [...]}`. + +### Glob dialect + +gitignore-style via stdlib `glob.translate(recursive=True, +include_hidden=True)` (Python ≥ 3.13); patterns repo-root-relative, real `**`; +compiled once at load. Directory rules = patterns covering a subtree. + +### Precedence (two-axis, source first) + +project file-rule > project directory-rule > global file-rule > global +directory-rule; ties broken by longest pattern, then last-defined. File-level +rules override directory rules (#38). The merge is **add-only**: a project +neutralizes a global rule by shadowing it with `lifetime: "keep"` — there is +no rule-removal mechanism. + +### Per-rule fields + +```json +{ + "glob": "autoresearch/*/**", + "lifetime": "keep|temporary|delete-once-served", + "extract": true, + "served_when": "free text — hint consumed by the LLM classifier, no trigger DSL", + "served_when_path": "openspec/changes/archive/{id}/", + "retain_recent": 3, + "max_age_days": 3, + "confirm": true, + "confirmed_by": "human | ", + "confirmed_on": "2026-07-14", + "source": "clutter-inventory #41", + "note": "free-text rationale" +} +``` + +- `retain_recent` default 3; `max_age_days` default **3** (not + null-means-forever) — #43 amendments to the #40 schema. +- `served_when_path` is the deterministic sibling of `served_when` (#43). +- `confirm: true` is an optional always-confirm escape hatch — **human-settable + only**; a model-proposed rule may never set it, only ask a human to (#42/#43). +- **There is no `propagate_ignore` field.** It was reserved in #40 and dropped + by #44: a lifecycle rule never writes into another tool's ignore/config + surface (ADR-0040). +- Unmatched files get NO lifetime — they flow through existing signals + unchanged and become `:calibrate` candidates (**unmatched = unmanaged**). + +### Scanner consumption + +New stdlib `rulebook.py` loader. A directory-rule match **prunes the walk** +(files beneath are never opened; one aggregate entry) — this also implements +the IGNORE surface for free. A file-rule match attaches a **lifecycle signal** +(rule ref + lifetime + served_when) consumed by the classifier as a new signal +class (#37). + +### Validation + +Skip-and-warn per invalid/unconfirmed rule — a rule missing `confirmed_by` +never acts (#39) but doesn't take scanning down. Hard-fail on unparseable JSON +or unknown `schema_version`. + +`.dochygiene-ignore` remains a hand-authored, human-only escape hatch, +unmanaged by the rulebook (#44). + +## 3. Deletion semantics and autonomy tiers (#35, #43 — ADR-0039) + +Delete = **true git deletion in a dedicated hygiene commit**; git history is +the archive. No `archive/` dirs or graveyard branches (relocated clutter still +distracts AI/search). + +Rule-backed deletes are **auto** — a rule confirmed per #39 *is* the standing +consent; no per-run prompt. The auto/confirm line is drawn on **evidence +quality + recoverability**, not file type: + +| Case | Behavior | +|---|---| +| IGNORE surface (`graphify-out/**`, `.dochygiene/**`) | never walked | +| lifetime `keep` | scanned + reported, never deleted | +| tracked + delete rule + clean worktree | **auto** | +| tracked + delete rule + DIRTY | **confirm** (an uncommitted diff dies with the file) | +| untracked + delete rule | **confirm** (no history to recover from) | +| no rule match | unmanaged; existing signals only, never deleted | + +`clean` verifies tracked+clean **at runtime** (`git ls-files` + dirty check); +it never trusts the rule's word for it. No `recoverable_via` field, no +"regenerate" class. + +## 4. Temporary tier (#43, #48) + +**retain-recent-N + age, not age alone.** Defaults `retain_recent: 3`, +`max_age_days: 3` (both per-rule overridable). The newest 3 entries matching a +rule are always kept regardless of age (they show current trajectory); an +entry ranked 4th or older is deleted once it exceeds `max_age_days`. 90-day +windows were rejected as far too slow. + +**Retention unit = the rule's match entry**: a file for file rules, a run +*directory* for directory rules ("3 most recent autoresearch runs", not "3 +most recent files inside one run"). + +**Age = git commit time, falling back to filesystem mtime for untracked +files.** mtime-alone was rejected (clone/branch-switch resets every mtime, +putting the rulebook to sleep on fresh checkouts); the objection doesn't apply +to the fallback, since untracked files don't exist in a fresh clone. No +per-rule `age_source` field (#48). + +**Untracked directory entries** use the directory inode's own mtime (one +stat), NOT a recursive max-mtime walk — the tier's failure mode is +self-healing (a spuriously bumped mtime merely delays deletion one round), so +the cheap signal suffices (#48). + +## 5. delete-once-served: served-signal split (#43) + +Two slots, split by evidence quality: + +```json +{ "glob": "openspec/changes/*/", "lifetime": "delete-once-served", + "served_when_path": "openspec/changes/archive/{id}/" } +``` +Scanner **proves** the served condition → may delete silently under the tier +matrix. + +```json +{ "glob": "docs/plans/*.md", "lifetime": "delete-once-served", + "served_when": "the effort this plan describes has shipped" } +``` +Classifier **judges** the condition → **always forced to confirm**, regardless +of tracked status. The LLM may propose; it may never silently destroy on a +hunch. + +Example of the boundary: `autoresearch/*/` can be auto (a concluded run is +provable from the filesystem); `PRD.md` cannot (purpose-triggered — "did this +ship?" is a judgment; and it is NOT temporary, since age-keying would delete +the PRD of a feature not yet built — #45 Addendum 2). + +## 6. Determinism promotion (#43 item 7, #47 — ADR-0041) + +**Design principle: hygiene drives projects toward structurally-obvious +maintenance.** When a rule's served signal is subjective (classifier-judged), +the tool does not merely downgrade it to confirm — it names the subjectivity +and recommends a concrete structural convention that would graduate the rule +to `served_when_path` and make it silent. Confirm-fatigue is the incentive to +fix the convention. + +### Completion-conventions catalog + +`plugins/os-doc-hygiene/conventions.json` — global-only, machine-readable so +the deterministic pipeline can emit nudges without an LLM. No per-project +override: the catalog only recommends; adoption lands in the project's own +rulebook. Each entry: name, what it proves, the `served_when_path` / +frontmatter template a rule graduates to, and a one-line human pitch. + +v1 contents — exactly two conventions: + +- **archive-bucket** — "done" = the file moved into a sibling `archive/` dir + (`docs/plans/x.md` → `docs/plans/archive/x.md`); graduates a rule to + `served_when_path: /archive/{name}`. Precedent: openspec changes. +- **status-frontmatter** — "done" = a `status: shipped|done` frontmatter key; + file stays put; scanner reads frontmatter. + +Successor-artifact checks stay in the fog until a calibration pass demands one. + +### Nudge surfacing (split by capability) + +- **`:check`** names promotion candidates in every report (deterministic, + recurring — the report gains a promotion-candidates section). +- **`:calibrate`** may go further and DRAFT the adoption — the graduated rule + plus the file moves — for human approval. It proposes, never applies unasked. + +## 7. Pipeline integration (#37) + +Lifecycle categorization is a **new signal class** in the existing +scanner/classifier pipeline; `delete` and `extract-then-delete` are **new op +types** in the clean report schema. The only new skill is +**`os-doc-hygiene:calibrate`**. + +## 8. The :calibrate protocol (#42, #39, #45) + +The learn-new-rules loop, run per project: + +1. **Cluster-and-sample** over unmatched files (unmatched = unmanaged = the + candidate pool). Clustering exists precisely so rules are written over the + cluster, not one member. +2. **Nomination (cheap model):** haiku nominates a bare glob + lifetime per + cluster — constrained to produce *patterns*, never exact-instance globs. +3. **Judgment (strong model):** one batched Opus/Fable judge gathers its own + evidence and authors final rule entries (#39: weak-model discoveries need + strong-model confirmation). Verdicts: **confirm / reject / amend / + consult** — consult is mandatory when an artifact's purpose is unclear + (regenerable ≠ removable). +4. **Rule report to the human — before any rule is persisted.** Per proposed + rule, the report shows: + 1. the **glob verbatim**, exactly as it would be persisted; + 2. every path it currently matches (or a capped sample + total count); + 3. **the boundary — near-miss paths it does NOT match** (this caught a + real bug during #45: `autoresearch/classic-*/` silently missing + `autoresearch/improve-260710-1057/`); + 4. lifetime + behavior tier (auto vs confirm); + 5. a plain-language why — what the artifact is and why it's clutter. + The human reviews patterns and examples, not JSON schema. +5. **Persistence:** project rules land on judge confirmation; + **global-rulebook writes are human-gated** (a cross-repo write into cc-os). + Rule removals are HITL-only, with recorded reasoning. +6. **Retest loop:** stop at <2 new rules OR <10% unmatched shrink; hard cap 3 + rounds. + +Seed intake: the #41 clutter-inventory seed candidates enter at judge intake — +**full intake for every run after calibration pass #1** (see the one-off +carve-out below). + +### Rule-quality tests the report enforces + +- **The rule is the CLASS, never the PATH.** A glob may hardcode a name that + *recurs by convention* (`PRD.md`, `HANDOFF-*.md`, `migration-report.md`); it + may NOT hardcode an identifier *unique to one instance* (a run-id, hash, or + bare timestamp). A rule matching one file today is fine; a rule that can + only EVER match one file is a failed generalization — flag loudly, never + silently persist. +- **Glob-breadth tie-breaker: prefer the NARROWER glob.** Too-narrow fails + safe (leaves clutter; the recurring pass catches it next round — self- + healing). Too-broad fails dangerous (deletes a keeper — not self-healing). + Readability beats cleverness when the cost is one missed round. + +## 9. Calibration pass #1: cc-os (#45) + +Project: **cc-os** — proves the protocol *works* before testing whether it +*generalizes*; the self-referential risk is accepted and paid for by the +validation criteria carrying the weight. + +- **Precision hard gate:** the pass FAILS if any rule persisted to the + rulebook has a glob matching a protected path — regardless of behavior tier + (a confirm gate is a safety property of the human sitting there, not of the + protocol). Exploration-time consult verdicts on protected paths are FREE. + Protected set (fixed before the pass, human-edited, never revised after): + eval `scenarios/`/`scenarios-reserve/`/`fixture/`/judge-rubric.md; + `openspec/specs/`; `docs/adr/**`; mirrored `.claude/`/`.codex/`/`.pi/` skill + dirs; `CLAUDE.md`; plugin source. +- **Recall floor: 8 of the 10 cc-os rows of the #41 inventory**, with 4 + mandatory (missing any fails the pass): `autoresearch//`, + `HANDOFF-*.md`, `docs/adr/migration-report.md`, + `.dochygiene/report.{json,md}`. `graphify-out/` is **void, not a miss** (it + is IGNORE surface per #43). The recall floor is a grading bar, not runtime + behavior — err-toward-keeping comes from the hard gate + consult. +- **Seed hold-out (one-off for pass #1 ONLY):** the cc-os rows of #41 are the + sealed answer key and are withheld from judge intake — otherwise the pass is + an open-book exam. This deliberately deviates from #42; **every later run + uses full seed intake.** Do not mistake the carve-out for a permanent + property. +- Novel matches beyond the answer key are expected and human-spot-checked — + a wrong novel match triggers rule adjustment + a retest round, not failure. +- A do-nothing pass cannot pass: the recall floor makes the pass falsifiable + in the finding direction. + +## Out of scope for this design + +Shipping the recurring cross-project categorize-and-learn skill (charted as +fog on map #31). Ignore-surface propagation into other tools' config +(rejected — ADR-0040); if disposable files polluting the knowledge graph later +proves painful, the fix belongs to graphify or os-vault:onboard-project.