From b5dd54f8414aa6c62e35fcb12f1528f831b7c1fd Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 16 Aug 2026 00:11:23 +0000 Subject: [PATCH 1/3] docs(docs-hygiene): process derivability route-to-sibling batch (0.14.7) Close the 38 audit-noise routes after re-scan (one Sources relocation), triage the 136 extract-ssot routes in-tree, and record false-keep sampling as deferred without opening new issues. Closes #2735 Co-authored-by: Kyle Sexton --- docs/conventions/commit-convention/README.md | 6 +- .../docs-hygiene/.claude-plugin/plugin.json | 2 +- plugins/docs-hygiene/CHANGELOG.md | 11 ++ .../context/derivability-route-followups.md | 122 ++++++++++++++++++ .../skills/audit-derivability/SKILL.md | 2 +- 5 files changed, 140 insertions(+), 3 deletions(-) create mode 100644 plugins/docs-hygiene/context/derivability-route-followups.md diff --git a/docs/conventions/commit-convention/README.md b/docs/conventions/commit-convention/README.md index 677a27450..bf76d67c6 100644 --- a/docs/conventions/commit-convention/README.md +++ b/docs/conventions/commit-convention/README.md @@ -101,7 +101,7 @@ Contract points: fail-closed broken-file contract applies; a key it omits still falls back per key to the markdown H2. **Both V1 reasons for shipping no well-known search are engaged, not overridden by fiat** (#163434 - is the demanding consumer; design: `docs/topics/commit-convention-well-known-path/`, PR #1185). V1 recorded + is the demanding consumer; design recorded under PR #1185 — see Sources). V1 recorded (i) "no consumer demanding it yet" — now void. And (ii) a search list "adds probe order and shadowing questions" and "keeps every path choice in the consuming repo's hands." V2 answers (ii) narrowly: it is a single fixed default path, **not** a search list, so probe order is the bounded @@ -170,3 +170,7 @@ purposes without it. Naming coincidence recorded per the seam rules: the convention file is `.claude/source-control.md` after the concern (delivery workflow), not the plugin. The plugin-name collision is incidental; the file is not renamed. + +## Sources + +- Design topic for the well-known-path decision: [`docs/topics/commit-convention-well-known-path/`](../../topics/commit-convention-well-known-path/), carrying PR #1185. diff --git a/plugins/docs-hygiene/.claude-plugin/plugin.json b/plugins/docs-hygiene/.claude-plugin/plugin.json index b3e9246c7..699eae384 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.6", + "version": "0.14.7", "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 824d2c86b..19da5eca3 100644 --- a/plugins/docs-hygiene/CHANGELOG.md +++ b/plugins/docs-hygiene/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog — docs-hygiene plugin +## [0.14.7] + +### Added + +- **audit-derivability route follow-ups:** in-tree status board + (`context/derivability-route-followups.md`) for the 174 route-to-sibling + annotations from the 2026-08-15 repo-wide sweep — noise routes closed after + re-scan + one Sources relocation; extract-ssot routes triaged + (synced-cluster / functional-scaffold / changelog-parity / pending) without + opening new issues (#2735). + ## [0.14.6] ### Added diff --git a/plugins/docs-hygiene/context/derivability-route-followups.md b/plugins/docs-hygiene/context/derivability-route-followups.md new file mode 100644 index 000000000..b3590233f --- /dev/null +++ b/plugins/docs-hygiene/context/derivability-route-followups.md @@ -0,0 +1,122 @@ +# Derivability route-to-sibling follow-ups (2026-08-15 sweep) + +Durable tracking for the 174 route-to-sibling annotations from the repo-wide +`/docs-hygiene:audit-derivability` sweep (issue #2735 / session ledger +`derivability-ledger.json`, ephemeral). This file is the in-tree status board — +no new GitHub issues are opened from it. + +## Batch record + +| Pass | Date | Scope | Outcome | +|---|---|---|---| +| audit-noise re-scan | 2026-08-16 | all 38 noise-routed paths | 32 files scanner-clean under detect 0.14.5+ exemptions; 6 `CHANGELOG.md` basename-exempt; 1 Tier-2 ghost-ref remediated (commit-convention README → Sources) | +| extract-ssot triage | 2026-08-16 | all 136 ssot-routed paths | dispositions below; exact byte-identical `reference/artifact-protocol.md` cluster already registered in `scripts/cross-plugin-source-registry.txt` (keep-as-synced-copies, not pointer-extract) | +| false-keep sampling | deferred | 1089 `keep-owns-facts` from the original sweep | original session ledger ephemeral; future sweeps sample keeps per the post-#2695 contract — do not invent a one-off sample without the ledger | + +## Route: audit-noise (38) + +Disposition after the 2026-08-16 pass: **closed for scanner follow-up**. Line-level +noise the original sweep saw was largely false-positive under the pre-exemption +scanner; remaining real cite relocated. + +| Path | Status | +|---|---| +| `CLAUDE.md` | clean | +| `docs/conventions/commit-convention/README.md` | remediated — design-topic ghost-ref moved to `## Sources` | +| `docs/conventions/config-cascade/README.md` | clean | +| `docs/conventions/ecosystem-commands/README.md` | clean | +| `docs/conventions/hook-observability/README.md` | clean | +| `docs/conventions/hook-telemetry/README.md` | clean | +| `docs/topics/context-engineering-claude-5/design/skill-inventory.md` | clean | +| `plugins/ai-briefing/skills/generate/references/build-pipeline.md` | clean | +| `plugins/ai-briefing/skills/generate/references/slide-generation.md` | clean | +| `plugins/architecture/CHANGELOG.md` | basename-exempt | +| `plugins/claude-ops/skills/known-issues/context/registry-schema.md` | clean (pointer-converted in #2695 — re-verified present) | +| `plugins/disk-hygiene/CHANGELOG.md` | basename-exempt | +| `plugins/domain-driven-design/README.md` | clean | +| `plugins/dometrain/README.md` | clean | +| `plugins/education/CHANGELOG.md` | basename-exempt | +| `plugins/education/README.md` | clean | +| `plugins/eol-normalizer/CHANGELOG.md` | basename-exempt | +| `plugins/eol-normalizer/README.md` | clean | +| `plugins/evals/CHANGELOG.md` | basename-exempt | +| `plugins/firecrawl/CHANGELOG.md` | basename-exempt | +| `plugins/knowledge/skills/youtube-digest/templates/sources.md` | clean | +| `plugins/knowledge/vendor/repo-analysis/README.md` | clean | +| `plugins/knowledge/vendor/video-digestion/TUNING.md` | clean | +| `plugins/machine-health/skills/audit/references/windows/check-catalog.md` | clean | +| `plugins/machine-health/skills/audit/references/windows/elevation-matrix.md` | clean | +| `plugins/mcp-tools/skills/audit/reference/server-discovery.md` | clean | +| `plugins/planning/reference/topic-docs.md` | clean | +| `plugins/planning/skills/draft-goal-condition/SKILL.md` | clean | +| `plugins/planning/skills/interview/context/session-config.md` | clean | +| `plugins/playbooks/skills/boris/SKILL.md` | clean | +| `plugins/songwriting/context/pat-pattison/research/ai-tools.md` | clean | +| `plugins/testing/README.md` | clean | +| `plugins/toolchain/skills/check/context/bash.md` | clean | +| `plugins/toolchain/skills/check/context/dotnet.md` | clean | +| `plugins/toolchain/skills/check/context/go.md` | clean | +| `plugins/toolchain/skills/check/context/python.md` | clean | +| `plugins/toolchain/skills/check/context/typescript.md` | clean | +| `plugins/typos-format/README.md` | clean | + +## Route: extract-ssot (136) + +Pragmatic triage (not a full Rule-of-Three extract pass). Categories: + +### A — Keep as synced byte-identical cluster (registered) + +Already enforced by `scripts/cross-plugin-source-registry.txt` + +`validate-plugin-contracts.mjs`. Pointer-extraction would break per-plugin +install copies. + +- `plugins/{discovery,implementation,planning,verification}/reference/artifact-protocol.md` + +### B — Functional artifacts / scaffolds (out of scope for dedup-into-prose-SSOT) + +Per post-#2695 rubric: runtime checklists and similar scaffolds may duplicate +*shape* without being extract-ssot candidates into a shared prose SSOT. +Re-open only if two checklists are byte-identical and meant to stay that way +(then register like artifact-protocol). + +- `plugins/**/templates/checklist.md` (planning, interview, session-flow, + debugging, codebase-health, code-tidying, claude-config, source-control, + work-items, …) +- `plugins/machine-health/skills/audit/scripts/{linux,macos}/NOT_IMPLEMENTED.md` + (near-dup scaffolding; OS-specific on purpose) +- `plugins/claude-config/skills/audit/templates/checklist.md` and siblings + +### C — CHANGELOG routes (changelog-parity before any dedup) + +Do not collapse changelogs across concerns. Judge each against the +changelog-parity convention if a future pass revisits them. + +- `docs/conventions/*/CHANGELOG.md` (finding-suppression, hook-telemetry, + liveness-assertion, plugin-data-report-keying, standards) +- `plugins/mutation-testing/CHANGELOG.md` + +### D — Pending extract-ssot candidates (not processed this batch) + +Everything else on the original 136 list remains a **candidate** for a future +`/docs-hygiene:extract-ssot` identify pass (path/glob-scoped, not bare +whole-repo). Highest-leverage next slices when resumed: + +1. Plugin README boilerplate clusters (format plugins, hygiene plugins) — + similarity ~0.5–0.7, needs Rule-of-Three evidence before extract. +2. `plugins/docs-hygiene/skills/rename-references/context/{apply,audit,triage}.md` + — same skill, likely progressive-disclosure not duplication. +3. Songwriting research/template prompt cluster — large; defer to a dedicated + extract-ssot wave. +4. Autonomy setup templates — likely intentional variants. + +Full original path list: GitHub issue #2735 (durable copy of the ephemeral +ledger). This file owns **status**, not a second full roster, so the two stay +aligned via the issue link rather than a duplicated 136-row table. + +## False-keep sampling backlog + +The completed sweep's 1089 `keep-owns-facts` verdicts were never sampled. The +contract now requires sampling keeps on future sweeps. A one-off 20-keep +fresh-context probe is blocked here because the session ledger is gone; do not +fabricate sample membership. Next full `audit-derivability` sweep must sample +keeps and record the sample set beside its ledger. diff --git a/plugins/docs-hygiene/skills/audit-derivability/SKILL.md b/plugins/docs-hygiene/skills/audit-derivability/SKILL.md index e21e7a12f..cd209a2cf 100644 --- a/plugins/docs-hygiene/skills/audit-derivability/SKILL.md +++ b/plugins/docs-hygiene/skills/audit-derivability/SKILL.md @@ -119,7 +119,7 @@ Batch / sweep aggregate at the end: Audited document(s): delete,

