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

165 lines
9.9 KiB
Markdown

---
type: user-guide
title: os-doc-hygiene — User Guide
summary: 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.
tags:
- type/user-guide
- tool/os-doc-hygiene
- tool/claude-code
- project/cc-os
- domain/documentation
scope: global
last_updated: 2026-07-15
date: 2026-07-15
related:
- cc-os-hub
source: 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.
## Related
- [[cc-os-hub]] the cc-os plugin family this tool belongs to.