2026-07-03 15:12:31 +00:00
|
|
|
|
# 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.
|
|
|
|
|
|
|
2026-07-15 11:41:59 +00:00
|
|
|
|
## 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.
|
|
|
|
|
|
|
2026-07-03 15:12:31 +00:00
|
|
|
|
## 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 `.dochygiene/`. 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
|
2026-07-03 15:23:24 +00:00
|
|
|
|
├── skills/ # slash-command entry points: /os-doc-hygiene:<skill>
|
|
|
|
|
|
│ ├── check/SKILL.md
|
|
|
|
|
|
│ ├── clean/SKILL.md
|
|
|
|
|
|
│ ├── status/SKILL.md
|
|
|
|
|
|
│ └── sweep/SKILL.md
|
2026-07-03 15:12:31 +00:00
|
|
|
|
├── 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`.
|