# skills/

User-invoked skills for the doc-hygiene plugin. Skills own the multi-step
workflows that mix deterministic scripts (invariant #6 — no model) with the
narrow model steps (classification, distillation). They are invoked directly
as slash commands (`/os-doc-hygiene:check`, `/os-doc-hygiene:clean`,
`/os-doc-hygiene:status`, `/os-doc-hygiene:sweep`) — never by the
`SessionStart` hook (invariant #1 — the hook only reminds).

## Contents

| Path | Purpose |
|------|---------|
| `check/SKILL.md` | The `check` orchestration: **scan → classify → finalize → validate → write → stamp**. (D) `scanner.py` produces the signal-bearing shortlist; candidates = the keys of `signals` (shortlist paths with zero signals are presumptively cleared, never read by the model). (M) ONE **Sonnet** subagent classifies all candidates via `workflows/classify-candidates.md`, returning slim per-file proposals (judgment only); single-file **Opus** escalation on low-confidence hard distinctions (stale-vs-bloat, delete-vs-rewrite). (logic) `--category` filter applied at the entry stage *after* classification. (D) `report_builder.py` finalizes — fills the four guardrail fields the model must not author (`expected_sha256`, `safety_tier`, `is_destructive`/`is_reversible`, `raw_tokens`) and emits a schema-valid machine report + human-report skeleton. (D) `validate_report.py` validates **on the scratch path BEFORE writing** (write_report is destructive-first; invariant #4). (D) `StateStore.write_report` does the rollover write and `set_last_check` stamps `last_check` = the validated report's own `generated_at`. All intermediates live in a session scratch dir; nothing touches `.cc-os/dochygiene/` until the validated write. **LOOP GUARD:** the subagent prompt points to `workflows/classify-candidates.md`, NEVER to `SKILL.md`. **SUBAGENT AUTHORIZATION:** every subagent dispatch includes an explicit terminal-authorization directive (REPORT-AND-EXIT, never block). |
| `check/workflows/classify-candidates.md` | Self-contained subagent workflow for the classification step. Defines the slim proposal object (`path`, `category` {class, subtype}, pass-through `signals`, `op`, `op_type`, optional `exact_edit`/`reducible_range`, `gloss`, `confidence`, `escalate`), the closed enums, the situation→kind→tier decision table, and the per-kind required `exact_edit` fields. The subagent reads each file and returns judgment only — it never authors hashes, token counts, safety tiers, or reversibility. |
| `clean/SKILL.md` | The `clean` orchestration: **load → validate → filter → gate → preflight → apply-deterministic → apply-generative → commit → stamp**. (D) `StateStore` loads and re-validates the machine report. (logic) Incompatible-pair detection (files with both generative + deterministic ops) and partition by `safety_tier`/`op_type`. (M-GATE) Confirm gate for `confirm`-tier entries; auto-tier entries apply silently. (D) Git preflight: record baseline, detect untracked files (which get skipped). (D) `patch_applier.py` applies deterministic entries mechanically; mtime-guarded, output include `applied`/`skipped`/`failed`. (M) Sonnet subagent distills each generative entry via `workflows/distill.md` (LOOP-GUARD + SUBAGENT AUTHORIZATION — terminal, REPORT-AND-EXIT). (D) `git-context commit-apply` produces exactly one cleanup commit. (D) `StateStore.set_last_clean` stamps the commit instant. Scoped by `--scope` (glob-or-path) and `--category` (class or subtype), both entry-stage filters. On failure (rollback-worthy): all intermediate mutations reverted to baseline. |
| `clean/workflows/distill.md` | Self-contained subagent workflow for generative distillation. Receives live file contents (not disk-read), category, op, and signals. Performs `distill` (condense in place, preserve frontmatter and structure, ~40–60% target length), `split` (extract to archive + pointer, archive path under `archive/`), or prose-rewrite ops (`provisional`, `contradicted`). Returns structured output (`DISTILL_RESULT_START`/`END` for single-file; `SPLIT_PRIMARY_START`/`END` + `SPLIT_ARCHIVE_DEST` + `SPLIT_ARCHIVE_START`/`END` for split). Never writes files or calls git; never re-reads disk. |
| `status/SKILL.md` | Read-only lifecycle-timestamp read (no model, no scan): `last_check`/`last_clean`/`last_reminded` + report presence, via `state_store.py`. |
| `sweep/SKILL.md` | Convenience wrapper: invokes `check` then `clean` in sequence, passing `--scope`/`--category` through to both. The confirm gate in `clean` still applies unchanged. |

## Planned additions (future changes)
