# Project Context

## Purpose

**doc-hygiene** is a Claude Code plugin that monitors and manages **stale** and
**bloated** project documentation. It is installed globally but operates
per-project. It reminds the developer (deterministically, zero AI tokens) on
`SessionStart` when docs haven't been checked recently, then checks and cleans
on demand via skills.

The plugin holds a core distinction:

- **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 an auto-injected
file (`CLAUDE.md`, memory index) misleads every session, so it is worse than the
same line in a doc nobody opens.

### Design Principles

1. **Deterministic-first** — scan, state, patch-apply, and token-estimate are
   scripts (no model). AI does only classification and prose distillation.
2. **Remind, don't nag** — the `SessionStart` hook only reminds; it never runs
   analysis or mutates anything. All mutation is user-invoked. Reminders snooze
   (at most once/day while stale).
3. **Non-intrusive** — state lives in-project under a gitignored `.dochygiene/`;
   no global index. The tool never silently edits the user's repo.
4. **Git-safe cleanup** — runs only on a clean/committed tree (or after an auto
   WIP checkpoint); each run lands as one reviewable commit.
5. **The tool must not become the bloat it polices** — report rollover keeps
   only the latest report.

## Tech Stack

- **Plugin format:** Claude Code plugin (skills + commands + a `SessionStart`
  hook declared in `hooks/hooks.json`, emitting a `systemMessage` banner).
- **Language:** Python, OOP — small single-responsibility classes, dependency
  injection, immutable transforms where possible.
- **Scripts:** structured JSON output, correct exit codes, idempotent, testable
  in isolation with injected clock/filesystem.
- **AI layers:** classification = Sonnet (hard cases → Opus); generative
  distillation = Sonnet (explicitly not Haiku); orchestration = Opus.

## Project Conventions

### Code Style

- Small composable single-responsibility classes; dependency injection for
  testability; immutable transforms where practical.
- Scripts emit structured JSON and correct exit codes; deterministic and
  idempotent.

### Architecture Patterns

- **Deterministic / AI split:** the deterministic scanner gathers objective
  signals and a candidate shortlist; the AI pass classifies and distills.
- **Report schema is the linchpin:** the machine report (per-file category,
  signals, op, op-type, safety tier, optional exact-edit, token estimate) is the
  contract every component consumes. It is designed and frozen first.
- **Operation taxonomy:** each op is tagged op-type (`deterministic` |
  `generative`) and safety tier (`auto` | `confirm`). `auto` runs without
  prompt; `confirm` (destructive/subjective/generative) escalates for approval.
- **mtime guard:** never apply a cached edit to a file changed since the check.

### Testing Strategy

- Assert external behavior at the highest deterministic seam — given an input
  doc tree / state file / report, the script produces correct structured output
  and exit code. Do not assert internal class structure.
- The AI classification layer is pinned by **golden examples**
  (`examples/golden/`) plus `invariants.md`, per the reversion-protection
  pattern — not by unit assertions.

### Git Workflow

- Cleanup requires a clean/committed working tree, or auto-commits a WIP
  checkpoint first; each cleanup run lands as a single reviewable commit.
- `confirm`-tier approvals are recorded to a decisions log.
- No pushing or outbound/network actions — cleanup is local commits only.

## Important Constraints

- The `SessionStart` hook spends no AI tokens, never mutates, keeps `timeout`
  low (≤5s), and always exits 0 (never blocks the session).
- State and the single most-recent report live under gitignored `.dochygiene/`;
  the scanner always self-excludes it.
- Frozen/ignored files (`hygiene: frozen` frontmatter, `.dochygiene-ignore`,
  append-only logs) are never flagged.
- Changing any behavioral invariant requires updating `invariants.md` and the
  golden examples, with explicit human approval.

## External Dependencies

- Claude Code plugin/hook runtime (`hooks/hooks.json`, `systemMessage` banner).
- A local tokenizer approximation for the token estimator (no API token
  counting at check time).
