9.9 KiB
9.9 KiB
| type | title | summary | tags | scope | last_updated | date | related | source | ||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| user-guide | os-doc-hygiene — User Guide | Mental model and gotchas for using the os-doc-hygiene Claude Code plugin well — the stale-vs-bloat distinction, check/calibrate/clean/sweep semantics, the two rule files, rule-activation pitfalls, safety tiers, and report-reading traps. |
|
global | 2026-07-15 | 2026-07-15 |
|
cc-os |
os-doc-hygiene — User Guide
What it is
os-doc-hygiene is a globally-installed Claude Code plugin that monitors and manages
project documentation across two independent failure modes: docs that are wrong and
docs that are true but bloated. It reminds passively every session (zero tokens, no
mutation) and does its actual work — scanning, classifying, and cleaning — only on
demand via skills. It is not a linter for prose quality; it is a lifecycle manager for
.md files, extended (2026-07-15) with a rulebook that can also delete or archive
files on a schedule or trigger.
Mental model
- Stale vs bloat is the core axis, and they are not the same failure. Stale means the doc is wrong — contradicted, orphaned, superseded, provisional-but-abandoned, completed-in-place, or duplicated; the remedy is fix or remove. Bloat means the doc is true but mostly irrelevant at its current altitude — the remedy is to distill, split, or freeze it, almost never to delete history. Conflating the two leads to either deleting a correct-but-long doc or leaving a wrong-but-short one in place.
- Severity scales with injection frequency, not file size. A stale line in a file
that gets auto-injected every session (e.g. a
CLAUDE.md) is worse than the same line in a doc nobody opens, even if the second doc is larger. - The skill quartet has distinct jobs, not a redundant surface:
:check— scan + classify against the existing rules, writes a report. Does not learn anything new.:calibrate— the learn-new-rules loop. It clusters the pool of files no existing rule governs, proposes candidate globs, and gets them judged and persisted. It is recurring and self-narrowing: each pass only looks at what's still unmatched, and it hard-caps at 3 rounds per invocation (stops early at <2 new rules or <10% unmatched shrink).:clean— applies the most recent report's findings (deterministic ops mechanically, generative ops via a subagent, confirm-tier gated).:sweep— check-then-clean back to back; it does not skip clean's confirm gate, it just chains the two.
- Rules live in two places with different trust levels. A committed,
per-project
.dochygiene-rules.jsonat the repo root (reviewable, versioned, travels with the repo — ADR-0038) holds the project's actual lifecycle rules. A gitignored.cc-os/dochygiene/directory (legacy fallback path:.dochygiene/) holds only local state and reports — never rules. The plugin also ships a globalplugins/os-doc-hygiene/rulebook.json, but that ships only IGNORE seeds (graphify-out/**,.dochygiene/**as of 2026-07-15) — it is not a source of general-purpose lifecycle rules for your project. - A rule with no
lifetimefield is not "keep" — it's an IGNORE sentinel. IGNORE paths are pruned from the scan walk entirely; they never appear in a report, not even as "kept." This is distinct fromlifetime: keep, which is walked and reported, just never deleted. If you expect a file to show up as "kept" in a report and it never does, check whether an IGNORE rule (nolifetime) is silently swallowing it.
Nuances & gotchas
temporarydeletion needs BOTH conditions, not either. A file is only deleted under atemporary-lifetime rule once it is both ranked pastretain_recent(i.e. not one of the N newest matches) and older thanmax_age_days. A file that is old but still in the top-N-newest survives; a file that's low-ranked but too young also survives. Don't reason about temporary rules with just one of the two numbers.served_whenvsserved_when_pathis a trust boundary, not a style choice.served_whenis free text judged by the LLM classifier — it is always confirm-tier, never auto, regardless of git state.served_when_pathis a path pattern the scanner can prove deterministically (e.g. "moved intoarchive/{id}/") — it can be auto-tier. If you want a rule to eventually run silently, the path is to graduate it fromserved_whentoserved_when_pathby adopting a structural completion convention (see conventions.json below) — not to just mark itconfirm: trueand move on.- A rule missing
confirmed_byis silently inactive. It doesn't error, doesn't warn loudly — it just never acts. If a rule you added doesn't seem to be doing anything, check for a missing or malformedconfirmed_by/confirmed_onbefore assuming the glob is wrong. - Safety tiers are about evidence quality and recoverability, not file type.
autorequires deterministic + reversible + objective;confirmcovers anything destructive, subjective, or generative. Critically: a dirty or untracked file always escalates to confirm even if a rule would otherwise make it auto — there's no git history to recover from. And git state is re-verified at apply time, not trusted from when the report was generated — a file that was tracked+clean at:checktime but got dirtied before:cleanruns will be downgraded to confirm (or skipped asgit-state-changed-since-check). - The report's "cleared" count is not what it sounds like.
cleared= shortlist entries minus findings — i.e. candidates the deterministic scanner surfaced but the classifier decided need no action. It can coincidentally equal (or come close to) the total files-scanned count, which misleads you into thinking "cleared" means "scanned." The AI classifier only ever sees the signal-bearing subset the scanner flagged — in the first cc-os run, that was 44 of 384 files scanned; the rest were cleared deterministically and never reached the model at all. - There is deliberately no calendar-date staleness signal. The scanner does not flag "this file hasn't been touched in N days" as a signal by itself. Staleness comes from broken references, file length (bloat), edit-recency-vs-churn patterns, and lifecycle rules — not raw age. A quiet-but-correct doc is not stale just because it's old.
- The scanner is a pure script — no tokens, full re-walk every run,
.mdfiles only. It doesn't cache or incrementally diff; every:checkre-walks the whole tree (minus exclusions). Don't expect it to pick up non-Markdown docs (READMEs in other formats, code comments) as scan targets. - The
SessionStarthook only reminds — never analyzes or mutates. It fires at most once per calendar day while docs are stale (keyed offlast_reminded), and spends zero AI tokens. If you see the reminder banner, no scan has happened yet — you still have to run:checkto get real findings. - New-project onboarding is not automatic. A fresh project has no
.dochygiene-rules.json. Until you run:calibrateonce to generate it, only the global IGNORE seeds apply and no lifecycle deletions happen —:check/:cleanstill work for stale/bloat findings, but the lifecycle layer is inert.
When NOT to use it / limits
- Not a prose/style linter — it does not check grammar, tone, or writing quality.
- Not a general file manager — the lifecycle/delete features only ever act on paths matched by an explicitly confirmed rule; unmatched files are left alone ("unmatched = unmanaged").
- Not a substitute for
:calibratewhen a project has structurally unusual doc conventions (e.g. specs/plans as the shipped product) — the global rulebook alone will under- or over-match. - Don't expect deletions to be silently recoverable outside git — deletion is a real
git rmin a dedicated commit; git history is the only archive. There is no graveyard directory. - Not designed to run unattended in CI as a destructive step without review — confirm- tier gates exist precisely because classifier judgment and dirty/untracked state need a human.
Command reference
| Command | Effect |
|---|---|
/os-doc-hygiene:status |
Read-only timestamps (last check/clean/reminded) + report presence. No model, no scan. |
/os-doc-hygiene:check [--scope <glob-or-path>] [--category <class|subtype>] |
Scan + classify against existing rules, write a report. |
/os-doc-hygiene:calibrate |
Learn-new-rules loop over the unmatched pool; hard cap 3 rounds; nothing persists without the human seeing the rule report first. |
/os-doc-hygiene:clean [--scope <glob-or-path>] [--category <class|subtype>] |
Apply the latest report's findings; confirm-tier entries gated. |
/os-doc-hygiene:sweep [--scope <glob-or-path>] [--category <class|subtype>] |
check then clean, same confirm gate as standalone clean. |
Pointers
plugins/os-doc-hygiene/CLAUDE.md(cc-os repo) — build map + the stale-vs-bloat invariant summary.plugins/os-doc-hygiene/lifecycle-spec.md(cc-os repo) — the full lifecycle-rules design: taxonomy, rulebook schema, deletion semantics,:calibrateprotocol.plugins/os-doc-hygiene/invariants.md(cc-os repo) — the reversion-protection contract; read before changing any behavioral invariant.plugins/os-doc-hygiene/conventions.json(cc-os repo) — the determinism-promotion catalog (archive-bucket,status-frontmatter) that graduatesserved_whenrules to auto-tierserved_when_path.docs/adr/0038-*.mdthrough0041-*.md(cc-os repo) — rulebook location, deletion autonomy tiers, no-ignore-propagation, and determinism-promotion decisions.
Related
- cc-os-hub — the cc-os plugin family this tool belongs to.