From 107b7f18c3f78ad5c772d6a014d3e3d9d3b21a2e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 16 Aug 2026 00:09:46 +0000 Subject: [PATCH] docs(docs-hygiene): share clean-tree fallback across audit skills (0.14.6) Add plugin-level context/clean-tree-fallback.md as the SSOT for the confirmation-gated bare-invocation shape. Cite it from the audit siblings and extract-ssot; teach compress audit on a clean tree to offer the free corpus audit instead of no-opping. Closes #2734 Co-authored-by: Kyle Sexton --- .../docs-hygiene/.claude-plugin/plugin.json | 2 +- plugins/docs-hygiene/CHANGELOG.md | 18 ++++++ plugins/docs-hygiene/README.md | 4 ++ .../context/clean-tree-fallback.md | 57 +++++++++++++++++++ .../skills/audit-derivability/SKILL.md | 2 + .../skills/audit-encapsulation/SKILL.md | 4 ++ .../docs-hygiene/skills/audit-noise/SKILL.md | 2 + plugins/docs-hygiene/skills/compress/SKILL.md | 10 ++-- .../skills/compress/context/target-types.md | 2 +- .../docs-hygiene/skills/extract-ssot/SKILL.md | 2 + 10 files changed, 97 insertions(+), 6 deletions(-) create mode 100644 plugins/docs-hygiene/context/clean-tree-fallback.md diff --git a/plugins/docs-hygiene/.claude-plugin/plugin.json b/plugins/docs-hygiene/.claude-plugin/plugin.json index eaae742493..b3e9246c76 100644 --- a/plugins/docs-hygiene/.claude-plugin/plugin.json +++ b/plugins/docs-hygiene/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "docs-hygiene", - "version": "0.14.5", + "version": "0.14.6", "description": "Documentation-hygiene toolkit: compress (flavor-trim markdown with a semantic-diff safety net), audit-noise (classify markdown noise), extract-ssot (deduplicate repeated content into a single source of truth), audit-encapsulation (detect citations into skill-private surfaces), rename-references (sweep stale references after renames), and audit-derivability (classify whether a whole document earns its existence \u2014 could a fresh agent re-derive it from the code?).", "author": { "name": "Melodic Software", diff --git a/plugins/docs-hygiene/CHANGELOG.md b/plugins/docs-hygiene/CHANGELOG.md index b78d3c0f45..824d2c86b9 100644 --- a/plugins/docs-hygiene/CHANGELOG.md +++ b/plugins/docs-hygiene/CHANGELOG.md @@ -1,5 +1,23 @@ # Changelog — docs-hygiene plugin +## [0.14.6] + +### Added + +- **Shared clean-tree / no-scope fallback contract** + (`context/clean-tree-fallback.md`): the offer → confirm → prescribed-defaults + → decline-or-silence-no-op skeleton that audit-noise 0.12.0 introduced, now + cited by `audit-noise`, `audit-derivability`, `audit-encapsulation`, + `compress`, and `extract-ssot`, with deliberate divergences recorded + (compress stays mutating/audit-first; encapsulation triggers on "no inherited + scope", not only a clean tree). + +### Changed + +- **compress `audit` on a clean tree** offers the confirmation-gated free + corpus audit (report-only) instead of the friendly no-op, matching the + sibling audit skills (#2734). + ## [0.14.5] ### Fixed diff --git a/plugins/docs-hygiene/README.md b/plugins/docs-hygiene/README.md index 4e68c19acb..48ddef2f74 100644 --- a/plugins/docs-hygiene/README.md +++ b/plugins/docs-hygiene/README.md @@ -39,6 +39,10 @@ upkeep. ## How the skills adapt to your repo +Bare invocations with no target share a confirmation-gated clean-tree / +no-scope fallback (`context/clean-tree-fallback.md`): offer a corpus run with +prescribed defaults, never auto-start, and no-op on decline or silence. + The bundled defaults are repo-agnostic: detectors run against the repository they are invoked in, output destinations default to conventional locations diff --git a/plugins/docs-hygiene/context/clean-tree-fallback.md b/plugins/docs-hygiene/context/clean-tree-fallback.md new file mode 100644 index 0000000000..df621e09f2 --- /dev/null +++ b/plugins/docs-hygiene/context/clean-tree-fallback.md @@ -0,0 +1,57 @@ +# Clean-tree / no-scope fallback — shared contract + +SSOT for how docs-hygiene audit skills behave when invoked with **no target and +no inherited working set**. Each skill cites this file and keeps only its +skill-specific prescribed defaults in its own `SKILL.md`. + +## Shared shape (all audit skills) + +When the invocation is bare — empty arg, clean tree (or no inherited scope), +and nothing in the conversation already naming a corpus — the skill: + +1. **Reports** that no default local target exists (uncommitted `.md` empty, or + no inherited detect scope). +2. **Offers** a confirmation-gated escalation to a repo-wide (or skill-default) + corpus run — never starts it unprompted. +3. Presents **prescribed defaults** (overridable) so a bare "yes" suffices. +4. On **decline or silence**, ends as the friendly no-op (skill-specific exit + message). Unattended / non-interactive sessions surface the offer as blocked + and stop — never launch the repo-wide run on silence. +5. An **explicit opt-in** keyword (`sweep`, `audit` with an explicit corpus + flag, or a user-stated "whole repo") skips the confirmation and runs. + +This is the audit-noise 0.12.0 clean-tree fallback generalized. Skills that +already had a close cousin (derivability's empty-target escalation, +encapsulation's no-scope confirmation, compress's interview fallback) converge +here rather than drifting. + +## Who participates + +| Skill | Trigger | Explicit opt-in that skips confirm | Notes | +|---|---|---|---| +| `audit-noise` | empty arg + clean tree | user-stated whole-repo / confirmed offer | Read-only; report-first; chunked `detect.sh` | +| `audit-derivability` | empty arg + clean tree | `sweep ` | Read-only; confirmed escalation runs as `sweep` | +| `audit-encapsulation` | bare detect with no inherited scope | `sweep` | Domain is already repo-wide; confirm is about intent, not discovery | +| `compress` (default + `audit`) | empty arg + clean tree, interactive | user-stated whole-repo / confirmed offer | Mutating default stays interview-gated after a free audit pass; bare `compress audit` on a clean tree offers the same free audit corpus (report-only) instead of no-opping | +| `extract-ssot` | bare invocation with no scope | path/glob-scoped survey after confirm | Already documented as "Bare invocation — confirm scope first"; cites this shape | +| `rename-references` | *(out of scope)* | — | Always needs an old/new token pair; no clean-tree corpus offer | + +## Deliberate divergences (do not "fix" these away) + +- **compress is mutating.** Its clean-tree path is audit-first then a second + confirmation before any Edit. Read-only siblings stop after the report. +- **encapsulation's trigger is "no inherited scope", not only "clean tree".** + A dirty tree with unrelated edits still needs the confirm when nothing + names the detect surface. +- **Prescribed defaults differ by skill** (fixture exclusions, concurrency, + spot-test caps, report-vs-fix). Those knobs stay in each skill's + `SKILL.md`; this file owns only the offer/confirm/no-op skeleton. +- **Non-interactive contexts** (subagent, headless/CI): no-op / blocked + offer — never auto-escalate. Compress states this explicitly; the others + inherit it from step 4 above. + +## Citation + +Skill bodies point here with a one-line cross-ref under their auto-detect / +no-scope section, e.g. "Shared shape: `../../context/clean-tree-fallback.md` +(plugin root)." Paths are relative to the citing `SKILL.md`. diff --git a/plugins/docs-hygiene/skills/audit-derivability/SKILL.md b/plugins/docs-hygiene/skills/audit-derivability/SKILL.md index f12660d11f..e21e7a12f0 100644 --- a/plugins/docs-hygiene/skills/audit-derivability/SKILL.md +++ b/plugins/docs-hygiene/skills/audit-derivability/SKILL.md @@ -75,6 +75,8 @@ One action per response; actions do not chain implicitly. `sweep` is the only re ## Auto-detect default +Shared clean-tree / no-scope shape: [`../../context/clean-tree-fallback.md`](../../context/clean-tree-fallback.md). + 1. Empty arg AND clean tree → no default target exists. Report that, then OFFER escalation to a repo-wide corpus sweep — never start it unprompted. Confirm with the user first (via `AskUserQuestion` where available, a plain prose question otherwise), presenting the prescribed defaults below pre-filled so a bare "yes" suffices; the interview may adjust any knob. Declining, or no answer, ends as the friendly no-op exit 0 ("No uncommitted .md files. Pass a file/dir target, or `sweep ` for a corpus.") 2. Empty arg AND uncommitted `.md` files → audit those files 3. Single file path → single-document audit diff --git a/plugins/docs-hygiene/skills/audit-encapsulation/SKILL.md b/plugins/docs-hygiene/skills/audit-encapsulation/SKILL.md index b5da979972..9be8a4c28d 100644 --- a/plugins/docs-hygiene/skills/audit-encapsulation/SKILL.md +++ b/plugins/docs-hygiene/skills/audit-encapsulation/SKILL.md @@ -44,6 +44,10 @@ One action per response. ### No inherited scope — confirm before a repo-wide run +Shared clean-tree / no-scope shape: [`../../context/clean-tree-fallback.md`](../../context/clean-tree-fallback.md). +This skill's trigger is "no inherited scope" (not only "clean tree") — a dirty tree with unrelated +edits still needs the confirm when nothing names the detect surface. + The default action's domain is the whole repo, but a bare invocation does not prove the user meant that. When the invocation arrives with NO inherited working set — no diff in flight, no prior audit notes, nothing in the conversation narrowing the scope — ask ONE confirmation before running the diff --git a/plugins/docs-hygiene/skills/audit-noise/SKILL.md b/plugins/docs-hygiene/skills/audit-noise/SKILL.md index cb605595b4..bfacbe7366 100644 --- a/plugins/docs-hygiene/skills/audit-noise/SKILL.md +++ b/plugins/docs-hygiene/skills/audit-noise/SKILL.md @@ -66,6 +66,8 @@ Single action v1; `relocate` and `generalize` actions are deferred until real de ## Auto-detect default +Shared clean-tree / no-scope shape: [`../../context/clean-tree-fallback.md`](../../context/clean-tree-fallback.md). + 1. Empty arg AND clean tree → OFFER the repo-wide audit instead of silently no-opping; run only on the user's confirmation. The offer carries prescribed defaults (overridable): corpus = all tracked `.md` minus `**/evals/fixtures/**` and `CHANGELOG.md`; slice-scoped files (contract and diff --git a/plugins/docs-hygiene/skills/compress/SKILL.md b/plugins/docs-hygiene/skills/compress/SKILL.md index d16c6a5c21..06fc2e40c6 100644 --- a/plugins/docs-hygiene/skills/compress/SKILL.md +++ b/plugins/docs-hygiene/skills/compress/SKILL.md @@ -65,19 +65,21 @@ Flags (apply to both actions): ## Auto-detect default +Shared clean-tree / no-scope shape: [`../../context/clean-tree-fallback.md`](../../context/clean-tree-fallback.md). + 1. Empty arg AND clean tree → interactive session: repo-wide interview fallback (next section); non-interactive context (subagent, headless/CI): friendly no-op exit 0 ("No uncommitted .md files. Pass file/dir target.") 2. Empty arg AND uncommitted `.md` files → batch default action over those files 3. Single file path → single-file default action 4. Directory path → batch default action (filenames sorted lexically for deterministic output) -5. First positional == `audit` → audit action on rest +5. First positional == `audit` → audit action on rest (same clean-tree offer as rule 1 when the rest is empty — report-only corpus audit, no Edit) ## Repo-wide interview fallback (empty arg, clean tree, interactive) -Instead of dead-ending, offer a repo-wide run — confirmation-gated at every step; declining at any step exits with the friendly no-op message. +Instead of dead-ending, offer a repo-wide run — confirmation-gated at every step; declining at any step exits with the friendly no-op message. Bare `/docs-hygiene:compress audit` on a clean tree uses steps 1–2 only (free audit + report; no compression interview). 1. **Offer** (AskUserQuestion): run against all tracked eligible `.md` files? Decline → no-op exit. -2. **Audit first** (free — mechanical scan, no subagents): run the audit action over every tracked eligible `.md`. Present INLINE only aggregate counts per class, a dispatch-cost estimate (2 subagent requests per compressed file), and a top-20 excerpt of COMPRESS rows selected deterministically: expected-yield band descending, then word count descending, then lexical path (band strings tie; the two tie-breaks keep the excerpt stable run-to-run). Write the full per-file table to a file — lexically sorted per the "Summary output deterministic" hard rule — and point at it. Never render every row inline — on a large repo the full table can run to hundreds of KB and truncate the confirmation prompt it feeds. -3. **Interview with prescribed defaults** (AskUserQuestion, recommended option listed first): +2. **Audit first** (free — mechanical scan, no subagents): run the audit action over every tracked eligible `.md`. Present INLINE only aggregate counts per class, a dispatch-cost estimate (2 subagent requests per compressed file), and a top-20 excerpt of COMPRESS rows selected deterministically: expected-yield band descending, then word count descending, then lexical path (band strings tie; the two tie-breaks keep the excerpt stable run-to-run). Write the full per-file table to a file — destination `${CLAUDE_PLUGIN_DATA}/audit/-audit.md` when that dir is writable, otherwise a temp path echoed to the user — lexically sorted per the "Summary output deterministic" hard rule — and point at it. Never render every row inline — on a large repo the full table can run to hundreds of KB and truncate the confirmation prompt it feeds. **Stop here when the invocation was the audit action** (report-only). +3. **Interview with prescribed defaults** (AskUserQuestion, recommended option listed first) — default (mutating) action only: - **Scope** — default: all COMPRESS-classified files, highest expected yield first; alternates: top-N highest-yield subset, include UNCERTAIN, stop after audit (report only). - **Concurrency** — default: 2 concurrent subagents per wave (rate-limit-conservative); alternates: 1 (sequential), 3-5 (`context/fan-out-orchestration.md` default). - **Always-loaded files** — default: excluded (SKIP per the 2-3% empirical baseline); including them requires the same explicit opt-in as `--force`. diff --git a/plugins/docs-hygiene/skills/compress/context/target-types.md b/plugins/docs-hygiene/skills/compress/context/target-types.md index 067227c8c3..14acc16ff5 100644 --- a/plugins/docs-hygiene/skills/compress/context/target-types.md +++ b/plugins/docs-hygiene/skills/compress/context/target-types.md @@ -13,7 +13,7 @@ Per `../SKILL.md` "Auto-detect default", argument resolution at invocation: | `/docs-hygiene:compress ` | single file | default action, single-file | | `/docs-hygiene:compress ` | every `.md` under `` (recursive); filenames sorted lexically for determinism | default action, batch | | `/docs-hygiene:compress audit` (empty rest) AND uncommitted `.md` exist | files from `git status --porcelain` matching `*.md` | audit action over each | -| `/docs-hygiene:compress audit` (empty rest) AND clean tree | (none) | friendly no-op exit 0 | +| `/docs-hygiene:compress audit` (empty rest) AND clean tree | interactive: all tracked eligible `.md` offered via the repo-wide interview fallback steps 1–2 (report-only); non-interactive: (none) | interactive: confirmation-gated free audit corpus; non-interactive: friendly no-op exit 0 | | `/docs-hygiene:compress audit ` | single file | audit action | | `/docs-hygiene:compress audit ` | every `.md` under ``; lexical sort | audit action, batch | diff --git a/plugins/docs-hygiene/skills/extract-ssot/SKILL.md b/plugins/docs-hygiene/skills/extract-ssot/SKILL.md index c2223f94ae..eb7f101388 100644 --- a/plugins/docs-hygiene/skills/extract-ssot/SKILL.md +++ b/plugins/docs-hygiene/skills/extract-ssot/SKILL.md @@ -81,6 +81,8 @@ One action per response; actions don't chain implicitly. ## Bare invocation — confirm scope first +Shared clean-tree / no-scope shape: [`../../context/clean-tree-fallback.md`](../../context/clean-tree-fallback.md). + A bare `/docs-hygiene:extract-ssot` with no working notes to resume, no argument, and no scope implied by the conversation does **not** auto-dispatch the exhaustive survey. Exhaustive `identify` sweeps every