convert-to-pointer, keep-as-cache, keep-owns-facts, out-of-scope functional artifacts; of the verdicts also carry a route-to-sibling annotation. ``` -Route-to-sibling is an ANNOTATION on a verdict, never a fifth verdict class: a document whose verdict stands (usually a keep) but whose rationale routes material to a sibling — doc-to-doc duplication to `/docs-hygiene:extract-ssot`, line-level noise to `/docs-hygiene:audit-noise` — records that route in its rationale, and `` counts the documents carrying one, so routed work is visible in the aggregate instead of vanishing into the keep bucket. +Route-to-sibling is an ANNOTATION on a verdict, never a fifth verdict class: a document whose verdict stands (usually a keep) but whose rationale routes material to a sibling — doc-to-doc duplication to `/docs-hygiene:extract-ssot`, line-level noise to `/docs-hygiene:audit-noise` — records that route in its rationale, and `` counts the documents carrying one, so routed work is visible in the aggregate instead of vanishing into the keep bucket. In-tree follow-up status for the 2026-08-15 repo-wide routed set: [`../../context/derivability-route-followups.md`](../../context/derivability-route-followups.md). Corpus-scale sweeps (more documents than one reply can carry): the per-document blocks live in the batch ledger files; the reply carries the aggregate line, the confirmed-actionable (`delete` / `convert-to-pointer`) subset, the provisional (cap-deferred) verdicts reported separately for visibility — never as part of the actionable subset — and the ledger file locations. From 01726f94129c6fb59b5da7df66a10be2f54f6ef5 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 16 Aug 2026 00:14:02 +0000 Subject: [PATCH 2/3] fix(docs-hygiene): compress caveman/taxonomy audit remediations (0.15.0) Rewrite caveman Step B for cross-tool Bash semantics, honor enabled+marketplace identity in detect-caveman, ship audit-scan.sh, add yield circuit breaker and top-10 interview default, and align taxonomy/LATITUDE/docs with the word-level batch path vs Edit-fallback split. Closes #2745 Co-authored-by: Kyle Sexton --- .../docs-hygiene/.claude-plugin/plugin.json | 2 +- plugins/docs-hygiene/CHANGELOG.md | 17 +++ plugins/docs-hygiene/skills/compress/SKILL.md | 46 ++++--- .../compress/context/fan-out-orchestration.md | 4 +- .../context/flavor-vs-content-matrix.md | 10 +- .../skills/compress/context/integration.md | 2 +- .../skills/compress/context/target-types.md | 9 +- .../skills/compress/evals/evals.json | 44 +++--- .../compress/evals/fixtures/terse-agent.md | 9 ++ .../skills/compress/scripts/audit-scan.sh | 129 ++++++++++++++++++ .../compress/scripts/audit-scan.test.sh | 36 +++++ .../skills/compress/scripts/detect-caveman.sh | 17 ++- .../compress/scripts/detect-caveman.test.sh | 41 ++++++ 13 files changed, 313 insertions(+), 53 deletions(-) create mode 100644 plugins/docs-hygiene/skills/compress/evals/fixtures/terse-agent.md create mode 100755 plugins/docs-hygiene/skills/compress/scripts/audit-scan.sh create mode 100755 plugins/docs-hygiene/skills/compress/scripts/audit-scan.test.sh diff --git a/plugins/docs-hygiene/.claude-plugin/plugin.json b/plugins/docs-hygiene/.claude-plugin/plugin.json index 699eae384..8d251778c 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.7", + "version": "0.15.0", "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 19da5eca3..8bd3b4bd6 100644 --- a/plugins/docs-hygiene/CHANGELOG.md +++ b/plugins/docs-hygiene/CHANGELOG.md @@ -1,5 +1,22 @@ # Changelog — docs-hygiene plugin +## [0.15.0] + +### Fixed + +- **compress (plugin-quality audit #2745):** rewrite caveman Step B as + cross-tool-call steps (no EXIT trap / non-persistent `$tempdir`); map + detector `unknown` → Edit fallback; require `enabled: true` (prefer + `caveman@caveman`) in `detect-caveman.sh`; fix pre-computed `|| echo none` + pipeline; name audit-table destination under `${CLAUDE_PLUGIN_DATA}/audit/`; + reword signal 6 as an owned curated token list; annotate taxonomy/LATITUDE + drift (batch = word-level; Edit fallback = full matrix); note drifted-skill + matrix niche is unreachable via signal 1; add yield circuit breaker + top-10 + interview default; ship `scripts/audit-scan.sh` + contract tests; point eval 8 + at `evals/fixtures/terse-agent.md`; widen fixture-gate conventions; soft-block + wording in `integration.md`; record deliberate `disable-model-invocation: + false`. + ## [0.14.7] ### Added diff --git a/plugins/docs-hygiene/skills/compress/SKILL.md b/plugins/docs-hygiene/skills/compress/SKILL.md index 06fc2e40c..78ca4ccad 100644 --- a/plugins/docs-hygiene/skills/compress/SKILL.md +++ b/plugins/docs-hygiene/skills/compress/SKILL.md @@ -12,13 +12,11 @@ metadata: ## Pre-computed context Current branch: !`git branch --show-current 2>/dev/null || echo "unknown"` -Uncommitted .md files: !`git status --porcelain 2>/dev/null | grep '\.md$' | head -10 || echo "none"` +Uncommitted .md files: !`{ git status --porcelain 2>/dev/null | grep '\.md$' || echo "none"; } | head -10` ## Purpose -Markdown in `docs/`, README files, onboarding docs, third-party pasted prose, and drifted skill bodies accumulates FLAVOR — filler ("just", "really", "basically"), hedging ("perhaps", "might"), articles, pleasantries, redundant restatement. `context/flavor-vs-content-matrix.md` defines FLAVOR (safe to cut) vs CONTENT (never cut); this skill applies that taxonomy AT EDIT TIME to content where author-time discipline does NOT apply. - -Always-loaded instruction files (`.claude/rules/**`, `AGENTS.md`, `CLAUDE.md`, `**/SKILL.md`) bound empirically at 2-3% yield (see ## Sources). Likely 5-15% yield on author-time-undisciplined content. +Markdown in `docs/`, README files, onboarding docs, third-party pasted prose, and drifted skill bodies accumulates FLAVOR — filler ("just", "really", "basically"), hedging ("perhaps", "might"), articles, pleasantries. `context/flavor-vs-content-matrix.md` defines FLAVOR (safe to cut) vs CONTENT (never cut). The **batch fan-out path** (Phase A LATITUDE) is a word-level trimmer: mechanical drops + passive→active + nominalization only — no sentence-level restatement deletion. The **single-file in-session Edit fallback** may apply the full matrix taxonomy (including redundant restatement of bold rule names) behind the same semantic-diff net. Always-loaded instruction files (`.claude/rules/**`, `AGENTS.md`, `CLAUDE.md`, `**/SKILL.md`) bound empirically at 2-3% yield (see ## Sources). Likely 5-15% yield on author-time-undisciplined content when the Edit fallback's broader latitude applies; batch fan-out yields are correspondingly smaller. Methodology: snapshot original → backend mechanical compression (the `caveman` plugin via `/caveman:compress`, OR in-session Edit fallback) → spawn semantic-diff subagent comparing original vs condensed (output: SEMANTIC LOSS / AMBIGUITY / FALSE POSITIVE per finding with verbatim citations) → revert every SEMANTIC LOSS + AMBIGUITY → run `markdownlint-cli2` → ship or revert. @@ -26,28 +24,34 @@ Methodology: snapshot original → backend mechanical compression (the `caveman` Default-action Step B picks the mechanical-compression backend: the `caveman` plugin (marketplace `caveman`, invoked as `/caveman:compress`) when present, otherwise the in-session Edit-based fallback. Caveman performs the mechanical flavor cuts (articles, fillers, hedging, verbose-verb collapses) as the compression backend — it is NOT the verification gate. Fallback policy is graceful: the in-session Edit-based path substitutes whenever caveman is absent or unwanted. Subsequent steps (semantic-diff dispatch, revert pass, markdownlint) wrap the output regardless of backend choice. + `disable-model-invocation: false` is deliberate: compress is model-invocable with interview confirmation gates and permission-governed Edit/Bash; not an oversight relative to D1 guidance that mutating skills often set the flag true. + Note the distinction inside that plugin: `/caveman:compress` is a function-call skill (this skill's backend); `/caveman:caveman` is a session-wide response formatter — unrelated to this skill. **Step A — detect caveman plugin:** `bash "${CLAUDE_SKILL_DIR}/scripts/detect-caveman.sh"` +Tri-state: `available` → prefer caveman; `absent` OR `unknown` → treat as absent and use the Edit fallback (`unknown` means `claude`/`jq` missing from PATH — fail open to Edit, not a hard error). -**Step B — caveman backend (preferred):** - -```bash -tempdir=$(mktemp -d) -trap 'rm -rf "$tempdir"' EXIT -cp "$target" "$tempdir/$(basename "$target")" -# Invoke caveman via Skill tool on tempdir copy: -# Skill(caveman:compress, args="$tempdir/$(basename "$target")") -# Caveman writes compressed output to tempdir/basename and backup to tempdir/.original.md. -# Both stay inside tempdir; trap cleans on EXIT. -cp "$tempdir/$(basename "$target")" "$target" # only on caveman success -``` +**Step B — caveman backend (preferred when available):** cross-tool-call steps (Bash state does not persist across tool calls — no `trap … EXIT`, no relying on `$tempdir` in a later call): + +1. **Bash call 1** — create a temp copy and echo its absolute path (no EXIT trap): + ```bash + tempdir=$(mktemp -d) + cp "$target" "$tempdir/$(basename "$target")" + printf '%s\n' "$tempdir/$(basename "$target")" + ``` +2. **Skill call** — `Skill(caveman:compress, args="")` on that temp copy. Caveman may write `.original.md` beside the copy inside the tempdir. +3. **Bash call 2** — on caveman success, copy the compressed file back and remove the tempdir explicitly: + ```bash + cp "" "$target" + rm -rf "$(dirname "")" + ``` + On caveman failure, skip the `cp` and still `rm -rf` the tempdir so the real target is untouched. -Tempdir wrapper contains caveman's hardcoded `.original.md` backup write. Real-path file replaced atomically on success. Consumers may add a defensive `**/*.original.md` entry to their `.gitignore` as belt-and-suspenders against tempdir cleanup races or future caveman backup-path-convention changes. +Tempdir wrapper contains caveman's hardcoded `.original.md` backup write. Real-path file replaced only on success. Consumers may add a defensive `**/*.original.md` entry to their `.gitignore` as belt-and-suspenders against cleanup races or future caveman backup-path-convention changes. -**Step B fallback — in-session Edit (caveman absent or disabled):** +**Step B fallback — in-session Edit (caveman absent, unknown, or unwanted):** -Agent applies Edit ops directly on `$target` per the `context/flavor-vs-content-matrix.md` taxonomy. Same flavor-vs-content rules; no backend indirection. +Agent applies Edit ops directly on `$target` per the `context/flavor-vs-content-matrix.md` taxonomy (full matrix, including restatement deletion). Same flavor-vs-content rules; no backend indirection. **Step C+ unchanged:** semantic-diff dispatch (mandatory hard rule), revert pass for SEMANTIC LOSS / AMBIGUITY / UNCERTAIN findings, markdownlint-cli2, summary. @@ -56,7 +60,7 @@ Agent applies Edit ops directly on `$target` per the `context/flavor-vs-content- | Action | Args | Behavior | |---|---|---| | `` (default, no action keyword) | empty → uncommitted `.md` from `git status`; file path → single-file; dir path → batch | snapshot → backend → dispatch → revert-pass → markdownlint verify → summary | -| `audit [target]` | same target rules | read-only dry-run; compute expected-yield heuristic per `context/target-types.md`; classify SKIP/COMPRESS/UNCERTAIN | +| `audit [target]` | same target rules | read-only dry-run; run `scripts/audit-scan.sh` (six-signal heuristic in `context/target-types.md`); classify SKIP/COMPRESS/UNCERTAIN | Flags (apply to both actions): @@ -80,7 +84,7 @@ Instead of dead-ending, offer a repo-wide run — confirmation-gated at every st 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 — 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). + - **Scope** — default: **top-10** COMPRESS-classified files, highest expected yield first (report-only / decline remains available); alternates: top-N (user picks N), all COMPRESS, include UNCERTAIN, stop after audit (report only). Downgraded from "all COMPRESS" after the 2026-08-15 calibration run (87 consecutive auto-reverts) — see `context/fan-out-orchestration.md` circuit breaker. - **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`. 4. **Confirm and run**: batch default action over the confirmed set, waves per `context/fan-out-orchestration.md`. Every per-file hard rule — semantic-diff dispatch, revert pass, markdownlint, `<3% AND 0 semantic-loss → REVERT` — applies unchanged. diff --git a/plugins/docs-hygiene/skills/compress/context/fan-out-orchestration.md b/plugins/docs-hygiene/skills/compress/context/fan-out-orchestration.md index 386d95293..ad77ee73c 100644 --- a/plugins/docs-hygiene/skills/compress/context/fan-out-orchestration.md +++ b/plugins/docs-hygiene/skills/compress/context/fan-out-orchestration.md @@ -1,6 +1,6 @@ # Compress fan-out orchestration -Read this when batch-compressing N markdown files via parallel subagents. Codifies the multi-phase split that keeps the mandatory semantic-diff in a SEPARATE fresh-context auditor. Before Claude Code v2.1.172 a subagent could not spawn the verifier at all (no nested Agent tool); as of v2.1.172 a foreground subagent can, but nested spawning is version-dependent and a fresh-context verifier beats self-critique regardless — so the auditor phase stays a main-session dispatch. +Read this when batch-compressing N markdown files via parallel subagents. Codifies the multi-phase split that keeps the mandatory semantic-diff in a SEPARATE fresh-context auditor. Nested subagent spawning has been version- and settings-dependent since v2.1.172 (defaults have moved across releases); a fresh-context verifier beats self-critique regardless — so the auditor phase stays a main-session dispatch. **Why this exists:** `/docs-hygiene:compress` "Hard rules" mandate semantic-diff dispatch. A subagent that invokes `/docs-hygiene:compress` must NOT run that dispatch as a self-audit in its own context — self-audit by the same model that produced the edits drifts toward EXPANSION ("preserve clarity" re-adds words just removed; an observed failure — see ## History). Fix: move the semantic-diff into a separate fresh-context subagent dispatched by the main session. @@ -18,6 +18,7 @@ Compress exactly ONE file: LATITUDE: - Mechanical drops: articles (the/a/an) before clear nouns, filler (just/really/basically/actually/simply), hedging (perhaps/somewhat/might in factually-direct statements), pleasantries, verbose verb phrases (in order to → to, due to the fact that → because, make use of → use) - Prose playbook: passive → active voice, nominalization collapse ("performs analysis of" → "analyzes", "is responsible for" → "owns") +- Batch fan-out does NOT delete sentence-level restatements (that latitude is Edit-fallback / single-file only — see SKILL.md Purpose). HARD RULES: - NEVER add words. EVER. @@ -67,6 +68,7 @@ Per FINDING block returned in Phase B: - **Phase A scope fence** — each compressor subagent's prompt names exactly ONE allowed file; any other file, git operation, or path is forbidden (the template above encodes this) - **Phase A does NOT invoke `/docs-hygiene:compress`** as a slash command from subagents — self-audit in the compressor context caused reverse-direction edits (see ## History) - **Refuse-fast threshold** — 5 consecutive Phase A or Phase B ERROR returns aborts the batch +- **Yield circuit breaker** — 5 consecutive auto-reverts in a wave (sub-3% / 0-SL successful outcomes that still discard the edit) → pause, report observed yield, and re-confirm with the user before the next wave. Reverts are not ERRORs; without this breaker a misclassified COMPRESS cohort burns two Opus dispatches per file to completion (2026-08-15 calibration: 87 consecutive auto-reverts). - **Phase B returns are unverified synthesis** — the main session reverts per finding rather than verifying each by hand; a forbidden citation token invalidates the whole dispatch ## History diff --git a/plugins/docs-hygiene/skills/compress/context/flavor-vs-content-matrix.md b/plugins/docs-hygiene/skills/compress/context/flavor-vs-content-matrix.md index d01a6fa88..4bcc2120c 100644 --- a/plugins/docs-hygiene/skills/compress/context/flavor-vs-content-matrix.md +++ b/plugins/docs-hygiene/skills/compress/context/flavor-vs-content-matrix.md @@ -10,9 +10,9 @@ Canonical FLAVOR / CONTENT taxonomy for the `/docs-hygiene:compress` semantic-di - Filler (just/really/basically/actually/simply) - Hedging (perhaps/somewhat/might) - Pleasantries -- Redundant restatement of bold rule names +- Redundant restatement of bold rule names (single-file Edit fallback only — batch Phase A LATITUDE does not delete sentences) - "in order to" / "due to the fact that" verbose forms -- Conversational connectives ("that said", "in other words") +- Conversational connectives ("that said", "in other words") (Edit fallback; batch LATITUDE is word-level) - Verbose verb phrases ("make use of" → "use") ### Content (NEVER cut) @@ -37,7 +37,7 @@ The taxonomy is invariant across content types. What varies is the EXPECTED YIEL | **Always-loaded instruction file** (`.claude/rules/**`, `AGENTS.md`, `CLAUDE.md`, `**/SKILL.md`) | 2-3% | (a) directives, (d) scope qualifiers, (e) rule-unique rationale, (f) cross-references | Author-time-disciplined. Default action will revert per SKILL.md "Hard rules" (<3% AND 0SL → REVERT). `--force` only when a targeted sub-3% diff is intentional. Empirical baseline: 3/3 attempts reverted | | **Onboarding doc** (README onboarding, `docs/onboarding-*.md`, contributor guides) | 8-15% | (b) prohibited-pattern tokens, (c) counter-examples, (h) thresholds | Verbose-prose baseline. Hedging + pleasantries dense; restatement of policy across sections common. Revert-pass strictness: keep every "X not Y" pair intact (counter-example loss = ambiguity in onboarding) | | **README** (`README.md`, `*/README.md` at app/lib/service roots) | 5-12% | (f) cross-references, (g) exception clauses, (j) inline-code tokens | Project-front-door surface. Inline-code density usually high (commands, paths); revert any (j) drop. Cross-references load-bearing for navigation | -| **Drifted skill body** (`**/SKILL.md` past ~250 lines AND not author-time-disciplined) | 4-7% | (a) directives, (e) rule-unique rationale, (i) enumeration items | Skill bodies tend to accumulate procedural prose during evolution. Revert any directive softening ("must" → "should"); revert any enumeration-item drop. Often a single revert-pass produces a final ship | +| **Drifted skill body** (`**/SKILL.md` past ~250 lines AND not author-time-disciplined) | 4-7% (Edit-fallback / explicit target only) | (a) directives, (e) rule-unique rationale, (i) enumeration items | **Unreachable via the audit gate's batch path:** signal 1 unconditionally SKIPs every `**/SKILL.md`. Drifted skill bodies require an explicitly-named single-file target (or Edit fallback); the matrix row remains for that niche. Revert any directive softening ("must" → "should"); revert any enumeration-item drop | | **Third-party pasted prose** (vendor docs, external policy text, copied research notes) | 10-20% | (b) prohibited-pattern tokens, (h) thresholds, (j) inline-code tokens | Highest yield + highest risk. Pasted prose carries verbose flavor authors did not edit. Inline-code tokens (CLI flags, schema field names) MUST survive verbatim; treat any (j) loss as SEMANTIC LOSS not AMBIGUITY | ## Variants never relax the preservation contract @@ -49,8 +49,8 @@ The (a)–(j) Content list defines the universal preservation contract. Per-cont `/docs-hygiene:compress audit ` classifies SKIP / COMPRESS / UNCERTAIN per `context/target-types.md` "Author-time-signal heuristic". The "Expected yield" column above feeds that heuristic's output: - Expected yield < 3% (always-loaded instruction files) → audit emits **SKIP** with empirical-baseline citation -- Expected yield 3-7% (drifted skill bodies) → audit emits **UNCERTAIN**; user gates via `--force` or skip -- Expected yield ≥ 8% (onboarding / README / third-party) → audit emits **COMPRESS** +- Expected yield 3-7% (density-narrow files, or an explicitly-targeted drifted skill body) → audit emits **UNCERTAIN**; user gates via `--force` or skip. Note: `**/SKILL.md` never reaches this band through the mechanical audit gate (signal 1 wins). +- Expected yield ≥ 8% (onboarding / README / third-party) → audit emits **COMPRESS**; the matrix's 8-15% / 10-20% bands assume Edit-fallback latitude on restatement-heavy prose and over-predict batch fan-out yield Numeric ranges drift; revisit the variant table as empirical evidence accumulates. diff --git a/plugins/docs-hygiene/skills/compress/context/integration.md b/plugins/docs-hygiene/skills/compress/context/integration.md index 04f4a1f2f..09850d52d 100644 --- a/plugins/docs-hygiene/skills/compress/context/integration.md +++ b/plugins/docs-hygiene/skills/compress/context/integration.md @@ -8,7 +8,7 @@ How `/docs-hygiene:compress` composes with sibling skills in this plugin and wit |---|---|---| | The consuming repo's markdown lint | `/docs-hygiene:compress` runs it | Post-edit verification. SKILL.md "Hard rules" requires `markdownlint-cli2` PASS on every ship, using the consuming repository's markdownlint config when present. If the consumer has a broader lint workflow, it may run after a `/docs-hygiene:compress` batch to surface the full report. Failure blocks ship per the `/docs-hygiene:compress` revert rule | | A planning workflow (if the consumer has one) | calls `/docs-hygiene:compress` | Plan authoring. When a planning artifact grows ≥ 2000 words (or an exploration/research artifact beyond 1500 words), the author may invoke `/docs-hygiene:compress ` on the artifact before handing it off. Composition is plan-level; the consumer's workflow decides when | -| An instruction-audit workflow (if the consumer has one) | calls `/docs-hygiene:compress` | Always-loaded surface audit. When such an audit flags `CLAUDE.md` or rule-file size bloat AND empirical yield > 3%, the user may invoke `/docs-hygiene:compress --force ` to take the targeted sub-3% diff. `--force` is mandatory on always-loaded instruction paths — audit will SKIP-recommend without it | +| An instruction-audit workflow (if the consumer has one) | calls `/docs-hygiene:compress` | Always-loaded surface audit. When such an audit flags `CLAUDE.md` or rule-file size bloat AND empirical yield > 3%, the user may invoke `/docs-hygiene:compress --force ` to keep a targeted sub-3% diff. `--force` is required only to **keep** a sub-3% result — the run itself proceeds and auto-reverts without it (SOFT-BLOCK per SKILL.md; not a structural refuse) | | `/docs-hygiene:audit-encapsulation` | parallel concern | No invocation either direction. `/docs-hygiene:audit-encapsulation` detects external citations into skill-private surfaces; `/docs-hygiene:compress` edits the markdown targets it is given. The two skills do not interact at runtime | | A pre-PR quality gate (if the consumer has one) | calls `/docs-hygiene:compress` | When a pre-PR check surfaces uncommitted `.md` files in the working tree, the user may invoke `/docs-hygiene:compress` (empty arg auto-detects) before PR prep. `/docs-hygiene:compress` does not auto-trigger from any gate; user-gated | diff --git a/plugins/docs-hygiene/skills/compress/context/target-types.md b/plugins/docs-hygiene/skills/compress/context/target-types.md index 14acc16ff..9a0d5c19d 100644 --- a/plugins/docs-hygiene/skills/compress/context/target-types.md +++ b/plugins/docs-hygiene/skills/compress/context/target-types.md @@ -27,13 +27,13 @@ Per-target gates before any dispatch: 2. Path ends in `.md` (case-insensitive) → otherwise skip with `reason=non-markdown` 3. Path NOT a symlink escaping repo root → otherwise skip with `reason=symlink-escape` 4. Path NOT inside `.git/` → otherwise skip with `reason=git-internal` -5. Default (mutating) action with an ENUMERATED target set only — any target set the user did not name file-by-file: the empty-arg uncommitted-`.md` batch (argument-shape row 1, enumerated from `git status`), directory expansion, or the repo-wide interview sweep: path NOT under an `evals/fixtures/` directory → otherwise skip with `reason=fixture` (fixture verbosity is deliberate test input — compressing it corrupts the eval, and the two most-verbose files in the authoring repo's 2026-08-15 run were this skill's own verbose fixtures). An explicitly-named single-file target bypasses this gate — naming a fixture is an intentional act, same philosophy as `--force`; the audit action is read-only and never applies it. +5. Default (mutating) action with an ENUMERATED target set only — any target set the user did not name file-by-file: the empty-arg uncommitted-`.md` batch (argument-shape row 1, enumerated from `git status`), directory expansion, or the repo-wide interview sweep: path NOT under a fixture convention directory (`evals/fixtures/`, and also `testdata/`, `__fixtures__/`, `test/fixtures/` when those appear — the skill's own layout uses `evals/fixtures/`; other conventions are acknowledged so consumers are not surprised, match case-insensitive on path segments) → otherwise skip with `reason=fixture` (fixture verbosity is deliberate test input — compressing it corrupts the eval, and the two most-verbose files in the authoring repo's 2026-08-15 run were this skill's own verbose fixtures). An explicitly-named single-file target bypasses this gate — naming a fixture is an intentional act, same philosophy as `--force`; the audit action is read-only and never applies it. Binary files and non-markdown files are out of scope per `../SKILL.md` "When NOT to use". ## Author-time-signal heuristic (audit action only) -Audit is a pure mechanical scan — no subagent dispatch, no edits. Per target, compute an expected-yield estimate from six signals; emit SKIP / COMPRESS / UNCERTAIN per the classification table below. +Audit is a pure mechanical scan — no subagent dispatch, no edits. Per target, compute an expected-yield estimate from six signals via `scripts/audit-scan.sh` (preferred; deterministic) or the table below; emit SKIP / COMPRESS / UNCERTAIN per the classification table. ### Six signals @@ -44,7 +44,7 @@ Audit is a pure mechanical scan — no subagent dispatch, no edits. Per target, | 3 | Cross-reference density | regex count per kilo-word of `@`-paths, `.md` cites, file-system path tokens (`[a-z][a-z0-9._/-]+\.(md\|cs\|sh\|json\|yaml)`); density > 8 = high | high density → load-bearing references → lower expected yield | | 4 | Explicit compression-discipline cite | `grep -F` for the fixed string `Prose compression discipline` — a file citing the consuming repo's author-time compression-discipline convention marks itself as already disciplined | match → author-time-disciplined → expected ≤ 3% | | 5 | Default fallback (no other signal fires) | none of 1-4 match AND signal 6 does not fire | verbose-prose baseline → expected 5-15% | -| 6 | Flavor-token density (gates signal 5; computed when no signal 1-4 fires) | `grep -oiwE` count per kilo-word of the compress template's LATITUDE flavor tokens (just/really/basically/actually/simply/perhaps/somewhat/very/quite, "in order to", "due to the fact that", "make use of", "it is important to", "note that", "keep in mind"); density < 5 = already disciplined | force expected ≤ 3%; a repo authored under standing prose discipline is lean without citing any convention (empirical: 2026-08-15 authoring-repo run, 9/9 signal-5-classified files at ≤7/kw yielded 0.02-0.4% and all reverted, while this skill's deliberately-verbose fixtures measured 50-60/kw) | +| 6 | Flavor-token density (gates signal 5; computed when no signal 1-4 fires) | `grep -oiwE` count per kilo-word of a **curated flavor-token list** owned by `scripts/audit-scan.sh` (superset/subset of Phase A LATITUDE — deliberately not identical: adds very/quite/"it is important to"/"note that"/"keep in mind"; keeps might; omits bare articles). Density < 5 = already disciplined | force expected ≤ 3%; a repo authored under standing prose discipline is lean without citing any convention (empirical: 2026-08-15 authoring-repo run, 9/9 signal-5-classified files at ≤7/kw yielded 0.02-0.4% and all reverted, while this skill's deliberately-verbose fixtures measured 50-60/kw) | ### Classification table @@ -52,7 +52,8 @@ Audit is a pure mechanical scan — no subagent dispatch, no edits. Per target, |---|---|---| | ≤ 3% (signals 1 OR 4 fire) | `SKIP` | "author-time-disciplined; empirical baseline 3/3 reverted; use `--force` only for targeted sub-3% diff" | | ≤ 3% (signal 6 fires) | `SKIP` | "flavor-token density N/kw < 5; disciplined-by-authorship; empirical baseline 9/9 reverted at 0.02-0.4%" — N inlined | -| 3-7% (signals 2 OR 3 fire, no signal 1/4) | `UNCERTAIN` | "inline-code density H AND/OR cross-ref density H; flavor band narrow" — H values inlined | +| 3-7% (signals 2 OR 3 fire, no signal 1/4/6) | `UNCERTAIN` | "inline-code density H AND/OR cross-ref density H; flavor band narrow" — H values inlined | +| 5-7% band under signal 5 alone | `COMPRESS` | "verbose-prose baseline (lower band); expected flavor cuts on filler/hedging/articles" — signal 5's 5-15% effect maps here and to ≥8% | | ≥ 8% (signal 5 fallback) | `COMPRESS` | "verbose-prose baseline; expected flavor cuts on filler/hedging/articles" | ### Output table (audit action) diff --git a/plugins/docs-hygiene/skills/compress/evals/evals.json b/plugins/docs-hygiene/skills/compress/evals/evals.json index 8dbf30cf5..f6427c3d2 100644 --- a/plugins/docs-hygiene/skills/compress/evals/evals.json +++ b/plugins/docs-hygiene/skills/compress/evals/evals.json @@ -6,7 +6,9 @@ "name": "default-compress-preserves-content", "prompt": "Compress this doc: read evals/fixtures/verbose-onboarding-snippet.md relative to the skill directory and tighten it. Drop the flavor but keep everything load-bearing.", "expected_output": "Runs the default action: snapshot, mechanical compression backend (caveman or in-session Edit), a separate semantic-diff pass, revert of any semantic loss, then markdownlint. Filler ('basically', 'really', 'just', 'honestly', 'perhaps') is cut; the zero-warnings directive, the `MAX_UPLOAD_MB=25` value, and the 25 MB rejection threshold are preserved verbatim.", - "files": ["evals/fixtures/verbose-onboarding-snippet.md"], + "files": [ + "evals/fixtures/verbose-onboarding-snippet.md" + ], "expectations": [ "Output removes filler/hedging words (e.g. 'basically', 'really', 'just', 'honestly', 'perhaps')", "The literal `MAX_UPLOAD_MB=25` and the 25 MB rejection threshold are preserved, not dropped or altered", @@ -17,7 +19,7 @@ { "id": 2, "name": "audit-classification-table", - "prompt": "Run /compress audit against evals/fixtures/audit-fixture-dir (relative to the skill directory) — a directory of three files: an author-time-disciplined reference doc, a verbose prose doc, and a mixed doc.", + "prompt": "Run /compress audit against evals/fixtures/audit-fixture-dir (relative to the skill directory) \u2014 a directory of three files: an author-time-disciplined reference doc, a verbose prose doc, and a mixed doc.", "expected_output": "Runs the audit action only: a read-only dry-run using the author-time-signal heuristic in context/target-types.md. No file is edited and no semantic-diff subagent is dispatched. Output is a markdown table with columns target/expected_yield_pct/classify/reason, one row per file, sorted lexically. `lean.md` classifies SKIP (signal 4: explicit compression-discipline citation fires). `verbose.md` classifies COMPRESS (signal 5 fallback: verbose-prose baseline, no code-token or cross-reference density). `mixed.md` classifies UNCERTAIN or COMPRESS (signals 2/3 borderline on a small mixed-density file).", "files": [ "evals/fixtures/audit-fixture-dir/lean.md", @@ -37,20 +39,21 @@ }, { "id": 3, - "name": "semantic-diff-is-separate-not-self-audit", - "prompt": "Compress the prose in docs/architecture/overview.md. It's long and wordy.", + "name": "semantic-diff-is-separate-not-self-audit-narration", + "prompt": "Narration eval: describe how /docs-hygiene:compress verifies a compression of a long wordy markdown file (do not require a real path to exist).", "expected_output": "After producing the condensed edits, a SEPARATE fresh-context semantic-diff pass compares original vs condensed and reverts every SEMANTIC LOSS and AMBIGUITY finding. The model that produced the edits does not grade its own output, and the verifier does not re-add words to 'preserve clarity'.", "files": [], "expectations": [ "The semantic-diff verification is described as a separate fresh-context pass, not a self-review by the editing model", "SEMANTIC LOSS and AMBIGUITY findings are reverted, not merely noted", - "The verifier is not used to re-expand or re-add removed words" + "The verifier is not used to re-expand or re-add removed words", + "This eval is narration-only \u2014 it grades the described procedure, not a fixture run" ] }, { "id": 4, "name": "sub-3-percent-reverts-without-force", - "prompt": "Compress CLAUDE.md — it's already pretty tight but see if there's anything to cut.", + "prompt": "Compress an always-loaded instruction file such as CLAUDE.md at the repo root (if present) \u2014 already pretty tight; see if there is anything to cut.", "expected_output": "An already-disciplined always-loaded instruction file yields under 3% with zero semantic loss, so the default `<3% AND 0 semantic-loss -> REVERT` rule trips and the change is reverted. The run notes that `--force` would be required to keep a sub-3% diff.", "files": [], "expectations": [ @@ -62,7 +65,7 @@ { "id": 5, "name": "refuse-code-file-target", - "prompt": "Compress apps/billing/src/fees.ts — trim the verbose comments and dead code.", + "prompt": "Compress apps/billing/src/fees.ts \u2014 trim the verbose comments and dead code.", "expected_output": "Declines: the methodology is markdown-specific and code / code-comment compression is out of scope. It does not run the compression pipeline on the TypeScript file and points to code-focused tooling instead.", "files": [], "expectations": [ @@ -74,8 +77,8 @@ { "id": 6, "name": "ssot-relocation-not-flavor", - "prompt": "Compress docs/guide.md. One paragraph restates the full label taxonomy that already lives in .claude/rules/labels.md — collapse it down to a citation while you're compressing.", - "expected_output": "Recognizes that collapsing a passage which recaps a cited single source of truth is content RELOCATION, not flavor removal — the semantic-diff net would see the words gone from THIS file and revert them as semantic loss, blind to the SSOT. It treats the relocation as a manual editorial pass (verifying the SSOT actually holds the detail first), or routes multi-file duplication to /extract-ssot, rather than silently deleting the paragraph as flavor.", + "prompt": "Compress docs/guide.md. One paragraph restates the full label taxonomy that already lives in .claude/rules/labels.md \u2014 collapse it down to a citation while you're compressing.", + "expected_output": "Recognizes that collapsing a passage which recaps a cited single source of truth is content RELOCATION, not flavor removal \u2014 the semantic-diff net would see the words gone from THIS file and revert them as semantic loss, blind to the SSOT. It treats the relocation as a manual editorial pass (verifying the SSOT actually holds the detail first), or routes multi-file duplication to /extract-ssot, rather than silently deleting the paragraph as flavor.", "files": [], "expectations": [ "Output distinguishes content relocation (recap of a cited SSOT) from flavor removal", @@ -87,13 +90,13 @@ "id": 7, "name": "repo-wide-interview-fallback", "prompt": "/compress", - "expected_output": "Invoked with no target on a clean tree in an interactive session, the skill does not dead-end at the no-op: it offers a repo-wide run via AskUserQuestion. Declining at any step exits with the friendly no-op message ('No uncommitted .md files. Pass file/dir target.'). On accept it runs the free mechanical audit first and presents INLINE only aggregate counts, a dispatch-cost estimate, and the top-20 highest-yield COMPRESS rows (full per-file table written to a file), then interviews on scope/concurrency with prescribed defaults (all COMPRESS-classified highest-yield-first; 2 concurrent subagents; always-loaded files excluded). No file is compressed before the final confirmation.", + "expected_output": "Invoked with no target on a clean tree in an interactive session, the skill does not dead-end at the no-op: it offers a repo-wide run via AskUserQuestion. Declining at any step exits with the friendly no-op message ('No uncommitted .md files. Pass file/dir target.'). On accept it runs the free mechanical audit first and presents INLINE only aggregate counts, a dispatch-cost estimate, and the top-20 highest-yield COMPRESS rows (full per-file table written to a file), then interviews on scope/concurrency with prescribed defaults (top-10 COMPRESS-classified highest-yield-first; 2 concurrent subagents; always-loaded files excluded). No file is compressed before the final confirmation.", "files": [], "expectations": [ "Empty target + clean tree in an interactive session produces an AskUserQuestion offer, not an immediate no-op exit", "Declining the offer (or any later step) exits with the friendly no-op message rather than proceeding", "The mechanical audit runs BEFORE any compression and its inline output is bounded (aggregates + top-20 rows; full table to a file, never every row inline)", - "The interview presents prescribed defaults: all COMPRESS-classified highest-yield-first, 2 concurrent subagents, always-loaded files excluded", + "The interview presents prescribed defaults: top-10 COMPRESS-classified highest-yield-first, 2 concurrent subagents, always-loaded files excluded", "No file is edited before the user's explicit final confirmation", "Per-file hard rules (semantic-diff dispatch, revert pass, markdownlint, <3% rule) still apply to the confirmed run" ] @@ -101,21 +104,23 @@ { "id": 8, "name": "signal-6-density-skip", - "prompt": "Run /compress audit against docs/reference/api-lifecycle.md — a reference doc written tersely by an agent, with plain prose, no code fences, and no 'Prose compression discipline' citation.", - "expected_output": "The audit's signal 6 fires: no signal 1-4 matches, and the file's flavor-token density (the compress template's LATITUDE tokens per kilo-word) is below 5/kw, so the file classifies SKIP with expected yield <= 3% and a reason citing the density value and the disciplined-by-authorship baseline (9/9 reverted at 0.02-0.4%) — it does NOT fall through to signal 5's COMPRESS verbose-prose baseline.", - "files": [], + "prompt": "Run /docs-hygiene:compress audit against evals/fixtures/terse-agent.md relative to the skill directory \u2014 a terse agent-authored reference doc with plain prose, no code fences, and no Prose compression discipline citation.", + "expected_output": "The audit runs scripts/audit-scan.sh (or the equivalent heuristic). Signal 6 fires: no signal 1-4 matches, and the file's flavor-token density is below 5/kw, so the file classifies SKIP with expected yield <= 3% and a reason citing the density value and the disciplined-by-authorship baseline \u2014 it does NOT fall through to signal 5's COMPRESS verbose-prose baseline.", + "files": [ + "evals/fixtures/terse-agent.md" + ], "expectations": [ "A low-flavor-density file with no signal 1-4 match classifies SKIP, not COMPRESS", "The reason cites flavor-token density (N/kw < 5) and the disciplined-by-authorship empirical baseline", - "Signal 6 is computed as a gate on signal 5's fallback — the verbose-prose baseline is not applied to a file signal 6 catches", + "Signal 6 is computed as a gate on signal 5's fallback \u2014 the verbose-prose baseline is not applied to a file signal 6 catches", "No file is modified (audit is read-only)" ] }, { "id": 9, "name": "fixture-path-skip-in-enumerated-sweep", - "prompt": "Compress plugins/docs-hygiene/skills/compress/ — the whole directory, everything markdown under it.", - "expected_output": "The directory target enumerates recursively, so target-validation gate 5 applies: every enumerated path under evals/fixtures/ skips with reason=fixture and is never edited (fixture verbosity is deliberate test input; compressing it corrupts the eval), while non-fixture markdown proceeds through the normal per-file pipeline. Gate 5 is scoped to the mutating action on ENUMERATED targets only — the empty-arg uncommitted-.md batch, directory expansion, and the repo-wide interview sweep — while an explicitly-named single fixture file (as in eval 1) and the read-only audit action (as in eval 2) bypass it.", + "prompt": "Compress the compress skill directory under the installed docs-hygiene plugin (the skill root that contains evals/fixtures/) \u2014 every markdown under it.", + "expected_output": "The directory target enumerates recursively, so target-validation gate 5 applies: every enumerated path under evals/fixtures/ skips with reason=fixture and is never edited (fixture verbosity is deliberate test input; compressing it corrupts the eval), while non-fixture markdown proceeds through the normal per-file pipeline. Gate 5 is scoped to the mutating action on ENUMERATED targets only \u2014 the empty-arg uncommitted-.md batch, directory expansion, and the repo-wide interview sweep \u2014 while an explicitly-named single fixture file (as in eval 1) and the read-only audit action (as in eval 2) bypass it.", "files": [], "expectations": [ "Enumerated paths under evals/fixtures/ are skipped with reason=fixture before any dispatch", @@ -127,13 +132,14 @@ { "id": 10, "name": "fixture-skip-in-uncommitted-batch", - "prompt": "/docs-hygiene:compress (invoked with no target; the working tree has two uncommitted .md files: docs/notes/draft.md and plugins/docs-hygiene/skills/compress/evals/fixtures/verbose-onboarding-snippet.md, the latter touched by a formatter)", + "prompt": "/docs-hygiene:compress (invoked with no target; the working tree has two uncommitted .md files: docs/notes/draft.md and /evals/fixtures/verbose-onboarding-snippet.md, the latter touched by a formatter)", "expected_output": "The empty-arg + uncommitted-.md batch enumerates its targets from git status, so target-validation gate 5 applies to it exactly as to directory expansion and the interview sweep: the fixture file skips with reason=fixture and is never edited, while docs/notes/draft.md proceeds through the normal per-file pipeline. The user never named the fixture file, so the explicit-naming bypass does not apply.", "files": [], "expectations": [ "The uncommitted fixture file is skipped with reason=fixture, not compressed", "The non-fixture uncommitted file is processed normally", - "Output does not treat an uncommitted edit to a fixture as intentional naming — the bypass is only for a target path the user typed" + "Output does not treat an uncommitted edit to a fixture as intentional naming \u2014 the bypass is only for a target path the user typed", + "This eval is narration-only regarding the hypothetical working-tree state" ] } ] diff --git a/plugins/docs-hygiene/skills/compress/evals/fixtures/terse-agent.md b/plugins/docs-hygiene/skills/compress/evals/fixtures/terse-agent.md new file mode 100644 index 000000000..8472a9b47 --- /dev/null +++ b/plugins/docs-hygiene/skills/compress/evals/fixtures/terse-agent.md @@ -0,0 +1,9 @@ +# Terse agent reference + +Agents write short prose. Paths stay concrete. Commands stay exact. + +Load `config.yaml` first. Then run `/toolchain:check`. Reject uploads over the +limit. Prefer active voice. + +No filler. No hedging. No discipline citation on purpose — this fixture is the +low flavor-token density target for signal-6 SKIP classification. diff --git a/plugins/docs-hygiene/skills/compress/scripts/audit-scan.sh b/plugins/docs-hygiene/skills/compress/scripts/audit-scan.sh new file mode 100755 index 000000000..18c9b982c --- /dev/null +++ b/plugins/docs-hygiene/skills/compress/scripts/audit-scan.sh @@ -0,0 +1,129 @@ +#!/usr/bin/env bash +# Mechanical audit scan for /docs-hygiene:compress audit action. +# Implements the six-signal heuristic in context/target-types.md as a script +# so runs do not re-implement awk/grep counting in-session (auditor M7). +# +# Output: one markdown table row per file, then an aggregate line. +# Exit: 0 on scan paths; 2 on unknown args. +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +usage() { + cat <<'EOF' +audit-scan.sh — classify markdown targets SKIP/COMPRESS/UNCERTAIN. + +Usage: + audit-scan.sh ... + audit-scan.sh --help + +Exit: 0 on scan, 2 on unknown arguments. +EOF +} + +TARGETS=() +while [[ $# -gt 0 ]]; do + case "$1" in + -h | --help) + usage + exit 0 + ;; + -*) + echo "audit-scan.sh: unknown arg '$1'" >&2 + exit 2 + ;; + *) + TARGETS+=("$1") + shift + ;; + esac +done + +if [[ ${#TARGETS[@]} -eq 0 ]]; then + echo "audit-scan.sh: pass one or more .md paths" >&2 + exit 2 +fi + +# Curated flavor-token list for signal 6 — OWNED here (superset/subset of +# Phase A LATITUDE; not claimed to be identical). Keep in sync intentionally. +FLAVOR_RE='just|really|basically|actually|simply|perhaps|somewhat|very|quite|might|in order to|due to the fact that|make use of|it is important to|note that|keep in mind' + +is_signal1_path() { + local f="$1" base + base="$(basename "$f")" + [[ "$f" == *'/.claude/rules/'* || "$f" == */.claude/rules/* ]] && return 0 + [[ "$base" == 'AGENTS.md' || "$base" == 'CLAUDE.md' || "$base" == 'SKILL.md' ]] && return 0 + return 1 +} + +word_count() { + # Floor small files: densities on <50 words are unstable. + local n + n=$(wc -w <"$1" | tr -d ' ') + if [[ "$n" -lt 50 ]]; then + echo 50 + else + echo "$n" + fi +} + +classify_file() { + local file="$1" + local words kw tick_pairs path_hits flavor_hits density reason classify yield + if [[ ! -f "$file" ]]; then + printf '| `%s` | — | SKIP | reason=missing |\n' "$file" + return 0 + fi + words=$(word_count "$file") + kw=$((words > 0 ? (words + 999) / 1000 : 1)) + [[ "$kw" -lt 1 ]] && kw=1 + + if is_signal1_path "$file"; then + printf '| `%s` | ≤3%% | SKIP | author-time-disciplined path (signal 1); empirical baseline 3/3 reverted; use `--force` only for targeted sub-3%% diff |\n' "$file" + return 0 + fi + if grep -Fq 'Prose compression discipline' "$file" 2>/dev/null; then + printf '| `%s` | ≤3%% | SKIP | author-time-disciplined; signal 4 cite; empirical baseline 3/3 reverted; use `--force` only for targeted sub-3%% diff |\n' "$file" + return 0 + fi + + tick_pairs=$(grep -o '`' "$file" 2>/dev/null | wc -l | tr -d ' ') + tick_pairs=$((tick_pairs / 2)) + path_hits=$(grep -Eoc '(@|[a-z][a-z0-9._/-]+\.(md|cs|sh|json|yaml))' "$file" 2>/dev/null || echo 0) + path_hits=${path_hits//$'\n'/} + flavor_hits=$(grep -oiwE "$FLAVOR_RE" "$file" 2>/dev/null | wc -l | tr -d ' ') + + local tick_dens path_dens flavor_dens + tick_dens=$((tick_pairs * 1000 / words)) + path_dens=$((path_hits * 1000 / words)) + flavor_dens=$((flavor_hits * 1000 / words)) + + if [[ "$flavor_dens" -lt 5 ]]; then + printf '| `%s` | ≤3%% | SKIP | flavor-token density %s/kw < 5; disciplined-by-authorship; empirical baseline 9/9 reverted at 0.02-0.4%% |\n' "$file" "$flavor_dens" + return 0 + fi + if [[ "$tick_dens" -gt 10 || "$path_dens" -gt 8 ]]; then + printf '| `%s` | 3-7%% | UNCERTAIN | inline-code density %s/kw AND/OR cross-ref density %s/kw; flavor band narrow |\n' "$file" "$tick_dens" "$path_dens" + return 0 + fi + printf '| `%s` | 5-15%% | COMPRESS | verbose-prose baseline; expected flavor cuts on filler/hedging/articles |\n' "$file" +} + +printf '| target | expected_yield_pct | classify | reason |\n' +printf '|---|---|---|---|\n' + +skips=0 compress=0 uncertain=0 +mapfile -t SORTED < <(printf '%s\n' "${TARGETS[@]}" | LC_ALL=C sort -u) +rows=() +for f in "${SORTED[@]}"; do + row="$(classify_file "$f")" + rows+=("$row") + case "$row" in + *'| SKIP |'*) skips=$((skips + 1)) ;; + *'| COMPRESS |'*) compress=$((compress + 1)) ;; + *'| UNCERTAIN |'*) uncertain=$((uncertain + 1)) ;; + esac +done +printf '%s\n' "${rows[@]}" +printf '\nTotal: %s skips, %s compress-recommended, %s uncertain\n' "$skips" "$compress" "$uncertain" +exit 0 diff --git a/plugins/docs-hygiene/skills/compress/scripts/audit-scan.test.sh b/plugins/docs-hygiene/skills/compress/scripts/audit-scan.test.sh new file mode 100755 index 000000000..0b0fafad2 --- /dev/null +++ b/plugins/docs-hygiene/skills/compress/scripts/audit-scan.test.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +# Contract smoke for audit-scan.sh. +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SCAN="$SCRIPT_DIR/audit-scan.sh" +FIX="$SCRIPT_DIR/../evals/fixtures" + +PASS=0 +FAIL=0 +ok() { echo "ok: $*"; PASS=$((PASS + 1)); } +fail() { echo "FAIL: $*" >&2; FAIL=$((FAIL + 1)); } + +out="$(bash "$SCAN" "$FIX/terse-agent.md" 2>/dev/null)" || true +case "$out" in +*'| SKIP |'*) ok "terse-agent classifies SKIP" ;; +*) fail "terse-agent classifies SKIP (got: $out)" ;; +esac + +out2="$(bash "$SCAN" "$FIX/audit-fixture-dir/verbose.md" 2>/dev/null)" || true +case "$out2" in +*'| COMPRESS |'*) ok "verbose fixture classifies COMPRESS" ;; +*) fail "verbose fixture classifies COMPRESS (got: $out2)" ;; +esac + +out3="$(bash "$SCAN" "$FIX/audit-fixture-dir/lean.md" 2>/dev/null)" || true +case "$out3" in +*'| SKIP |'*) ok "lean fixture classifies SKIP" ;; +*) fail "lean fixture classifies SKIP (got: $out3)" ;; +esac + +if [[ $FAIL -ne 0 ]]; then + echo "$FAIL check(s) failed." >&2 + exit 1 +fi +echo "OK: audit-scan.sh tests passed ($PASS checks)" diff --git a/plugins/docs-hygiene/skills/compress/scripts/detect-caveman.sh b/plugins/docs-hygiene/skills/compress/scripts/detect-caveman.sh index dd4e8014c..2fb7e38b3 100755 --- a/plugins/docs-hygiene/skills/compress/scripts/detect-caveman.sh +++ b/plugins/docs-hygiene/skills/compress/scripts/detect-caveman.sh @@ -17,6 +17,11 @@ Usage: detect-caveman.sh [--help] Exit: always 0. + +Reports available only when an enabled install matches marketplace id +caveman@caveman (or, if that exact id is absent, any other enabled +caveman@* marketplace match — reported as available with that id for +operator visibility; SKILL.md pins marketplace `caveman`). EOF } @@ -34,7 +39,17 @@ if ! command -v claude >/dev/null 2>&1 || ! command -v jq >/dev/null 2>&1; then exit 0 fi -plugin_id="$(claude plugin list --json 2>/dev/null | jq -r '.[] | select(.id | startswith("caveman@")) | .id' 2>/dev/null | head -1 | tr -d '\r')" +# Prefer the pinned marketplace identity; require enabled=true across scopes. +# Disabled installs must not report available (skills of disabled plugins are +# not loaded). Empirically `claude plugin list --json` carries id + enabled. +json="$(claude plugin list --json 2>/dev/null || true)" +plugin_id="$( + printf '%s' "$json" | jq -r ' + [.[] | select(.enabled == true) | select(.id | startswith("caveman@"))] + | (map(select(.id == "caveman@caveman")) + .) + | .[0].id // empty + ' 2>/dev/null | head -1 | tr -d '\r' +)" if [[ -n "$plugin_id" ]]; then printf 'Caveman backend: available\n' diff --git a/plugins/docs-hygiene/skills/compress/scripts/detect-caveman.test.sh b/plugins/docs-hygiene/skills/compress/scripts/detect-caveman.test.sh index 897f358eb..a39139bed 100755 --- a/plugins/docs-hygiene/skills/compress/scripts/detect-caveman.test.sh +++ b/plugins/docs-hygiene/skills/compress/scripts/detect-caveman.test.sh @@ -62,6 +62,47 @@ fi assert_contains "degrades to unknown without claude/jq" "$out_nopath" "Caveman backend: unknown" assert_contains "plugin id none without claude/jq" "$out_nopath" "Caveman plugin id: none" +# Enabled-field contract: a disabled caveman@ entry must not report available. +FAKE_BIN="$SCRIPT_DIR/.fake-bin-$$" +mkdir -p "$FAKE_BIN" +trap 'rm -rf "$FAKE_BIN"' EXIT +cat >"$FAKE_BIN/claude" <<'EOF' +#!/usr/bin/env bash +if [[ "${1:-}" == plugin && "${2:-}" == list && "${3:-}" == --json ]]; then + cat <<'JSON' +[{"id":"caveman@caveman","enabled":false},{"id":"other@x","enabled":true}] +JSON + exit 0 +fi +exit 1 +EOF +chmod +x "$FAKE_BIN/claude" +# jq must remain available from the real PATH. +if out_disabled="$(PATH="$FAKE_BIN:$(dirname "$(command -v jq)"):$PATH" bash "$DETECT" 2>/dev/null)"; then + ok "disabled-install invocation exits 0" +else + fail "disabled-install invocation exits 0 (non-zero exit)" +fi +assert_contains "disabled caveman reports absent" "$out_disabled" "Caveman backend: absent" + +cat >"$FAKE_BIN/claude" <<'EOF' +#!/usr/bin/env bash +if [[ "${1:-}" == plugin && "${2:-}" == list && "${3:-}" == --json ]]; then + cat <<'JSON' +[{"id":"caveman@caveman","enabled":true}] +JSON + exit 0 +fi +exit 1 +EOF +if out_enabled="$(PATH="$FAKE_BIN:$(dirname "$(command -v jq)"):$PATH" bash "$DETECT" 2>/dev/null)"; then + ok "enabled-install invocation exits 0" +else + fail "enabled-install invocation exits 0 (non-zero exit)" +fi +assert_contains "enabled caveman reports available" "$out_enabled" "Caveman backend: available" +assert_contains "enabled caveman reports pinned id" "$out_enabled" "caveman@caveman" + echo if [[ $FAIL -ne 0 ]]; then echo "$FAIL check(s) failed." >&2 From afe41531fca7d8ab6cef6e20eb33853b9df4732d Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 16 Aug 2026 01:14:19 +0000 Subject: [PATCH 3/3] fix(docs-hygiene): satisfy markdownlint, typos, and shellcheck on compress audit Co-authored-by: Kyle Sexton --- plugins/docs-hygiene/skills/compress/SKILL.md | 4 ++++ .../skills/compress/context/fan-out-orchestration.md | 2 +- .../skills/compress/scripts/audit-scan.sh | 11 +++++++++-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/plugins/docs-hygiene/skills/compress/SKILL.md b/plugins/docs-hygiene/skills/compress/SKILL.md index 78ca4ccad..8e6b6800a 100644 --- a/plugins/docs-hygiene/skills/compress/SKILL.md +++ b/plugins/docs-hygiene/skills/compress/SKILL.md @@ -34,17 +34,21 @@ Tri-state: `available` → prefer caveman; `absent` OR `unknown` → treat as ab **Step B — caveman backend (preferred when available):** cross-tool-call steps (Bash state does not persist across tool calls — no `trap … EXIT`, no relying on `$tempdir` in a later call): 1. **Bash call 1** — create a temp copy and echo its absolute path (no EXIT trap): + ```bash tempdir=$(mktemp -d) cp "$target" "$tempdir/$(basename "$target")" printf '%s\n' "$tempdir/$(basename "$target")" ``` + 2. **Skill call** — `Skill(caveman:compress, args="")` on that temp copy. Caveman may write `.original.md` beside the copy inside the tempdir. 3. **Bash call 2** — on caveman success, copy the compressed file back and remove the tempdir explicitly: + ```bash cp "" "$target" rm -rf "$(dirname "")" ``` + On caveman failure, skip the `cp` and still `rm -rf` the tempdir so the real target is untouched. Tempdir wrapper contains caveman's hardcoded `.original.md` backup write. Real-path file replaced only on success. Consumers may add a defensive `**/*.original.md` entry to their `.gitignore` as belt-and-suspenders against cleanup races or future caveman backup-path-convention changes. diff --git a/plugins/docs-hygiene/skills/compress/context/fan-out-orchestration.md b/plugins/docs-hygiene/skills/compress/context/fan-out-orchestration.md index ad77ee73c..e43bbe6e2 100644 --- a/plugins/docs-hygiene/skills/compress/context/fan-out-orchestration.md +++ b/plugins/docs-hygiene/skills/compress/context/fan-out-orchestration.md @@ -68,7 +68,7 @@ Per FINDING block returned in Phase B: - **Phase A scope fence** — each compressor subagent's prompt names exactly ONE allowed file; any other file, git operation, or path is forbidden (the template above encodes this) - **Phase A does NOT invoke `/docs-hygiene:compress`** as a slash command from subagents — self-audit in the compressor context caused reverse-direction edits (see ## History) - **Refuse-fast threshold** — 5 consecutive Phase A or Phase B ERROR returns aborts the batch -- **Yield circuit breaker** — 5 consecutive auto-reverts in a wave (sub-3% / 0-SL successful outcomes that still discard the edit) → pause, report observed yield, and re-confirm with the user before the next wave. Reverts are not ERRORs; without this breaker a misclassified COMPRESS cohort burns two Opus dispatches per file to completion (2026-08-15 calibration: 87 consecutive auto-reverts). +- **Yield circuit breaker** — 5 consecutive auto-reverts in a wave (sub-3% / 0-SL successful outcomes that still discard the edit) → pause, report observed yield, and re-confirm with the user before the next wave. Reverts are not ERROR returns; without this breaker a misclassified COMPRESS cohort burns two Opus dispatches per file to completion (2026-08-15 calibration: 87 consecutive auto-reverts). - **Phase B returns are unverified synthesis** — the main session reverts per finding rather than verifying each by hand; a forbidden citation token invalidates the whole dispatch ## History diff --git a/plugins/docs-hygiene/skills/compress/scripts/audit-scan.sh b/plugins/docs-hygiene/skills/compress/scripts/audit-scan.sh index 18c9b982c..3bfaef443 100755 --- a/plugins/docs-hygiene/skills/compress/scripts/audit-scan.sh +++ b/plugins/docs-hygiene/skills/compress/scripts/audit-scan.sh @@ -7,7 +7,7 @@ # Exit: 0 on scan paths; 2 on unknown args. set -uo pipefail -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# SCRIPT_DIR unused — script is self-contained usage() { cat <<'EOF' @@ -69,8 +69,9 @@ word_count() { classify_file() { local file="$1" - local words kw tick_pairs path_hits flavor_hits density reason classify yield + local words kw tick_pairs path_hits flavor_hits if [[ ! -f "$file" ]]; then + # shellcheck disable=SC2016 # intentional backticks in markdown table cells printf '| `%s` | — | SKIP | reason=missing |\n' "$file" return 0 fi @@ -79,10 +80,12 @@ classify_file() { [[ "$kw" -lt 1 ]] && kw=1 if is_signal1_path "$file"; then + # shellcheck disable=SC2016 # intentional backticks in markdown table cells printf '| `%s` | ≤3%% | SKIP | author-time-disciplined path (signal 1); empirical baseline 3/3 reverted; use `--force` only for targeted sub-3%% diff |\n' "$file" return 0 fi if grep -Fq 'Prose compression discipline' "$file" 2>/dev/null; then + # shellcheck disable=SC2016 # intentional backticks in markdown table cells printf '| `%s` | ≤3%% | SKIP | author-time-disciplined; signal 4 cite; empirical baseline 3/3 reverted; use `--force` only for targeted sub-3%% diff |\n' "$file" return 0 fi @@ -99,13 +102,16 @@ classify_file() { flavor_dens=$((flavor_hits * 1000 / words)) if [[ "$flavor_dens" -lt 5 ]]; then + # shellcheck disable=SC2016 # intentional backticks in markdown table cells printf '| `%s` | ≤3%% | SKIP | flavor-token density %s/kw < 5; disciplined-by-authorship; empirical baseline 9/9 reverted at 0.02-0.4%% |\n' "$file" "$flavor_dens" return 0 fi if [[ "$tick_dens" -gt 10 || "$path_dens" -gt 8 ]]; then + # shellcheck disable=SC2016 # intentional backticks in markdown table cells printf '| `%s` | 3-7%% | UNCERTAIN | inline-code density %s/kw AND/OR cross-ref density %s/kw; flavor band narrow |\n' "$file" "$tick_dens" "$path_dens" return 0 fi + # shellcheck disable=SC2016 # intentional backticks in markdown table cells printf '| `%s` | 5-15%% | COMPRESS | verbose-prose baseline; expected flavor cuts on filler/hedging/articles |\n' "$file" } @@ -122,6 +128,7 @@ for f in "${SORTED[@]}"; do *'| SKIP |'*) skips=$((skips + 1)) ;; *'| COMPRESS |'*) compress=$((compress + 1)) ;; *'| UNCERTAIN |'*) uncertain=$((uncertain + 1)) ;; + *) ;; esac done printf '%s\n' "${rows[@]}"