SecondBrain/user-guide/os-doc-hygiene-user-guide.md

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.
type/user-guide
tool/os-doc-hygiene
tool/claude-code
project/cc-os
domain/documentation
global 2026-07-15 2026-07-15
cc-os-hub
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.json at 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 global plugins/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 lifetime field 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 from lifetime: 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 (no lifetime) is silently swallowing it.

Nuances & gotchas

  • temporary deletion needs BOTH conditions, not either. A file is only deleted under a temporary-lifetime rule once it is both ranked past retain_recent (i.e. not one of the N newest matches) and older than max_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_when vs served_when_path is a trust boundary, not a style choice. served_when is free text judged by the LLM classifier — it is always confirm-tier, never auto, regardless of git state. served_when_path is a path pattern the scanner can prove deterministically (e.g. "moved into archive/{id}/") — it can be auto-tier. If you want a rule to eventually run silently, the path is to graduate it from served_when to served_when_path by adopting a structural completion convention (see conventions.json below) — not to just mark it confirm: true and move on.
  • A rule missing confirmed_by is 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 malformed confirmed_by/confirmed_on before assuming the glob is wrong.
  • Safety tiers are about evidence quality and recoverability, not file type. auto requires deterministic + reversible + objective; confirm covers 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 :check time but got dirtied before :clean runs will be downgraded to confirm (or skipped as git-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, .md files only. It doesn't cache or incrementally diff; every :check re-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 SessionStart hook only reminds — never analyzes or mutates. It fires at most once per calendar day while docs are stale (keyed off last_reminded), and spends zero AI tokens. If you see the reminder banner, no scan has happened yet — you still have to run :check to get real findings.
  • New-project onboarding is not automatic. A fresh project has no .dochygiene-rules.json. Until you run :calibrate once to generate it, only the global IGNORE seeds apply and no lifecycle deletions happen — :check/:clean still 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 :calibrate when 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 rm in 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, :calibrate protocol.
  • 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 graduates served_when rules to auto-tier served_when_path.
  • docs/adr/0038-*.md through 0041-*.md (cc-os repo) — rulebook location, deletion autonomy tiers, no-ignore-propagation, and determinism-promotion decisions.
  • cc-os-hub — the cc-os plugin family this tool belongs to.