From 7ae45c7bf8d185a498aee7ea45f58ffbf58198e8 Mon Sep 17 00:00:00 2001 From: jared Date: Fri, 3 Jul 2026 11:23:24 -0400 Subject: [PATCH] Align os-doc-hygiene skill naming and invocation with marketplace convention Rename skills to bare verbs (hygiene-check -> check, hygiene-clean -> clean), matching the os-vault/os-orchestration pattern. Remove the commands/hygiene.md dispatcher in favor of two new skills, status and sweep, so all four actions are invoked directly as /os-doc-hygiene: instead of through a custom subcommand parser that no other plugin in the marketplace uses. Co-Authored-By: Claude Sonnet 5 --- CLAUDE.md | 4 +- plugins/os-doc-hygiene/CLAUDE.md | 9 +- .../HANDOFF-add-deterministic-core.md | 2 +- plugins/os-doc-hygiene/commands/CONTEXT.md | 21 ---- plugins/os-doc-hygiene/commands/hygiene.md | 103 ------------------ .../examples/golden/classifier/README.md | 6 +- .../openspec/specs/doc-check/spec.md | 18 +-- .../openspec/specs/doc-clean/spec.md | 36 +++--- plugins/os-doc-hygiene/scripts/reminder.py | 4 +- plugins/os-doc-hygiene/skills/CONTEXT.md | 17 +-- .../skills/{hygiene-check => check}/SKILL.md | 10 +- .../workflows/classify-candidates.md | 0 .../{hygiene-clean => clean}/CONTEXT.md | 4 +- .../skills/{hygiene-clean => clean}/SKILL.md | 30 ++--- .../workflows/distill.md | 2 +- plugins/os-doc-hygiene/skills/status/SKILL.md | 39 +++++++ plugins/os-doc-hygiene/skills/sweep/SKILL.md | 26 +++++ .../tests/test_classifier_golden.py | 2 +- 18 files changed, 139 insertions(+), 194 deletions(-) delete mode 100644 plugins/os-doc-hygiene/commands/CONTEXT.md delete mode 100644 plugins/os-doc-hygiene/commands/hygiene.md rename plugins/os-doc-hygiene/skills/{hygiene-check => check}/SKILL.md (97%) rename plugins/os-doc-hygiene/skills/{hygiene-check => check}/workflows/classify-candidates.md (100%) rename plugins/os-doc-hygiene/skills/{hygiene-clean => clean}/CONTEXT.md (94%) rename plugins/os-doc-hygiene/skills/{hygiene-clean => clean}/SKILL.md (95%) rename plugins/os-doc-hygiene/skills/{hygiene-clean => clean}/workflows/distill.md (98%) create mode 100644 plugins/os-doc-hygiene/skills/status/SKILL.md create mode 100644 plugins/os-doc-hygiene/skills/sweep/SKILL.md diff --git a/CLAUDE.md b/CLAUDE.md index 5441550..2dca195 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -89,8 +89,8 @@ to those two and fix the stale doc. **Global os-doc-hygiene plugin** — `cc-os/plugins/os-doc-hygiene/` (git-tracked, 2026-07-03); symlinked into `~/.claude/plugins/os-doc-hygiene` - Hooks: `hooks/hooks.json` → SessionStart hook (matcher: startup|resume) runs `scripts/reminder.py` via `${CLAUDE_PLUGIN_ROOT}` (5s timeout), emitting a deterministic zero-token reminder banner -- Behavior: Monitors stale and bloated project documentation per-project under `.dochygiene/` state dir (gitignored). SessionStart reminder is deterministic (no AI tokens, once/day snooze). Skills: `hygiene-check` (AI-assisted classification of staleness signals, emits machine+human reports), `hygiene-clean` (AI-assisted or deterministic patch application with git-safe scoped cleanup). Reversion-protected via invariants.md + golden-example test fixtures. -- Migration: migrated from standalone repo (`~/dev/cc-plugins/doc-hygiene/`, 2026-07-03) and integrated into cc-os. No content conflicts detected (doc-hygiene scope does not overlap cc-os memory system). Renamed from `doc-hygiene` to `os-doc-hygiene` per cc-os plugin naming convention. +- Behavior: Monitors stale and bloated project documentation per-project under `.dochygiene/` state dir (gitignored). SessionStart reminder is deterministic (no AI tokens, once/day snooze). Skills (verb-first, no `commands/` dispatcher — invoked directly as `/os-doc-hygiene:`, per [[cc-os-plugin-skill-naming-convention]]): `check` (AI-assisted classification of staleness signals, emits machine+human reports), `clean` (AI-assisted or deterministic patch application with git-safe scoped cleanup), `status` (read-only lifecycle-timestamp read), `sweep` (check then clean in sequence). Reversion-protected via invariants.md + golden-example test fixtures. +- Migration: migrated from standalone repo (`~/dev/cc-plugins/doc-hygiene/`, 2026-07-03) and integrated into cc-os. No content conflicts detected (doc-hygiene scope does not overlap cc-os memory system). Renamed from `doc-hygiene` to `os-doc-hygiene` per cc-os plugin naming convention. Skills renamed `hygiene-check`/`hygiene-clean` → `check`/`clean`, and the `commands/hygiene.md` dispatcher removed in favor of two new skills (`status`, `sweep`), aligning with the `os-vault`/`os-orchestration` pattern of no `commands/` directory (2026-07-03). **Graphify** — v0.8.31 at `/home/jared/.local/bin/graphify` - Vault graph: `~/Documents/SecondBrain/graphify-out/` — disposable, structural index rebuilt by SessionStart hook; handles relational/graph queries. Distinct from vault-index.json (planned, Plan B Phase 1): a flat `{tag: [{path, title, summary}]}` lookup rebuilt at SessionEnd, queried by the `/memory-find` skill for fast tag-based SB note discovery. diff --git a/plugins/os-doc-hygiene/CLAUDE.md b/plugins/os-doc-hygiene/CLAUDE.md index cf8311f..5169b24 100644 --- a/plugins/os-doc-hygiene/CLAUDE.md +++ b/plugins/os-doc-hygiene/CLAUDE.md @@ -79,10 +79,11 @@ doc-hygiene/ ├── PRD.md # done — source of truth ├── CLAUDE.md # this file ├── invariants.md # TODO — declare behavioral invariants -├── commands/ # /hygiene entry points (check / clean / sweep / status) -├── skills/ -│ ├── hygiene-check/SKILL.md -│ └── hygiene-clean/SKILL.md +├── skills/ # slash-command entry points: /os-doc-hygiene: +│ ├── 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) diff --git a/plugins/os-doc-hygiene/HANDOFF-add-deterministic-core.md b/plugins/os-doc-hygiene/HANDOFF-add-deterministic-core.md index 97f7d12..165dae0 100644 --- a/plugins/os-doc-hygiene/HANDOFF-add-deterministic-core.md +++ b/plugins/os-doc-hygiene/HANDOFF-add-deterministic-core.md @@ -24,7 +24,7 @@ Task 1.2 requires a human to start a real Claude Code session and **visually con **To confirm:** start a fresh Claude Code session (any directory; plugin is user-scoped) and look for, alongside the memsearch line: - [doc-hygiene] Documentation health reminder is active. Run /hygiene check to scan for stale or bloated docs. + [doc-hygiene] Documentation health reminder is active. Run /os-doc-hygiene:check to scan for stale or bloated docs. - **Renders** → check task 1.2, then do task 1.3 (record the confirmed format: plugin-root `hooks/hooks.json`, top-level `hooks` key, per-matcher nested `hooks` array, `systemMessage` field, `matcher: startup|resume`, `timeout: 5`). Then build Group 4, then Group 5. - **Blank** → the hooks.json format or loading is wrong for this build; debug BEFORE Group 4 inherits the format. (Format currently mirrors the canonical corrected shape from commit `4d595a9`, avoiding the 3 prior hook-format bugs.) diff --git a/plugins/os-doc-hygiene/commands/CONTEXT.md b/plugins/os-doc-hygiene/commands/CONTEXT.md deleted file mode 100644 index 5ebd0fe..0000000 --- a/plugins/os-doc-hygiene/commands/CONTEXT.md +++ /dev/null @@ -1,21 +0,0 @@ -# commands/ - -Slash-command entry points for the doc-hygiene plugin. Thin dispatchers — they -parse `$ARGUMENTS`, route to a skill or run a read-only state read, and surface -the result. They own no analysis logic of their own (invariant #6 lives in the -scripts/skills they call). - -## Contents - -| File | Purpose | -|------|---------| -| `hygiene.md` | `/hygiene` — the single user entry point. Parses the first token of `$ARGUMENTS` as the subcommand and passes the rest through. Subcommands: **`check [--scope ] [--category ]`** invokes the `hygiene-check` skill verbatim (the skill owns the full scan → classify → finalize → validate → write → stamp `last_check` workflow; the command does NOT scan or classify); **`status`** is read-only (no skill, no model, no scan) — calls `state_store.py` via `python3 -c` and prints the three lifecycle timestamps (`last_check`/`last_clean`/`last_reminded`) plus report presence; **`clean`** / **`sweep`** are reserved and respond "not yet implemented (Phase 4)". No args or an unrecognized subcommand prints usage then runs the `status` read. Scripts resolve under `${CLAUDE_PLUGIN_ROOT}/scripts/` (mirrors `hooks/hooks.json`). | - -## Arg semantics (pass-through to `hygiene-check`) - -- `--scope ` — narrows the scan. A glob (contains `*`) maps to the - scanner's `--globs`; a bare path is handled by the skill as a post-scan prefix - filter (the scanner's glob matcher is unreliable for mid-pattern `**`). -- `--category ` — filters which **entries** are produced, applied - by the skill *after* classification (the scanner is category-agnostic). `class` - ∈ { `stale`, `bloat` }; `subtype` is one of the closed enum values. diff --git a/plugins/os-doc-hygiene/commands/hygiene.md b/plugins/os-doc-hygiene/commands/hygiene.md deleted file mode 100644 index 7d2b3fd..0000000 --- a/plugins/os-doc-hygiene/commands/hygiene.md +++ /dev/null @@ -1,103 +0,0 @@ ---- -name: hygiene -description: Manage stale and bloated docs. `/hygiene check` scans the project for stale/bloated documentation and writes a report; `/hygiene clean` applies documented findings; `/hygiene sweep` runs check then clean; `/hygiene status` shows the last check/clean/reminded timestamps and whether a report exists. -argument-hint: "[check [--scope ] [--category ] | status | clean [--scope ] [--category ] | sweep [--scope ] [--category ]]" ---- - -# /hygiene — Documentation Hygiene - -Single entry point for the doc-hygiene plugin. Dispatch on `$ARGUMENTS`. - -Parse the first token of `$ARGUMENTS` as the subcommand; everything after it is -passed through. Scripts live under `${CLAUDE_PLUGIN_ROOT}/scripts/` (mirrors -`hooks/hooks.json`). - -## `check [--scope ] [--category ]` - -Invoke the **`hygiene-check`** skill (Skill tool, `skill: "hygiene-check"`), -passing the `--scope` and `--category` arguments through verbatim. The skill owns -the full workflow — scan → classify → finalize → validate → write report → stamp -`last_check`. Do not scan or classify here; just route to the skill and surface -its summary. - -## `status` - -Read-only. No skill, no model, no scan. Run the state store directly and report -the three lifecycle timestamps plus whether a report exists. `state_store.py` is a -module (no CLI), so call it via `python3 -c`: - -```bash -python3 -c ' -import sys, os -sys.path.insert(0, os.environ["CLAUDE_PLUGIN_ROOT"] + "/scripts") -from state_store import StateStore, resolve_project_root -from pathlib import Path -s = StateStore(resolve_project_root(Path(os.getcwd()))) -def fmt(dt): return dt.isoformat() if dt else "never" -print("last_check: " + fmt(s.get_last_check())) -print("last_clean: " + fmt(s.get_last_clean())) -print("last_reminded: " + fmt(s.get_last_reminded())) -print("report: " + ("present" if s.read_report() else "none")) -' -``` - -Present the output as a short human summary, for example: - -``` -doc-hygiene status for: - - last check 2026-06-20T14:03:11+00:00 - last clean never - last reminded 2026-06-24T09:00:00+00:00 - report present (run /hygiene check to refresh) -``` - -Do not classify or distill anything — these are timestamp reads only. - -## `clean [--scope ] [--category ]` - -Invoke the **`hygiene-clean`** skill (Skill tool, `skill: "hygiene-clean"`), -passing the `--scope` and `--category` arguments through verbatim. The skill owns -the full workflow — load report → gate confirm-tier entries → apply deterministic -ops → dispatch generative ops → commit → stamp `last_clean`. Do not apply any -edits here; just route to the skill and surface its summary. - -A report must exist before running `clean`. If the user has not yet run -`/hygiene check`, the skill will stop and instruct them to do so. - -## `sweep [--scope ] [--category ]` - -`sweep` is check-then-clean: a convenience that runs both skills in sequence, -passing the same `--scope` and `--category` through to each. - -1. **First**, invoke the **`hygiene-check`** skill (Skill tool, - `skill: "hygiene-check"`), passing `--scope` and `--category` verbatim. - Surface the check summary to the user. - -2. **Then**, invoke the **`hygiene-clean`** skill (Skill tool, - `skill: "hygiene-clean"`), passing the same `--scope` and `--category` - verbatim. - -The confirm gate in `hygiene-clean` applies identically under `sweep` — sweep -does **not** auto-approve any confirm-tier or generative entries. The user will -still be prompted to approve any entries that require it before any file -mutation occurs (invariant #7). - -`sweep` produces at most one cleanup commit (the clean step); the check step -writes only to the gitignored `.dochygiene/` and does not commit. - -## No arguments or unrecognized subcommand - -Print usage, then run the `status` block above and show current state: - -``` -/hygiene — documentation hygiene - - check [--scope ] [--category ] - scan for stale/bloated docs and write a report - status show last check/clean/reminded timestamps + report presence - clean [--scope ] [--category ] - apply documented findings (requires a report; run check first) - sweep [--scope ] [--category ] - check then clean in sequence (confirm gate still applies) -``` diff --git a/plugins/os-doc-hygiene/examples/golden/classifier/README.md b/plugins/os-doc-hygiene/examples/golden/classifier/README.md index 3cd3d31..97aab07 100644 --- a/plugins/os-doc-hygiene/examples/golden/classifier/README.md +++ b/plugins/os-doc-hygiene/examples/golden/classifier/README.md @@ -57,15 +57,15 @@ you want to confirm the classifier still reaches the expected judgment. For each case directory: -1. Run an actual classification over `input/` — either `/hygiene check - --scope /input` or by invoking the `hygiene-check` skill against that +1. Run an actual classification over `input/` — either `/os-doc-hygiene:check + --scope /input` or by invoking the `check` skill against that tree. This produces a fresh machine report. > **Confirm the invocation once the check command lands.** `--scope` is a > filter on a scan rooted at the resolved project root, not a re-root — to > classify a case in isolation you want the check *rooted at* > `/input` (as the hermetic suite roots the scanner). Verify the exact - > flag/rooting against the shipped `commands/hygiene.md` before relying on it. + > flag/rooting against the shipped `skills/check/SKILL.md` before relying on it. 2. Diff the produced classification's **stable** fields against `expected.json`, per entry: - `category.class` diff --git a/plugins/os-doc-hygiene/openspec/specs/doc-check/spec.md b/plugins/os-doc-hygiene/openspec/specs/doc-check/spec.md index 4fa74ab..972b655 100644 --- a/plugins/os-doc-hygiene/openspec/specs/doc-check/spec.md +++ b/plugins/os-doc-hygiene/openspec/specs/doc-check/spec.md @@ -6,26 +6,26 @@ TBD - created by archiving change add-check. Update Purpose after archive. ### Requirement: `/hygiene` Command Surface The plugin SHALL provide a single `/hygiene` command that dispatches on its -arguments. `/hygiene check [--scope ] [--category ]` -SHALL invoke the `hygiene-check` skill. `/hygiene status` SHALL read and report the +arguments. `/os-doc-hygiene:check [--scope ] [--category ]` +SHALL invoke the `check` skill. `/os-doc-hygiene:status` SHALL read and report the lifecycle timestamps (`last_check`, `last_clean`, `last_reminded`) and whether a -report exists, using no scan and no model. `/hygiene clean` and `/hygiene sweep` +report exists, using no scan and no model. `/os-doc-hygiene:clean` and `/os-doc-hygiene:sweep` SHALL be reserved and SHALL report that they are not yet implemented (Phase 4). No arguments or unknown arguments SHALL print usage plus the current status. #### Scenario: Check dispatches to the skill -- **WHEN** the user runs `/hygiene check` -- **THEN** the command invokes the `hygiene-check` skill, passing through any `--scope` or `--category` flag +- **WHEN** the user runs `/os-doc-hygiene:check` +- **THEN** the command invokes the `check` skill, passing through any `--scope` or `--category` flag #### Scenario: Status is read-only -- **WHEN** the user runs `/hygiene status` +- **WHEN** the user runs `/os-doc-hygiene:status` - **THEN** the command reports `last_check`, `last_clean`, `last_reminded`, and whether a report exists, without running a scan or any model #### Scenario: Clean and sweep are reserved -- **WHEN** the user runs `/hygiene clean` or `/hygiene sweep` +- **WHEN** the user runs `/os-doc-hygiene:clean` or `/os-doc-hygiene:sweep` - **THEN** the command reports that the subcommand is not yet implemented (Phase 4) and does not mutate anything #### Scenario: Unknown arguments print usage @@ -35,7 +35,7 @@ arguments or unknown arguments SHALL print usage plus the current status. ### Requirement: Check Skill Orchestrates Scan, Classification, and Report Writing -The `hygiene-check` skill SHALL orchestrate the check pipeline: run the deterministic +The `check` skill SHALL orchestrate the check pipeline: run the deterministic scanner, dispatch a Sonnet subagent for judgment-only classification of the signal-bearing candidates, run the deterministic finalize pass, validate, write the report pair, and stamp `last_check`. The skill SHALL run all non-judgment steps as @@ -47,7 +47,7 @@ after classification; both SHALL be recorded in the human-report header. #### Scenario: Skill runs the full pipeline -- **WHEN** the `hygiene-check` skill runs +- **WHEN** the `check` skill runs - **THEN** it scans (deterministic), classifies signal-bearing candidates (Sonnet), finalizes (deterministic), validates (deterministic), writes the report pair (deterministic), and stamps `last_check` (deterministic) #### Scenario: Zero-signal files are not read by the model diff --git a/plugins/os-doc-hygiene/openspec/specs/doc-clean/spec.md b/plugins/os-doc-hygiene/openspec/specs/doc-clean/spec.md index 1bfd49b..0d7c668 100644 --- a/plugins/os-doc-hygiene/openspec/specs/doc-clean/spec.md +++ b/plugins/os-doc-hygiene/openspec/specs/doc-clean/spec.md @@ -94,7 +94,7 @@ single batch-confirm list showing path, category, op, token count, and rationale per-entry opt-out, visually distinguishing irreversible `delete-range` entries from reversible entries. The approved set SHALL be all `auto` entries plus any user-approved `confirm` entries. The gate SHALL run identically under `sweep` — the -`/hygiene sweep` convenience path SHALL NOT bypass invariant #7. +`/os-doc-hygiene:sweep` convenience path SHALL NOT bypass invariant #7. #### Scenario: auto entries apply without prompt @@ -113,8 +113,8 @@ user-approved `confirm` entries. The gate SHALL run identically under `sweep` #### Scenario: sweep does not bypass the gate -- **WHEN** the user runs /hygiene sweep and the report contains confirm-tier entries -- **THEN** the confirm gate runs identically to a standalone /hygiene clean +- **WHEN** the user runs /os-doc-hygiene:sweep and the report contains confirm-tier entries +- **THEN** the confirm gate runs identically to a standalone /os-doc-hygiene:clean ### Requirement: Git-Safe Single Commit @@ -137,12 +137,12 @@ to the commit instant, not the run-start instant. #### Scenario: Clean tree produces exactly one commit -- **WHEN** the user runs /hygiene clean with a clean git tree +- **WHEN** the user runs /os-doc-hygiene:clean with a clean git tree - **THEN** the run produces exactly one git commit containing all applied edits #### Scenario: Dirty tree gets a WIP checkpoint then one cleanup commit -- **WHEN** the user runs /hygiene clean with unstaged changes in the working tree +- **WHEN** the user runs /os-doc-hygiene:clean with unstaged changes in the working tree - **THEN** the skill auto-creates a WIP checkpoint commit of the user's work, then produces exactly one cleanup commit — two commits total, cleanup still exactly one #### Scenario: All-skipped produces zero commits @@ -167,8 +167,8 @@ to the commit instant, not the run-start instant. ### Requirement: Clean Skill Orchestration -The `hygiene-clean` skill SHALL load the current report via `StateStore.read_report` -(if none, it SHALL tell the user to run `/hygiene check` and stop). It SHALL +The `clean` skill SHALL load the current report via `StateStore.read_report` +(if none, it SHALL tell the user to run `/os-doc-hygiene:check` and stop). It SHALL re-validate the loaded report via `validate_report.py` (if invalid, stop). It SHALL apply a scope/category filter to entries. It SHALL partition entries into `auto+deterministic`, `confirm+deterministic` (i.e., `delete-range`), and @@ -183,8 +183,8 @@ SHA. #### Scenario: No report — prompt to check first -- **WHEN** the user runs /hygiene clean and no report exists in .dochygiene/ -- **THEN** the skill tells the user to run /hygiene check first and stops without modifying anything +- **WHEN** the user runs /os-doc-hygiene:clean and no report exists in .dochygiene/ +- **THEN** the skill tells the user to run /os-doc-hygiene:check first and stops without modifying anything #### Scenario: Invalid report — stop @@ -228,22 +228,22 @@ be treated as `incompatible-ops-on-file` and skipped. - **WHEN** a file has both a generative entry and a deterministic entry - **THEN** the applier (or skill) skips the file with reason incompatible-ops-on-file and recommends re-analysis -### Requirement: Sweep Composes Check Then Clean at Command Level +### Requirement: Sweep Composes Check Then Clean -`/hygiene sweep` SHALL invoke the `hygiene-check` skill followed by the -`hygiene-clean` skill, passing `--scope` and `--category` to both. The sweep SHALL +`/os-doc-hygiene:sweep` SHALL invoke the `check` skill followed by the +`clean` skill, passing `--scope` and `--category` to both. The sweep SHALL NOT produce a double-commit: `check` writes only to the gitignored `.dochygiene/` directory and never commits; the single cleanup commit from `clean` is the only git -commit the sweep produces. The sweep routing SHALL live in `commands/hygiene.md`, -not in a third skill or by having the clean skill invoke the check skill internally. +commit the sweep produces. The sweep routing SHALL live in its own `sweep` skill +(`skills/sweep/SKILL.md`), not by having the `clean` skill invoke `check` internally. -#### Scenario: Sweep routes through the command, not a third skill +#### Scenario: Sweep routes through its own skill, not by nesting clean inside check -- **WHEN** the user runs /hygiene sweep -- **THEN** commands/hygiene.md invokes hygiene-check then hygiene-clean sequentially, passing shared flags +- **WHEN** the user runs /os-doc-hygiene:sweep +- **THEN** the `sweep` skill invokes the `check` skill then the `clean` skill sequentially, passing shared flags #### Scenario: Sweep produces at most one cleanup commit -- **WHEN** the user runs /hygiene sweep on a clean tree +- **WHEN** the user runs /os-doc-hygiene:sweep on a clean tree - **THEN** at most one git commit is produced (the cleanup commit from clean); check writes nothing to git diff --git a/plugins/os-doc-hygiene/scripts/reminder.py b/plugins/os-doc-hygiene/scripts/reminder.py index 0097deb..6ce727d 100644 --- a/plugins/os-doc-hygiene/scripts/reminder.py +++ b/plugins/os-doc-hygiene/scripts/reminder.py @@ -137,8 +137,8 @@ class Reminder: # --------------------------------------------------------------------------- # # DEVIATION NOTE: the `session-reminder` spec requires the banner to "name the -# slash command the user runs". That command (`/hygiene check`) is not built in -# this change, so per the task we advertise that monitoring is active without +# slash command the user runs". That command (`/os-doc-hygiene:check`) is not +# built in this change, so per the task we advertise that monitoring is active without # promising an unbuilt command. The staleness phrase IS retained (the spec also # says the banner states "how stale the docs are"), keyed off `last_check`. diff --git a/plugins/os-doc-hygiene/skills/CONTEXT.md b/plugins/os-doc-hygiene/skills/CONTEXT.md index 38fc9df..bfef37f 100644 --- a/plugins/os-doc-hygiene/skills/CONTEXT.md +++ b/plugins/os-doc-hygiene/skills/CONTEXT.md @@ -2,17 +2,20 @@ 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 dispatched by the -`/hygiene` command, never by the `SessionStart` hook (invariant #1 — the hook -only reminds). +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 | |------|---------| -| `hygiene-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 `.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). | -| `hygiene-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. | -| `hygiene-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. | -| `hygiene-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. | +| `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 `.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) diff --git a/plugins/os-doc-hygiene/skills/hygiene-check/SKILL.md b/plugins/os-doc-hygiene/skills/check/SKILL.md similarity index 97% rename from plugins/os-doc-hygiene/skills/hygiene-check/SKILL.md rename to plugins/os-doc-hygiene/skills/check/SKILL.md index 1cfc42b..37cbc88 100644 --- a/plugins/os-doc-hygiene/skills/hygiene-check/SKILL.md +++ b/plugins/os-doc-hygiene/skills/check/SKILL.md @@ -1,6 +1,6 @@ --- -name: hygiene-check -description: Scan the project for stale and bloated documentation and write a hygiene report. Runs the deterministic scanner, dispatches a Sonnet subagent to classify only the signal-bearing candidates, finalizes/validates the machine report deterministically, then writes the report pair and stamps `last_check`. Invoked by `/hygiene check [--scope ] [--category ]`. +name: check +description: Scan the project for stale and bloated documentation and write a hygiene report. Runs the deterministic scanner, dispatches a Sonnet subagent to classify only the signal-bearing candidates, finalizes/validates the machine report deterministically, then writes the report pair and stamps `last_check`. Invoked by `/os-doc-hygiene:check [--scope ] [--category ]`. --- # Hygiene Check Skill @@ -26,7 +26,7 @@ resolved. Use the session scratchpad directory for all intermediate artifacts ## Arguments -Passed through from `/hygiene check`: +Passed through from `/os-doc-hygiene:check`: - `--scope ` — narrow the scan. A glob (contains `*`) maps to the scanner's `--globs`. A **bare path** does NOT map cleanly to a `--globs` value @@ -120,7 +120,7 @@ Agent tool parameters: - description: "Classify doc-hygiene candidates" - prompt: | Read and follow the workflow at: - ${CLAUDE_PLUGIN_ROOT}/skills/hygiene-check/workflows/classify-candidates.md + ${CLAUDE_PLUGIN_ROOT}/skills/check/workflows/classify-candidates.md Project root: @@ -264,7 +264,7 @@ Reports written: /.dochygiene/report.json /.dochygiene/report.md -Run /hygiene clean to act on these (Phase 4), or /hygiene status for timestamps. +Run /os-doc-hygiene:clean to act on these (Phase 4), or /os-doc-hygiene:status for timestamps. ``` The human report header renders `scope_globs` but has no category field (the diff --git a/plugins/os-doc-hygiene/skills/hygiene-check/workflows/classify-candidates.md b/plugins/os-doc-hygiene/skills/check/workflows/classify-candidates.md similarity index 100% rename from plugins/os-doc-hygiene/skills/hygiene-check/workflows/classify-candidates.md rename to plugins/os-doc-hygiene/skills/check/workflows/classify-candidates.md diff --git a/plugins/os-doc-hygiene/skills/hygiene-clean/CONTEXT.md b/plugins/os-doc-hygiene/skills/clean/CONTEXT.md similarity index 94% rename from plugins/os-doc-hygiene/skills/hygiene-clean/CONTEXT.md rename to plugins/os-doc-hygiene/skills/clean/CONTEXT.md index 061a4c9..64c112d 100644 --- a/plugins/os-doc-hygiene/skills/hygiene-clean/CONTEXT.md +++ b/plugins/os-doc-hygiene/skills/clean/CONTEXT.md @@ -1,7 +1,7 @@ -# hygiene-clean/ +# clean/ The `clean` skill: documentation cleanup orchestrator. Loads the machine report -from `/hygiene check`, gates confirm-tier entries, applies deterministic ops +from `/os-doc-hygiene:check`, gates confirm-tier entries, applies deterministic ops via the patch applier, delegates generative distillation to a Sonnet subagent, produces exactly one cleanup commit, and stamps `last_clean`. diff --git a/plugins/os-doc-hygiene/skills/hygiene-clean/SKILL.md b/plugins/os-doc-hygiene/skills/clean/SKILL.md similarity index 95% rename from plugins/os-doc-hygiene/skills/hygiene-clean/SKILL.md rename to plugins/os-doc-hygiene/skills/clean/SKILL.md index 680d5a6..85c5145 100644 --- a/plugins/os-doc-hygiene/skills/hygiene-clean/SKILL.md +++ b/plugins/os-doc-hygiene/skills/clean/SKILL.md @@ -1,6 +1,6 @@ --- -name: hygiene-clean -description: Apply documented hygiene findings to project docs. Loads the current machine report, gates confirm-tier entries, applies deterministic ops via the patch applier, dispatches generative ops to a Sonnet subagent, stages precisely, and produces exactly one git commit. Invoked by `/hygiene clean [--scope ] [--category ]`. +name: clean +description: Apply documented hygiene findings to project docs. Loads the current machine report, gates confirm-tier entries, applies deterministic ops via the patch applier, dispatches generative ops to a Sonnet subagent, stages precisely, and produces exactly one git commit. Invoked by `/os-doc-hygiene:clean [--scope ] [--category ]`. --- # Hygiene Clean Skill @@ -27,7 +27,7 @@ project directory (`cwd`). ## Arguments -Passed through from `/hygiene clean`: +Passed through from `/os-doc-hygiene:clean`: - `--scope ` — narrow which entries to act on. Applied at **Step 3** (entry stage), same semantics as the check skill: a glob (contains `*`) matches @@ -46,7 +46,7 @@ If no arguments are given, act on all entries in the report. ### Step 1 — (D) Load report via StateStore Resolve the project root and read the current report. If no report exists, stop -immediately and tell the user to run `/hygiene check` first. +immediately and tell the user to run `/os-doc-hygiene:check` first. ```bash python3 -c ' @@ -67,7 +67,7 @@ else: ``` - If `status == "no_report"` → tell the user: "No hygiene report found. Run - `/hygiene check` first to generate one." **STOP.** + `/os-doc-hygiene:check` first to generate one." **STOP.** - If `status == "ok"` → record `PROJECT_ROOT` and `REPORT_PATH` (the canonical `.dochygiene/report.json`) for use in subsequent steps. @@ -84,7 +84,7 @@ python3 "${CLAUDE_PLUGIN_ROOT}/scripts/validate_report.py" "$REPORT_PATH" - Exit `0` → valid. Proceed to Step 3. - Exit `1` → invalid. Show the violation list from stdout and stop: "The hygiene - report is invalid. Re-run `/hygiene check` to regenerate it." **STOP.** + report is invalid. Re-run `/os-doc-hygiene:check` to regenerate it." **STOP.** - Exit `2` → usage error (file missing after Step 1 succeeded = internal bug). Stop and report. @@ -174,7 +174,7 @@ confirm_gen = [(i, e) for i, e in in_scope_clean ### Step 5 — (M-GATE) Confirm gate — BEFORE any mutation or git op > **Invariant #7:** this gate must run before the first file write or git -> operation. It runs identically under `/hygiene sweep`. +> operation. It runs identically under `/os-doc-hygiene:sweep`. Only present the gate if `confirm_det` or `confirm_gen` is non-empty. If both are empty (all entries are `auto`), skip directly to Step 6 — no prompt. @@ -371,7 +371,7 @@ For each approved generative entry (in order): - description: "Distill doc-hygiene generative entry: " - prompt: | Read and follow the workflow at: - ${CLAUDE_PLUGIN_ROOT}/skills/hygiene-clean/workflows/distill.md + ${CLAUDE_PLUGIN_ROOT}/skills/clean/workflows/distill.md File path (project-root-relative): Category: / @@ -526,27 +526,27 @@ doc-hygiene clean complete (commit: ) Confirmed (deterministic): Confirmed (generative): - Skipped: (re-analysis recommended — re-run /hygiene check) + Skipped: (re-analysis recommended — re-run /os-doc-hygiene:check) Incompatible-ops (skipped): Untracked (skipped): - ` then re-run /hygiene check> + ` then re-run /os-doc-hygiene:check> ``` If a `move-to-archive` was applied, append: ``` -Note: move-to-archive ops create link orphans. The next /hygiene check will +Note: move-to-archive ops create link orphans. The next /os-doc-hygiene:check will flag any new broken_reference signals in files that pointed to the moved doc. ``` If entries were skipped, append: ``` -Run /hygiene check to refresh the report and pick up any remaining issues. +Run /os-doc-hygiene:check to refresh the report and pick up any remaining issues. ``` --- @@ -555,8 +555,8 @@ Run /hygiene check to refresh the report and pick up any remaining issues. | Condition | Action | |-----------|--------| -| No report (Step 1) | Tell user to run `/hygiene check`. STOP. No mutation. | -| Invalid report (Step 2) | Show violations. Tell user to re-run `/hygiene check`. STOP. | +| No report (Step 1) | Tell user to run `/os-doc-hygiene:check`. STOP. No mutation. | +| Invalid report (Step 2) | Show violations. Tell user to re-run `/os-doc-hygiene:check`. STOP. | | Zero in-scope entries (Step 3) | Report no-op. STOP. No git ops. | | Approved set empty (Step 5) | Report "nothing approved". STOP. No git ops. | | Applier exit 2 (Step 7) | Rollback to baseline, abort with structured error. | @@ -590,7 +590,7 @@ echo "Rolled back to $(git rev-parse --short HEAD). No commit was created." ## LOOP GUARD The generative subagent prompt MUST point to -`skills/hygiene-clean/workflows/distill.md`, NEVER to this SKILL.md (prevents +`skills/clean/workflows/distill.md`, NEVER to this SKILL.md (prevents recursive skill invocation). **SUBAGENT AUTHORIZATION:** the distill subagent is the executor; it MUST NOT diff --git a/plugins/os-doc-hygiene/skills/hygiene-clean/workflows/distill.md b/plugins/os-doc-hygiene/skills/clean/workflows/distill.md similarity index 98% rename from plugins/os-doc-hygiene/skills/hygiene-clean/workflows/distill.md rename to plugins/os-doc-hygiene/skills/clean/workflows/distill.md index 00647dd..7f76947 100644 --- a/plugins/os-doc-hygiene/skills/hygiene-clean/workflows/distill.md +++ b/plugins/os-doc-hygiene/skills/clean/workflows/distill.md @@ -142,5 +142,5 @@ DISTILL_ERROR: 6. **Be conservative.** When uncertain whether to delete or keep a passage, keep it (distilled if possible). Deletions are harder to recover than over-cautious prose. -7. **Do not re-invoke the parent skill** (`hygiene-clean`). This workflow is +7. **Do not re-invoke the parent skill** (`clean`). This workflow is the final step; recursion would be incorrect. diff --git a/plugins/os-doc-hygiene/skills/status/SKILL.md b/plugins/os-doc-hygiene/skills/status/SKILL.md new file mode 100644 index 0000000..79a7dd1 --- /dev/null +++ b/plugins/os-doc-hygiene/skills/status/SKILL.md @@ -0,0 +1,39 @@ +--- +name: status +description: Show the doc-hygiene lifecycle state for the current project — last check/clean/reminded timestamps and whether a report exists. Read-only, no model, no scan. Invoked by `/os-doc-hygiene:status`. +--- + +# Status Skill + +Read-only. No model, no scan, no mutation. Reads the three lifecycle +timestamps plus report presence via `state_store.py` and reports them. + +`state_store.py` is a module (no CLI), so call it via `python3 -c`: + +```bash +python3 -c ' +import sys, os +sys.path.insert(0, os.environ["CLAUDE_PLUGIN_ROOT"] + "/scripts") +from state_store import StateStore, resolve_project_root +from pathlib import Path +s = StateStore(resolve_project_root(Path(os.getcwd()))) +def fmt(dt): return dt.isoformat() if dt else "never" +print("last_check: " + fmt(s.get_last_check())) +print("last_clean: " + fmt(s.get_last_clean())) +print("last_reminded: " + fmt(s.get_last_reminded())) +print("report: " + ("present" if s.read_report() else "none")) +' +``` + +Present the output as a short human summary, for example: + +``` +doc-hygiene status for: + + last check 2026-06-20T14:03:11+00:00 + last clean never + last reminded 2026-06-24T09:00:00+00:00 + report present (run /os-doc-hygiene:check to refresh) +``` + +Do not classify or distill anything — this is a timestamp read only. diff --git a/plugins/os-doc-hygiene/skills/sweep/SKILL.md b/plugins/os-doc-hygiene/skills/sweep/SKILL.md new file mode 100644 index 0000000..9fa373b --- /dev/null +++ b/plugins/os-doc-hygiene/skills/sweep/SKILL.md @@ -0,0 +1,26 @@ +--- +name: sweep +description: Run doc-hygiene check then clean in sequence — a convenience wrapper. The confirm gate in `clean` still applies; sweep never auto-approves confirm-tier or generative entries. Invoked by `/os-doc-hygiene:sweep [--scope ] [--category ]`. +--- + +# Sweep Skill + +`sweep` is check-then-clean: a convenience that runs both skills in sequence, +passing the same `--scope` and `--category` through to each. + +## Workflow + +1. **First**, invoke the **`check`** skill (Skill tool, `skill: "check"`), + passing `--scope` and `--category` verbatim. Surface the check summary to + the user. + +2. **Then**, invoke the **`clean`** skill (Skill tool, `skill: "clean"`), + passing the same `--scope` and `--category` verbatim. + +The confirm gate in `clean` applies identically under `sweep` — sweep does +**not** auto-approve any confirm-tier or generative entries (invariant #7). +The user will still be prompted to approve any entries that require it before +any file mutation occurs. + +`sweep` produces at most one cleanup commit (the clean step); the check step +writes only to the gitignored `.dochygiene/` and does not commit. diff --git a/plugins/os-doc-hygiene/tests/test_classifier_golden.py b/plugins/os-doc-hygiene/tests/test_classifier_golden.py index 4789691..19c73d9 100644 --- a/plugins/os-doc-hygiene/tests/test_classifier_golden.py +++ b/plugins/os-doc-hygiene/tests/test_classifier_golden.py @@ -22,7 +22,7 @@ per the META-RULE). This suite asserts only the deterministic / stable parts: - exact_edit.expected_sha256 == a fresh sha256 of the input/ file bytes (proves the golden's hashes track the fixtures). -The LIVE model-classification regression (actually running /hygiene check +The LIVE model-classification regression (actually running /os-doc-hygiene:check against input/ and diffing the produced classification) lives OUTSIDE this suite — see examples/golden/classifier/README.md.