# doc-hygiene

Claude Code plugin that monitors and manages **stale** and **bloated** project
documentation. Installed globally, operates per-project. Reminds (deterministic,
zero-token) on `SessionStart`; checks and cleans on demand via skills.

> Read `PRD.md` first — it is the source of truth for scope, decisions, and
> rationale. This file is the build map.

> Vault user guide (nuance/mental-model, not a how-to):
> `~/Documents/SecondBrain/user-guide/os-doc-hygiene-user-guide.md`.

## Lifecycle layer (2026-07-15)

A rulebook layer sits on top of the scan/classify/clean core: global
`rulebook.json` + optional per-project `.dochygiene-rules.json` declare
lifetime rules for known-temporary artifacts, feeding tier derivation and a
`/os-doc-hygiene:calibrate` skill for tuning rules against a real project.
Read `lifecycle-spec.md` before touching `rulebook.py`, the scanner's
lifecycle signals, or the calibrate skill; decisions are ADR-0038–0041.

## Core distinction (do not conflate)

- **Stale** = the doc is *wrong* (contradicted, orphaned, superseded,
  provisional, completed-in-place, duplicated). Remedy: fix or remove.
- **Bloat** = the doc is *true but mostly irrelevant* (distill, split, freeze).
  Remedy: change its altitude, almost never delete history.

Severity scales with **injection frequency** — a stale line in `CLAUDE.md` (auto
-injected every session) is worse than the same line in a doc nobody opens.

## Design invariants (see PRD + reversion-protection pattern)

1. The `SessionStart` hook **only reminds** — it never runs analysis or mutates
   anything, and spends no AI tokens. All mutation is user-invoked.
2. Reminder **snoozes** (≤ once/day while stale) via `last_reminded`.
3. State lives **in-project** under gitignored `.cc-os/dochygiene/` (ADR-0027;
   legacy `.dochygiene/` is fallback-read only). No global index.
4. **Report rollover**: keep only the latest report. The tool must not become
   the bloat it polices.
5. Cleanup is **git-safe**: clean/committed tree required (or auto WIP
   checkpoint), each run is one reviewable commit.
6. **Deterministic-first**: scan/state/patch-apply/token-estimate are scripts
   (no model). AI does only classification and prose distillation.
7. **Safety tiers**: `auto` (deterministic+reversible+objective) runs without
   prompt; `confirm` (destructive/subjective/generative) escalates for approval.
8. **mtime guard**: never apply a cached edit to a file changed since the check.
9. Frozen/ignored files (`hygiene: frozen` frontmatter, `.dochygiene-ignore`,
   append-only logs) are never flagged.

Changing any invariant requires updating `invariants.md` and the golden
examples, with explicit human approval.

## Build order (report schema gates everything)

1. **Machine report schema** — the contract every component consumes. Freeze
   first. (per-file: category, signals, op, op-type, safety tier, optional
   exact-edit, token estimate.)
2. **Deterministic core** — *build-spike #1 first*: a trivial `hooks/hooks.json`
   emitting a `systemMessage` banner on `SessionStart`, confirmed to render
   visibly in a real session (no plugin in this collection wires a CC hook yet).
   Then: scanner (signals + shortlist), state store (timestamps, rollover,
   snooze, atomic writes, project-root resolution), reminder hook
   (`SessionStart`, `matcher: startup|resume`, injected-clock testable).
3. **`check` skill** — scanner shortlist → AI classify (Sonnet, judgment only) →
   **`report_builder.py`** deterministic finalize pass → human + machine reports →
   update `last_check`. The finalize pass sits **between** model classification and
   the report write (invariants #6, #10): it computes `expected_sha256`, looks up
   `is_destructive`/`is_reversible` from `exact_edit.kind`, derives `safety_tier`
   via `validate_report.py`'s single-source `derive_safety_tier(...)`, sources
   `raw_tokens` from the token estimator, and assembles the schema-valid machine
   report + human-report skeleton. The model authors none of those four fields.
4. **`clean` skill + patch-applier** — consume report, mtime-guard, apply
   `deterministic` ops mechanically, delegate `generative` to Sonnet, gate
   `confirm`, git checkpoint + single commit, scopable by category/file →
   update `last_clean`. Then **`sweep`** = check-then-clean.
   - **RESOLVED (Phase 4):** `insert-frontmatter` ops have `has_anchor=False` and
     carry **no** `expected_sha256`, so invariant #8's content hash cannot protect
     them. The applier re-derives frontmatter freshness at apply time: re-read the
     file, parse frontmatter; key present with target value → idempotent no-op; key
     present with different value → skip (`frontmatter-key-conflict`); key absent →
     insert. No cached hash is trusted. This resolves the deferred hole noted in
     `add-check`.
5. **Bonus (v2)** — deterministic patch-apply hardening + token estimator
   (local tokenizer, injection-frequency weighting, bottom-up rollup).

## Intended layout (per cc-plugins conventions)

```
doc-hygiene/
├── .claude-plugin/plugin.json      # done
├── PRD.md                          # done — source of truth
├── CLAUDE.md                       # this file
├── invariants.md                   # TODO — declare behavioral invariants
├── skills/                         # slash-command entry points: /os-doc-hygiene:<skill>
│   ├── check/SKILL.md
│   ├── clean/SKILL.md
│   ├── status/SKILL.md
│   └── sweep/SKILL.md
├── scripts/                        # Python OOP — scanner, state, reminder, estimator, report_builder, applier
│   └── ...
├── hooks/hooks.json                # SessionStart wiring → reminder script (systemMessage banner)
├── examples/golden/                # classifier golden examples (input tree → expected report)
└── tests/                          # unit tests per deterministic seam + fixture doc trees
```

## Conventions (inherited from cc-plugins)

- **Language**: Python, OOP — small single-responsibility classes, dependency
  injection, immutable transforms where possible. See
  `../cc-architect/references/tool-patterns/deterministic-scripting.md`.
- **Scripts**: structured JSON output, correct exit codes, idempotent, testable
  in isolation with injected clock/filesystem.
- **Model routing**: scripts = no model; classification = Sonnet (hard cases →
  Opus); generative distillation = Sonnet (NOT Haiku); orchestration = Opus.
- **Reversion protection**: `invariants.md` + `examples/golden/` + change-impact
  analysis + human gate for golden-example changes. See
  `../cc-architect/references/tool-patterns/reversion-protection.md`.
- Read a directory's `CONTEXT.md` before its source files (progressive
  disclosure); generate one per directory as it's built.

## Completion steps (do NOT do until functional)

1. Add `invariants.md` and 3–5 golden examples.
2. Register in `../.claude-plugin/marketplace.json` (name + source + description,
   matching `plugin.json`). See `../.claude/references/plugin-registration.md`.
3. Verify installable via `/install doc-hygiene@cc-plugins`.
