claudemd-cli 0.55.0 → 0.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,40 @@ All notable changes to the `claudemd` plugin. This changelog tracks plugin artif
8
8
  - **Canonical spec version source**: `spec/CLAUDE.md` top-line title (`# AI-CODING-SPEC vX.Y.Z — Core`) + `spec/CLAUDE-changelog.md` top `##` entry.
9
9
  - **Plugin semver vs spec semver** are independent: plugin patch (0.2.0 → 0.2.1) may ship when spec is unchanged (this release); plugin minor (0.1.9 → 0.2.0) ships when spec minor updates (v0.2.0 shipped spec v6.10.0).
10
10
 
11
+ ## [0.57.0] - 2026-07-25
12
+
13
+ Closes the 2026-07-25 audit's remaining open items — five decided as "do", three explicitly closed as "won't do" (see `tasks/audit-2026-07-25-deferred.md`). Spec unchanged (**v6.22.0**). Minor bump: an opt-in hook is removed.
14
+
15
+ **What changes for users**: `mid-spine-yield-scan` is gone — if you had `MID_SPINE_YIELD_SCAN=1` set, that Stop advisory no longer fires and `DISABLE_MID_SPINE_YIELD_HOOK` / `/claudemd-toggle mid-spine-yield-scan` are no-ops (the §11 rule itself is unchanged and still self-enforced; only its observation mirror is retired). `banned-vocab` bypass rows change shape, and a same-day re-run of `/claudemd-sampling-audit` now refuses to overwrite its own report.
16
+
17
+ - **remove: the `mid-spine-yield-scan` Stop hook (16 → 15 hooks).** Zero events for its entire lifetime (v0.15.0 → now) — it shipped default-OFF per the behavior-layer convention and was never enabled anywhere, while still spawning a bash process on every Stop to exit at its opt-in guard, and its stderr text had drifted to quote the pre-v6.21.1 rule wording. By the project's own §13.1 criterion (hook-enforced, 0 hits in window) it was the cleanest demote candidate in the tree; syncing it to the v6.22.0 precondition would have been spend on something that has never run. `spec/hard-rules.json` is untouched: `§11-mid-spine-yield` is `enforcement: "self"` with `rule_hits_section: null`, so the §13 partition (7 hook / 14 self / 1 both / 1 external) is unaffected. Removed: the hook, its 12-case suite, the `hooks.json` registration, the registry entry (drops `DISABLE_MID_SPINE_YIELD_HOOK`), the doctor kill-switch row, the contract DOCUMENTED entry, and the count assertions in `full-lifecycle` / `hook-registry` / `install` / README (16 → 15 together). The `mid-spine-advisory` event is retired but historical rows stay valid — `session-end-check`'s L2+ heuristic still counts them.
18
+ - **feat(telemetry): `banned-vocab` bypass rows carry the term they suppressed.** The demote question is "which word does the operator keep overriding", and the data could not answer it: deny rows carried `{matched}`, bypass rows carried only `{token}`, and the token short-circuited before the scan ran. The scan now runs first and the bypass row is emitted at the hit with `{token, matched, path}`. **Measurement-semantics change**: a token on a command that would have passed anyway now records nothing (it was inflating the override rate with prophylactic tokens), so the 51.6% figure and any post-0.57.0 rate are not comparable. This is the instrumentation the §13.2 review needs before deciding anything about §10-V — the decision itself stays deferred.
19
+ - **fix(doctor): the demote label cited a rule the spec does not contain.** `rule-usage:<section>` reported "§0.1 demotion candidate" above a 50% bypass ratio, but §0.1 demotes on ZERO hits and a bypass threshold appears only as a §13.3 **promotion** gate (<10% to advance). The high-override signal is real; the action it licenses is a review, not a demotion. Relabeled to point at the §13.2 batch review and to say outright that no demote-by-bypass-rate rule exists — the mislabel had cost a full re-adjudication every run (`tasks/banned-vocab-demote-evaluation-2026-07-25.md` settled exactly this question for §10-V and the check kept re-asking it).
20
+ - **fix(hard-rules-audit): safety-class rules are exempt from the demote queue.** §8 rules are §5.1 Never-downgrade AND sparse by design — the attack surface they guard is rare, not absent — so listing `§8-curl-sh` as a 0-hit demote candidate recommended a forbidden action. They move to a new `safetyClassExempt` field, still visible (a safety gate that never fires may be broken, but that is an FN-matrix question, not a demotion one).
21
+ - **fix(sampling-audit): refuse to overwrite an existing report.** The output path is date-based, so a same-day re-run always collides — on 2026-07-25 a plain run replaced a committed, hand-annotated 216-line calibration record with a 16-line fresh scan (recovered via git). Now refuses unless `--force`; `--json` still prints without writing.
22
+ - **test: class-level repo-write guard in `run-all.sh`.** Records `git rev-list --count HEAD` before and after the suite and fails if it moved. `perf-baseline.sh` escaped for two months because only the files someone thought to check were checked; two lines now cover every existing and future suite. Verified RED against a deliberately-escaping fixture test.
23
+ - **docs**: the 0.55.0 entry's noop-commit count is corrected in place — 46 across two clusters (40 on 07-17, 6 on 05-10), not 48 all on one day; the May cluster dated to the night `perf-baseline.sh` first shipped and had gone unnoticed.
24
+
25
+ **Explicitly not done** (recorded in `tasks/audit-2026-07-25-deferred.md`): the §8-rm-rf-var scratch-cleanup friction — 0.56.0 changed the deny message to hand out the mktemp recipe, and measuring that intervention comes before stacking a second one; a byte cap on `MEMORY.md` — 78% of its 60 entries are durable `feedback_*` lessons, so a cap deletes lessons, and the recoverable space is in consolidating 9 closed-loop `project_*` audit entries; slash-command usage telemetry — an unused command's cost is 5,380 B of frontmatter, which compression addresses without building a measurement apparatus.
26
+
27
+ ## [0.56.0] - 2026-07-25
28
+
29
+ Fix batch from the 2026-07-25 four-dimension audit (`docs/comprehensive-audit-2026-07-25-v0.55.0.md`, local-only per the docs/ gitignore convention). Ships spec **v6.22.0** (minor). Minor bump: the §8 curl-sh and rm-rf-var deny surfaces both widen (user-visible default behavior change).
30
+
31
+ **What changes for users**: three §8 shapes that previously slipped now deny — a wrapper hiding a path-prefixed fetch or sink binary (`sudo /usr/bin/curl … | sh`, `curl … | sudo /bin/sh`), a `..` walk out of an mktemp-validated var (`rm -rf "$S/../../elsewhere"`), and provenance riding on a command that binds `IFS`. Escape hatches unchanged: `[allow-curl-sh]` / `[allow-rm-rf-var]` tokens, `DISABLE_PRE_BASH_SAFETY_HOOK=1`, or pin the plugin ≤0.55.0; the deny message carries all bypass paths at the moment of impact. Telemetry rows gain a `hook_version` field — consumers reading `~/.claude/logs/claudemd.jsonl` should treat it as optional (null on pre-0.56.0 rows).
32
+
33
+ - **fix(§8): curl-sh gate missed the canon-after-strip its sibling gates have** (audit F1, the batch's only HIGH). `canon_cmd_words` runs globally at command position, but a transparent wrapper OCCUPIES that position — so `sudo /usr/bin/curl … | sh` reached the gate with the path prefix intact while the rm (`:626`) and npx (`:864`) gates, which re-canonicalize after `s8_strip_wrappers`, denied the identical shape. The curl-sh gate now does the same on the fetch side, and `CURLSH_SINKPFX` tolerates a backslash or a slash-terminated path on the sink word (the sink is regex-matched, not word-looped, so canon cannot reach it there). Both are monotonic — they expose tokens the shell EXECs, never hide one. Corpus 411 → 425 (+14: 8 RED, 3 rm/npx parity analogues, 3 FP guards); full-corpus differential vs the pre-fix hook: 8 allow→deny, 0 deny→allow. A segment lacking the literal `curl`/`wget` now short-circuits before the strip/canon/grep spawns — proven verdict-neutral over all 332 rows.
34
+ - **fix(§8): four mktemp-provenance holes, all found by adversarial review of a change that was then reverted.** `rm -rf "$S/../../home/u/proj"` rode on `$S`'s provenance (provenance certifies where the var points, not where a `..` walk from it lands); `IFS=/` word-split an unquoted provenance target into relative paths deleted from the cwd; `S+=/../../etc` escaped the rebind guard because `canon_cmd_words` did not treat `+=` as an assignment and basenamed the whole word, erasing the bare-name mention the guard counts; `rm -rf "$S" "$EVIL"` passed because condition 3 scanned only the first positional. All four now deny; `S=$(mktemp -d); rm -rf "$S/build"` and the quoted `bak="$(mktemp …)"` idiom still allow.
35
+ - **revert(§8): temp-root literal provenance, built and withdrawn the same day.** It was aimed at the batch's largest DX finding — 86% of §8-rm-rf-var denies (177/205 over 14d) are the agent cleaning its own scratch dirs, which §8.V4 mandates it do. Adversarial review broke it four ways in one pass, three sandbox-confirmed deleting real directories: backslash-escaped traversal (`D=/tmp/\.\./\.\./etc` — hook text ≠ runtime value), subshell / short-circuit / pipeline / background assignments that never bind the parent shell yet land at a "segment head", `..` residue, and `IFS` truncating the captured RHS to a prefix of the runtime value. Root cause is the one `tasks/specs/s8-literal-provenance.md` already records for the rejected v0.48.0 attempt: **text position is not command position, and a captured RHS that merely prefixes the runtime value proves nothing.** All 28 breaking commands ship as F21 `deny` corpus rows so a third attempt cannot pass silently. The friction stays open in `tasks/audit-2026-07-25-deferred.md` — the safe direction is making the harness emit mktemp-shaped scratch paths, not loosening the gate.
36
+ - **fix(audit): the dashboard contradicted the calibration record it is built on.** `selfCompliance()` recomputed status from `precision >= 0.8` alone, so the six detectors 0.54.0 closed reported `collecting (rate withheld…)` — advertising exactly the "keep collecting" posture the 2026-07-24 labeling pass rejected. It now honors `ruleStatus()` / `closedReason` from `sampling-audit.js`; rate stays A4-withheld either way.
37
+ - **feat(telemetry): `hook_version` on every rule-hits row.** The live log holds 242 `stale-root` events across 12 distinct version gaps (worst: 0.37.0 hooks vs 0.41.0 install, 145 prompts over 12.7h) — rows written by a stale registered hook dir were pooled indistinguishably with current ones, making their contribution to calibration unknowable rather than measured-small. Cached per process; null when the manifest is unreadable.
38
+ - **fix(§10-V): unterminated fenced block diverged between the two engines.** `stripIdentifiers` blanked from an unterminated ``` to EOF while the bash hook — whose jq extraction flattens newlines, leaving its fence-awk inert — scanned the claim after it. A terminator guard (fence opens only if a closing fence exists later) restores parity in the HIT direction; same strict-AND-narrowing shape as the §8 heredoc guard, so blanked text is a subset of before. New multi-line parity fixture + a terminated-fence control.
39
+ - **fix(§11-turn-yield): `YIELD_ASK_RE` read the bare `么` of 什么/怎么/这么 as a question particle**, so mid-work statements ("要处理什么边界情况") counted as asks and suppressed the tell. Negative lookbehind; true-ask control arms retained.
40
+ - **spec v6.22.0** (minor): level/precedence seam closure — Iron Law #1 surfaces in core §7 at its true L2+ level (core's only pointer filed it under L3 while §2.2 forbids L2 loading extended); the §11 Tell's "closed" now includes a compliant L1 single-line `Done:` (the four-section-only definition made correct L1 closes self-diagnose as yields); §3's Order line ranks project `CLAUDE.md` explicitly, and §13's drift check states the delegation boundary instead of citing a rank §3 never held; §2's L0 row drops `config`, which contradicted the §0 Fast-Path whitelist and §5 hard-AUTH. Paired net-delete per §0.1 (C9 §9↔§1 double-write, C11 footer pointer dup, §1 `reasoning`): core 24715 → 24714B. §EXT §12 Fallback gains `gs:/document-release` + `/careful`. Two new joins pin the class: Iron Law #1 level-tag consistency (core mention ↔ extended heading) and §3-rank citation coverage.
41
+ - **ci: shellcheck scope 24/53 → all 53 shell files** (audit L2). The excluded set held a live guard-defeating bug of exactly the class the gate exists for: `DISABLE_UPSTREAM_CHECK=1 OUT=$(… bash hook …)` creates a shell variable rather than exporting into the child. Fixed, plus two dead-variable warnings.
42
+ - **test: version pins become dynamic consistency joins** (audit L3) — `spec-structure.test.js` asserted `6.21.2` under a test named `changelog top entry is v6.21.1`, and needed a hand edit every release. Now core ↔ extended ↔ `hard-rules.json` ↔ changelog must agree with each other; "did the release land" stays with `version-cascade-check.js` and the upgrade-lifecycle pin.
43
+ - **docs**: `cross-project-pilot.md` records that `perf-baseline.sh` is hermetic since 0.55.0 (its `cd <pilot-project>` step is now a silent no-op, and `off_ms` is no longer comparable with pre-0.55.0 series); `OPERATOR.md` marks the subject-keyword fix-rate metric as broken as a treadmill gauge (it re-measures 15-20% where content classification puts fix-primary releases near 60-70% — release subjects stopped using the words); the 0.52.0 entry above notes its referenced audit file is a local-only working doc; `RULE-HITS-SCHEMA.md` documents `hook_version`.
44
+
11
45
  ## [0.55.0] - 2026-07-25
12
46
 
13
47
  Ships spec **v6.21.2** (patch) plus two hermeticity/observability fixes surfaced by the 2026-07-25 spec audit ("按建议执行" pass over the audit's P0-P3).
@@ -15,7 +49,7 @@ Ships spec **v6.21.2** (patch) plus two hermeticity/observability fixes surfaced
15
49
  **What changes for users**: SessionStart now banners when Claude Code is loading hooks from an older build than the marketplace cache holds — the exact drift that let two releases (0.53.0, 0.54.0) run unnoticed on a stale 0.52.0 install; `scripts/perf-baseline.sh` can no longer commit into your repo or write live telemetry.
16
50
 
17
51
  - **feat(session-start): stale-registration banner (`stale_cache_check`).** `upstream_check` compares the cache max against the REMOTE tag, so after a release + local marketplace update (cache == remote) it stays silent even though the registered root is older — reproduced 2026-07-25: hooks running v0.52.0 while cache and remote both held v0.54.0, zero banners for two releases; only `doctor`'s on-demand `plugin cache:staleness` check saw it. The new check compares the running root against the local cache max — no network, no 24h sentinel, repeats every SessionStart until `/claudemd-refresh` re-registers. Skips the redundant remote probe when it fires. Telemetry reuses the registered `stale-root` event with `{hook_version, cache_max_version}` extra (no new event name). Shared `semver_cache_max()` helper replaces the inline glob in `upstream_check` (one source for the cache scan). Tests: `session-start.test.sh` cases 23-24 — case 23 RED pre-fix (hook emitted nothing on a newer cache), 24/24 post-fix.
18
- - **fix(perf-baseline): probes run in a throwaway repo + telemetry off.** The `git commit --allow-empty -m 'noop'` probe executed in the CALLER's cwd — 48 stray `noop` commits landed on this repo's main on 2026-07-17 (all stamped 15:14:04-05) and were pushed with the next release. All probe commands now execute inside a `mktemp -d` sandbox repo (trap-cleaned, spec §8.V3), and the script exports `DISABLE_RULE_HITS_LOG=1` so its synthetic hook invocations stop polluting live telemetry (`feedback_manual_hook_probe_pollutes_telemetry`). New regression gate `tests/integration/perf-baseline-hermetic.test.sh`: RED pre-fix (caller repo commits 1 → 3 at `--runs 1`), 3/3 post-fix.
52
+ - **fix(perf-baseline): probes run in a throwaway repo + telemetry off.** The `git commit --allow-empty -m 'noop'` probe executed in the CALLER's cwd — 48 stray `noop` commits landed on this repo's main on 2026-07-17 (all stamped 15:14:04-05) and were pushed with the next release. *(Correction, 0.57.0: the real count is 46 across **two** clusters — 40 on 2026-07-17 15:14:04-05 and 6 on 2026-05-10 02:58:04-05, the night `perf-baseline.sh` first shipped in v0.9.7. The May cluster went unnoticed at the time and was folded into the July narrative above. `git log --grep='^noop$'` is the check.)* All probe commands now execute inside a `mktemp -d` sandbox repo (trap-cleaned, spec §8.V3), and the script exports `DISABLE_RULE_HITS_LOG=1` so its synthetic hook invocations stop polluting live telemetry (`feedback_manual_hook_probe_pollutes_telemetry`). New regression gate `tests/integration/perf-baseline-hermetic.test.sh`: RED pre-fix (caller repo commits 1 → 3 at `--runs 1`), 3/3 post-fix.
19
53
  - **change(spec v6.21.2): §EXT §10-V compressed to OK-shapes + pointers** — rationale and demote-evaluation record in `spec/CLAUDE-changelog.md`. `banned-vocab-canonical.json` updated in step (pattern rows kept as in_spec=false, ten spec-only rows retired); `hooks/banned-vocab.patterns` untouched — mechanical enforcement byte-identical.
20
54
 
21
55
  ## [0.54.0] - 2026-07-24
@@ -42,7 +76,7 @@ Ships spec **v6.21.0** (minor) plus the drift test that pins it. No hook, script
42
76
 
43
77
  ## [0.52.0] - 2026-07-24
44
78
 
45
- Fix batch from the 2026-07-24 production-readiness audit (`docs/2026-07-24-production-readiness-audit.md`) — items 1-3 and 5-10 of its action table, plus the restocked core net-delete candidate pool (C7-C12, item 4 core-side). Ships spec **v6.20.1** (patch). Minor bump: the curl-sh gate's deny surface widens (user-visible default behavior change).
79
+ Fix batch from the 2026-07-24 production-readiness audit (`docs/2026-07-24-production-readiness-audit.md` — a local-only working file per the docs/ gitignore convention; not distributed, and no longer on disk as of 2026-07-25. The action-table items are restated below) — items 1-3 and 5-10 of its action table, plus the restocked core net-delete candidate pool (C7-C12, item 4 core-side). Ships spec **v6.20.1** (patch). Minor bump: the curl-sh gate's deny surface widens (user-visible default behavior change).
46
80
 
47
81
  **What changes for users**: wrapper-prefixed fetch-to-shell commands (`sudo curl … | sh`, `nohup curl … | bash`, `FOO=1 curl … | sh`, `timeout 5 curl … | sh`, `( sudo curl … | sh )`) previously slipped past the §8 curl-sh gate and now deny. Escape hatches unchanged: `[allow-curl-sh]` token or `DISABLE_PRE_BASH_SAFETY_HOOK=1`; prior behavior = pin plugin ≤0.51.1. The deny message carries the bypass paths at the moment of impact.
48
82
 
package/README.md CHANGED
@@ -1,12 +1,12 @@
1
1
  # claudemd
2
2
 
3
- > A **personal AI-coding discipline harness**: one developer's opinionated **AI-CODING-SPEC v6.21**, encoded as Claude Code shell hooks and shipped with the plugin. Built and dogfooded on my own repos — fork and adapt, don't adopt wholesale.
3
+ > A **personal AI-coding discipline harness**: one developer's opinionated **AI-CODING-SPEC v6.22**, encoded as Claude Code shell hooks and shipped with the plugin. Built and dogfooded on my own repos — fork and adapt, don't adopt wholesale.
4
4
 
5
5
  [![CI](https://github.com/sdsrss/claudemd/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/sdsrss/claudemd/actions/workflows/ci.yml)
6
6
  [![npm](https://img.shields.io/npm/v/claudemd-cli.svg)](https://www.npmjs.com/package/claudemd-cli)
7
7
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
8
8
 
9
- claudemd plugs into the Claude Code hook system to **block commits, pushes, and bash commands** that violate AI-CODING-SPEC v6.21 — banned vocabulary in commit messages, `rm -rf $VAR` without variable validation, ship-on-red-CI, unread `MEMORY.md` entries during release flows, and more. The spec itself (`CLAUDE.md` + `CLAUDE-extended.md` + `CLAUDE-changelog.md` + `OPERATOR.md`) ships with the plugin and installs into `~/.claude/`, so the rules Claude Code reads at session start match the rules the hooks enforce. (`OPERATOR.md` is the human-only spec-maintenance handbook — Agent-loaded files are the CLAUDE trio.)
9
+ claudemd plugs into the Claude Code hook system to **block commits, pushes, and bash commands** that violate AI-CODING-SPEC v6.22 — banned vocabulary in commit messages, `rm -rf $VAR` without variable validation, ship-on-red-CI, unread `MEMORY.md` entries during release flows, and more. The spec itself (`CLAUDE.md` + `CLAUDE-extended.md` + `CLAUDE-changelog.md` + `OPERATOR.md`) ships with the plugin and installs into `~/.claude/`, so the rules Claude Code reads at session start match the rules the hooks enforce. (`OPERATOR.md` is the human-only spec-maintenance handbook — Agent-loaded files are the CLAUDE trio.)
10
10
 
11
11
  A standalone CLI (`npx claudemd-cli`) reuses the same `banned-vocab.patterns` source for git pre-commit hooks, GitHub Actions, and other agents that don't run inside Claude Code.
12
12
 
@@ -57,10 +57,10 @@ Verify in one command (Linux): `node --version && jq --version && gh --version &
57
57
 
58
58
  | Layer | Contents |
59
59
  |---|---|
60
- | 16 shell hooks | `banned-vocab-check` · `pre-bash-safety-check` · `ship-baseline-check` · `residue-audit` · `memory-read-check` · `memory-prompt-hint` · `mid-spine-yield-scan` · `sandbox-disposal-check` · `session-start-check` · `session-extended-read` · `session-summary` · `session-end-check` · `transcript-vocab-scan` · `transcript-structure-scan` · `version-sync` · `mem-audit` |
60
+ | 15 shell hooks | `banned-vocab-check` · `pre-bash-safety-check` · `ship-baseline-check` · `residue-audit` · `memory-read-check` · `memory-prompt-hint` · `sandbox-disposal-check` · `session-start-check` · `session-extended-read` · `session-summary` · `session-end-check` · `transcript-vocab-scan` · `transcript-structure-scan` · `version-sync` · `mem-audit` |
61
61
  | 16 slash commands | `/claudemd-install` · `/claudemd-status` · `/claudemd-update` · `/claudemd-refresh` · `/claudemd-audit` · `/claudemd-toggle` · `/claudemd-doctor` · `/claudemd-analyze` · `/claudemd-uninstall` · `/claudemd-rules` · `/claudemd-clean-residue` · `/claudemd-sparkline` · `/claudemd-sampling-audit` · `/claudemd-bypass-audit` · `/claudemd-design-adopt` · `/claudemd-statusline` |
62
62
  | 1 standalone CLI | `claudemd-cli lint` · `claudemd-cli audit` ([npm: `claudemd-cli`](https://www.npmjs.com/package/claudemd-cli)) |
63
- | Spec v6.21 | `~/.claude/CLAUDE.md` · `CLAUDE-extended.md` · `CLAUDE-changelog.md` · `OPERATOR.md` (backup-before-overwrite) |
63
+ | Spec v6.22 | `~/.claude/CLAUDE.md` · `CLAUDE-extended.md` · `CLAUDE-changelog.md` · `OPERATOR.md` (backup-before-overwrite) |
64
64
  | StatusLine (opt-out) | PS1-style line — `user@host:dir (branch) Model [ctx:N% · 5h:N% · 7d:N%]` (`dir` = cwd basename; context / 5-hour quota / weekly quota, all **used %**, read from Claude Code's `rate_limits` payload; quota segments auto-hide when the data is absent, or force-hide with `DISABLE_STATUSLINE_QUOTA=1`) — wired into `~/.claude/settings.json` on install **only when the slot is empty**; an existing statusline is left untouched. Skip entirely with `CLAUDEMD_NO_STATUSLINE=1`. Manage via `/claudemd-statusline`. |
65
65
 
66
66
  Install backs up a hand-written `~/.claude/CLAUDE.md` (any file without the `# AI-CODING-SPEC` H1) to `~/.claude/backup-<ISO>/` before overwriting (last 5 kept automatically). An already-installed claudemd spec is overwritten **without** a backup — deliberate (v0.23.11): the sole backup is always your own content, so `restore` can never return a stale spec instead. Uninstall offers `keep / restore / delete`; `delete` requires an extra confirmation.
@@ -183,7 +183,6 @@ export DISABLE_TRANSCRIPT_STRUCTURE_SCAN_HOOK=1 # v0.9.10+ — Stop §10 four-s
183
183
  export DISABLE_MEM_AUDIT_HOOK=1 # v0.9.4+ — Stop Why:-less citation advisory
184
184
  export DISABLE_SESSION_END_CHECK_HOOK=1 # v0.9.27+ — SessionEnd §11-session-exit mid-SPINE check
185
185
  export DISABLE_SESSION_EXTENDED_READ_HOOK=1 # v0.10.1+ — PreToolUse:Read §13.1-extended-read denominator signal
186
- export DISABLE_MID_SPINE_YIELD_HOOK=1 # v0.15.0+ — Stop §11-mid-spine-yield advisory (opt-in via MID_SPINE_YIELD_SCAN=1)
187
186
  ```
188
187
 
189
188
  **2a. Per-sub-feature** (v0.4.0+). Sub-flags inside an enabled hook, named without the `_HOOK` suffix:
@@ -348,12 +347,12 @@ claudemd/
348
347
  ├── .claude-plugin/
349
348
  │ ├── plugin.json # minimal manifest (name, version, author, license, keywords)
350
349
  │ └── marketplace.json # marketplace catalog entry
351
- ├── hooks/ # 16 shell hooks + hooks/lib/ (hook-common, rule-hits, platform)
350
+ ├── hooks/ # 15 shell hooks + hooks/lib/ (hook-common, rule-hits, platform)
352
351
  │ └── hooks.json # authoritative hook registration (v0.1.5+); CC expands ${CLAUDE_PLUGIN_ROOT} here
353
352
  ├── commands/ # 16 slash-command markdown files
354
353
  ├── bin/ # standalone CLI entrypoint (claudemd-lint.js → `npx claudemd-cli` on npmjs.org)
355
354
  ├── scripts/ # 18 Node.js scripts + scripts/lib/ (single-source registry, lint, etc.)
356
- ├── spec/ # shipped v6.21.2 CLAUDE*.md trio + OPERATOR.md + hard-rules.json manifest
355
+ ├── spec/ # shipped v6.22.0 CLAUDE*.md trio + OPERATOR.md + hard-rules.json manifest
357
356
  ├── tests/ # hook shell tests + Node.js tests + integration + fixtures
358
357
  ├── docs/ # ADDING-NEW-HOOK.md + RULE-HITS-SCHEMA.md + superpowers/
359
358
  └── .github/workflows/ # ci.yml (ubuntu+macOS × node 20) + npm-publish.yml (tag-triggered)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudemd-cli",
3
- "version": "0.55.0",
3
+ "version": "0.57.0",
4
4
  "description": "Standalone CLI for §10-V banned-vocab + transcript scanning. Companion to the claudemd Claude Code plugin (github.com/sdsrss/claudemd) for use in git pre-commit hooks, GitHub Actions, and other agents.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -92,13 +92,28 @@ function posixClassesToJs(regex) {
92
92
  // prose and bare-word claims (the real violations) stay intact and still match.
93
93
  export function stripIdentifiers(text) {
94
94
  if (!text) return text;
95
- // 1. Fenced code blocks: line-based fence toggle, mirroring the bash awk
96
- // `/^[[:space:]]*```/{f=!f; next} !f` drop the ``` marker lines AND the
97
- // body between them. An unterminated fence drops to EOF (in_fence stays on).
95
+ // 1. Fenced code blocks: line-based fence toggle with a TERMINATOR GUARD
96
+ // (2026-07-25 audit): an opening ``` only starts a fence if a closing
97
+ // fence line exists later otherwise it is literal text and everything
98
+ // after it stays scannable. Pre-fix an unterminated fence blanked to EOF,
99
+ // silently under-counting relative to the live hook: the bash side
100
+ // (transcript-vocab-scan.sh) flattens newlines during jq extraction, so
101
+ // its fence-awk never fires and an unterminated-fence claim HITs there —
102
+ // node=miss/bash=hit was the one divergence a 12-shape differential
103
+ // found. Same strict-AND-narrowing shape as the §8 heredoc terminator
104
+ // guard: blanked text is a subset of before, so this can only EXPOSE
105
+ // more text to the detector, never hide a claim.
106
+ const lines = text.split('\n');
98
107
  const kept = [];
99
108
  let inFence = false;
100
- for (const line of text.split('\n')) {
101
- if (/^\s*```/.test(line)) { inFence = !inFence; continue; }
109
+ for (let i = 0; i < lines.length; i++) {
110
+ const line = lines[i];
111
+ if (/^\s*```/.test(line)) {
112
+ if (inFence) { inFence = false; continue; }
113
+ if (lines.slice(i + 1).some(l => /^\s*```/.test(l))) { inFence = true; continue; }
114
+ kept.push(line);
115
+ continue;
116
+ }
102
117
  if (!inFence) kept.push(line);
103
118
  }
104
119
  return kept.join('\n')