claudemd-cli 0.17.5 → 0.21.5
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 +465 -0
- package/README.md +45 -23
- package/hooks/banned-vocab.patterns +1 -1
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,471 @@ 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.21.5] - 2026-05-24
|
|
12
|
+
|
|
13
|
+
**Patch — clarify: memory-layer terminology cleanup. Spec bumped v6.13.1 → v6.13.2 (clarification only, identical Agent behavior).**
|
|
14
|
+
|
|
15
|
+
### Why this patch
|
|
16
|
+
|
|
17
|
+
User audit of the `claude-mem-lite` plugin × `MEMORY.md` durable layer integration surfaced a terminology collision in agent-visible context:
|
|
18
|
+
|
|
19
|
+
- claude-mem-lite plugin emits `[mem] Startup dashboard:` / `[mem] events: ...` / mid-turn `[mem]` recall blocks
|
|
20
|
+
- claudemd plugin's `memory-prompt-hint.sh` emitted `[mem-hint]` for MEMORY.md tag matches
|
|
21
|
+
- claudemd plugin's `mem-audit.sh` Stop hook outputs `[claudemd] §11-EXT mem-audit:` (already prefixed, OK)
|
|
22
|
+
|
|
23
|
+
Two `mem-`-prefixed channels referring to different layers — ambiguous when the agent reasons about "what does mem mean here". User's audit flagged the bare `mem` overload as a routing-quality issue.
|
|
24
|
+
|
|
25
|
+
### What changed
|
|
26
|
+
|
|
27
|
+
- **Hook output**: `hooks/memory-prompt-hint.sh:169` — `[mem-hint] §11 —` → `[claudemd] §11 memory-hint:`. Brings the only outlier into the existing `[claudemd] §<section> <hook-name>:` convention used by `mem-audit.sh`. Same payload, same instructions; LLM behavior unchanged.
|
|
28
|
+
- **Spec §11-EXT Memory operations**: added Terminology bullet (~+620B) defining `claude-mem-lite` = recall plugin, `MEMORY.md` = durable layer, ban bare `mem` in new spec/hook text. Scopes existing `mem_*` plugin tool names and `mem-audit` hook name so no renames are triggered.
|
|
29
|
+
- **Spec §11 SPINE Mid-SPINE turn-yield**: inline qualifier added — `mid-turn `[mem]` context` → `mid-turn `[mem]` claude-mem-lite recall` (+15B core delta).
|
|
30
|
+
- **Memory file `feedback_memory_layer_routing.md`**: replaced the "(out of scope; not yet shipped) `claude-mem-lite import-recall-fallback`" promise with a manual-migration note. The plugin-absent → re-detect path is narrow enough that a shipped importer has no clear ROI; durable layer should not carry vaporware tool references.
|
|
31
|
+
|
|
32
|
+
### What was NOT changed (deliberately, per user-confirmed minimal scope)
|
|
33
|
+
|
|
34
|
+
- Hook file names (`hooks/mem-audit.sh` stays) — file-name rename would cascade into `hooks.json`, `scripts/lib/hook-registry.js`, tests + sentinel paths; cost > benefit at terminology layer.
|
|
35
|
+
- Published env var `DISABLE_MEM_AUDIT_HOOK` — rename would be a breaking change for users who set it; deprecation grace would need a different release shape (L3 minor).
|
|
36
|
+
- Plugin tool/CLI names `mem_save / mem_search / mem_recall / mem_recent` — those are claude-mem-lite plugin's published surface, not in this repo.
|
|
37
|
+
|
|
38
|
+
### Verification
|
|
39
|
+
|
|
40
|
+
- `node scripts/version-cascade-check.js` → ok (v6.13 consistent across 3 file(s); Sizing drift within ±20B for 3 target(s))
|
|
41
|
+
- `grep -rn "mem-hint\\|\\[mem-hint" tests/ scripts/` → 0 results pre-edit (no test depends on the old literal)
|
|
42
|
+
- spec extended 46071 → 47573 bytes (Δ +1502, 2427B headroom remaining, 95.15% of 50000B ceiling)
|
|
43
|
+
|
|
44
|
+
### Followup candidates (not in this patch, logged to `tasks/improvement-candidates-2026-05.md`)
|
|
45
|
+
|
|
46
|
+
- P1: type-distribution audit (84% of memory files are `feedback_*` in this project — needs `claudemd-audit` data before any spec change)
|
|
47
|
+
- P2: lesson-promotion candidate counter (semi-auto `mem_search` aggregation by similarity)
|
|
48
|
+
- P4: MEMORY.md sub-index threshold at 50 lines / 10 KB (preemptive; not active need)
|
|
49
|
+
- ~~P5: `memory-prompt-hint.sh` sort order ratchet~~ — CLOSED on re-verify: hook already sorts by `match_count desc, mtime desc` since v0.19.2 B3. Original audit misread the changelog-style comment at lines 122-123 as current behavior. Lesson logged in candidates file.
|
|
50
|
+
|
|
51
|
+
## [0.21.4] - 2026-05-21
|
|
52
|
+
|
|
53
|
+
**Patch — fix: §8 SAFETY rm-detection coverage gaps surfaced by continued dogfooding of v0.21.3. Per-segment iteration replaces single-shot greedy regex. Spec unchanged at v6.13.1.**
|
|
54
|
+
|
|
55
|
+
### Why this patch
|
|
56
|
+
|
|
57
|
+
Round-4 dogfood of v0.21.3 surfaced three more §8 SAFETY rm-rf-var detection holes — same severity class, same file, all reachable. The v0.21.3 fix closed the sanitize-bypass and added `${VAR:?…}` guard recognition but kept the original single-shot `RM_FLAG_REGEX` matching strategy. That strategy fails on three flag/sequencing variants:
|
|
58
|
+
|
|
59
|
+
1. **Long-form flags** — `rm --recursive --force $X` allowed. The regex required a single `-*[rRfF]*` block; `--recursive` doesn't fit.
|
|
60
|
+
2. **Split short flags** — `rm -v -i -rf $X` allowed. Same root cause — multiple `-*` blocks, only the first considered.
|
|
61
|
+
3. **Multi-rm chain** — `rm -rf "$A" && : "${B:?msg}" && rm -rf "$B"` allowed. The greedy `sed -E "s/.*${RM_FLAG_REGEX}//"` anchored at the LAST `rm -rf` in the command; the earlier unguarded rm-rf on `$A` was silently skipped because its target never reached the for-loop. The guard for `$B` accidentally certified the whole command.
|
|
62
|
+
|
|
63
|
+
All three were latent in every release back to v0.5.0; v0.21.3's per-varname guard recognition exposed them as exploitable.
|
|
64
|
+
|
|
65
|
+
### What changed (code)
|
|
66
|
+
|
|
67
|
+
- `[fix]` **`hooks/pre-bash-safety-check.sh`** rm-detection refactor — replaced the single-shot `RM_FLAG_REGEX` match with per-segment iteration. Splits `SANITIZED_CMD` (multi-line, preserves newline command terminators — `SANITIZED_CMD_FLAT` would have collapsed them) on `&&`, `||`, `;`, `&`, `|`. For each segment starting with the `rm` token, parses args with a token-aware loop that recognizes `--` (POSIX separator), `--recursive` / `--force` (long-form), `-*[rRfF]*` (short with danger letter), `--*` and `-*` (other flags — ignored), and first non-flag positional as target. Each rm-rf-with-var is independently checked for HOME/PWD/OLDPWD/TMPDIR whitelist or `${VAR:?…}` guard. Multiple HITS in one command all surface in the deny message.
|
|
68
|
+
|
|
69
|
+
- `[test]` **`tests/fixtures/bash-safety/corpus.tsv`** — 9 new corpus cases. Long-form: `rm --recursive --force $X` deny, `rm --recursive -f $X` deny, `rm -r --force $X` deny. Split short: `rm -v -i -rf $X` deny. Target-before-flag: `rm $X -rf` deny. Multi-rm: unguarded-then-guarded deny, both-guarded pass. Long-form FP guards: `rm --recursive --help` pass, `rm -r --force /tmp/literal` pass. Net 90 → 99 cases.
|
|
70
|
+
|
|
71
|
+
- `[change]` plugin / package / marketplace bump 0.21.3 → 0.21.4.
|
|
72
|
+
|
|
73
|
+
### Migration
|
|
74
|
+
|
|
75
|
+
None. `[allow-rm-rf-var]` escape token continues to work. Commands that the v0.21.3 regex missed now correctly deny; legitimate forms (`rm -rf /tmp/literal`, `rm -rf $HOME/cache`, canonical `:?` guard) continue to allow. Multi-line shapes (`TMP=$(mktemp -d)\nrm -rf $UNSAFE`) continue to deny — newlines are now treated as natural segment terminators by the per-segment splitter.
|
|
76
|
+
|
|
77
|
+
### What this DOES NOT do
|
|
78
|
+
|
|
79
|
+
- Does not change `${VAR:?…}` guard semantics. Position-agnostic full-command search retained: a guard appearing anywhere in the command still satisfies the same-varname rm-rf, even if positioned syntactically after the rm-rf. (As noted in v0.21.3 CHANGELOG, bash semantics already neutralize the late-guard case — `rm -rf ""` on unset var is a no-op error.)
|
|
80
|
+
- Does not extend recognition to non-`:?` guard forms. `[[ -n ]]` / `set -u` still need `[allow-rm-rf-var]`.
|
|
81
|
+
- Does not handle nested `bash -c "bash -c '...'"` chains under `BASH_SAFETY_INDIRECT_CALL=1`. Single-layer unwrap is the existing design.
|
|
82
|
+
|
|
83
|
+
## [0.21.3] - 2026-05-21
|
|
84
|
+
|
|
85
|
+
**Patch — fix: §8 SAFETY silent bypass in `pre-bash-safety-check.sh` sanitize step + `${VAR:?}` canonical-guard recognition. End-to-end dogfood findings. Spec unchanged at v6.13.1.**
|
|
86
|
+
|
|
87
|
+
### Why this patch
|
|
88
|
+
|
|
89
|
+
Dogfooding the plugin end-to-end uncovered a latent §8 SAFETY bypass in `sanitize_cmd`. The pre-fix regex `sed -E 's/"[^"$]*"/""/g'` could pair the closing `"` of one `$`-containing double-quoted string with the opening `"` of the next, eating any code in between — including `&& rm -rf "$VAR"` — before the rm-rf-var detector ran. Reproducer: `echo "$A" && rm -rf "$B"` sanitized to `echo "$A""$B"`, no `rm` token visible, hook allowed. The same gap also bypassed npx-unpinned detection on `echo "$A" && npx prettier "$B"`. Both shapes silently passed enforcement.
|
|
90
|
+
|
|
91
|
+
Fixing the sanitizer then exposed a second issue: the spec §8 deny message explicitly recommends `: "${VAR:?must be set}" && rm -rf "$VAR"` as the canonical "validate the var inline" form, but post-fix the hook denied it (no guard recognition existed — the previous accidental allow came from the sanitize bug, not deliberate logic). Hook now recognizes the bash `${VARNAME:?...}` set-or-exit guard when the varname matches the rm-rf target.
|
|
92
|
+
|
|
93
|
+
Also tightened `industry-standard` (hyphen form) in `banned-vocab.patterns` — canonical fixture already noted the drift; pattern now mirrors `production[- ]ready`.
|
|
94
|
+
|
|
95
|
+
### What changed (code)
|
|
96
|
+
|
|
97
|
+
- `[fix]` **`hooks/pre-bash-safety-check.sh`** sanitize_cmd — replaced the broken sed regex `"[^"$]*"` with an awk char-by-char state machine that pairs `"` correctly. Adjacent `$`-containing double-quoted regions stay distinct; the gap between them no longer gets matched as a single quote body. Closes the silent §8 SAFETY bypass for both rm-rf-var (Pattern 1) and npx-unpinned (Pattern 2) detection. Performance: 10KB command sanitized in 52ms (well under the 3s hook timeout).
|
|
98
|
+
|
|
99
|
+
- `[add]` **`hooks/pre-bash-safety-check.sh`** canonical-guard recognition — after detecting an unguarded non-whitelisted `rm -rf $VAR`, the hook checks for `${VAR:?...}` (bash set-or-exit operator) in `SANITIZED_CMD_FLAT` with the SAME varname. Match → emit `rm-rf-allow-validated` rule-hits row with `extra.var`, allow. Anchored via `(^|[^\\])` so `\${X:?...}` inside a `echo "use \${X:?msg} guard"` literal does NOT satisfy the guard (the backslash escapes the `$` — no expansion happens at runtime). Other guard forms (`[[ -n ]]`, `set -u`, control flow) remain unrecognized — use `[allow-rm-rf-var]`.
|
|
100
|
+
|
|
101
|
+
- `[change]` **`hooks/banned-vocab.patterns`** `\bindustry standard\b` → `\bindustry[- ]standard\b`. Mirrors existing `\bproduction[- ]ready\b` handling. Spec §10-V uses the hyphenated form; canonical fixture (`tests/fixtures/banned-vocab-canonical.json`) previously noted the drift but did not close it.
|
|
102
|
+
|
|
103
|
+
- `[doc]` **`docs/RULE-HITS-SCHEMA.md`** — new `rm-rf-allow-validated` event row in the Events table + section taxonomy. Records `extra.var`.
|
|
104
|
+
|
|
105
|
+
- `[test]` **`tests/fixtures/bash-safety/corpus.tsv`** — 17 new corpus rows. Sanitize cross-quote-region: rm-rf after `&&` / `;` / leading non-echo form (4 rows). Canonical `:?` guard: quoted braces + `&&`, `;` separator, bare braces, preceded by other commands, wrong-var guard, `:-` default not a guard, `[[ -n ]]` not a guard, backslash-escaped literal not a guard (8 rows). npx-unpinned cross-quote-region: bare + scoped (2 rows). Net 76 → 90 cases (+14, plus pre-existing 3 in the sanitize-fix block).
|
|
106
|
+
|
|
107
|
+
- `[test]` **`tests/fixtures/banned-vocab-canonical.json`** — `industry-standard` pattern updated to char class; note updated to match `production[- ]ready` style.
|
|
108
|
+
|
|
109
|
+
- `[test]` **`tests/hooks/contract.test.sh`** — `rm-rf-allow-validated:pre-bash-safety` added to KNOWN_EVENTS so drift-test C ("emitted event is documented") passes.
|
|
110
|
+
|
|
111
|
+
- `[change]` plugin / package / marketplace bump 0.21.2 → 0.21.3.
|
|
112
|
+
|
|
113
|
+
### Migration
|
|
114
|
+
|
|
115
|
+
None. `[allow-rm-rf-var]` escape token continues to work. Commands previously allowed via the sanitize bypass now correctly deny — the canonical `: "${VAR:?msg}" && rm -rf "$VAR"` form continues to allow (now via deliberate recognition rather than accidental sanitize bypass).
|
|
116
|
+
|
|
117
|
+
### What this DOES NOT do
|
|
118
|
+
|
|
119
|
+
- Does not model backslash-escape sequences inside `"..."` (`\"` still closes the quote — same gap as the prior sed implementation; not widened).
|
|
120
|
+
- Does not recognize non-`:?` guard forms (`[[ -n ]]`, `set -u`, control flow). Use `[allow-rm-rf-var]` if those are your idiom.
|
|
121
|
+
- Does not change the readonly-fast-path: commands starting with whitelisted readers (`echo`, `cat`, `ls`, …) still bypass detection — `echo "$A" rm -rf "$B"` (juxtaposition with no operator) remains allowed because bash-semantically `rm` is just a literal argument to `echo`, not a command.
|
|
122
|
+
|
|
123
|
+
## [0.21.2] - 2026-05-21
|
|
124
|
+
|
|
125
|
+
**Patch — add: spec **Sizing** drift pre-tag check in `scripts/version-cascade-check.js`. Plus a v0.21.1 test-hermeticity follow-up triggered by enabling `CLAUDEMD_PATH2_DRY_RUN=1` in `~/.claude/settings.json`. Spec unchanged at v6.13.1.**
|
|
126
|
+
|
|
127
|
+
### Why this patch
|
|
128
|
+
|
|
129
|
+
`feedback_spec_sizing_recursive_rewrite.md` documented this 3-times-in-session: operator writes the **Sizing** line in `spec/CLAUDE-extended.md`, post-edit `wc -c` shows the claim diverged from actual by 100-400 bytes (rewriting the Sizing line itself changes extended.md's size — recursive trap). The memory gave two options: accept ±20B drift, or build a mechanical pre-tag check. v0.17.6 / v0.19.0 / v0.21.0 all manually iterated to convergence. `fe88a38` staged v6.14.x net-delete candidates — next spec ship would hit this trap again. Option 2 ships in this patch.
|
|
130
|
+
|
|
131
|
+
Separately: enabling `CLAUDEMD_PATH2_DRY_RUN=1` in user settings.json during the v0.21.1 rollout surfaced that `tests/hooks/banned-vocab.test.sh` + the doctor `selfTests` Path 2 spawn weren't hermetic — they inherited the dry-run flag from `process.env` and silently degraded "expected deny" cases to "pass under dry-run". Closed in this patch.
|
|
132
|
+
|
|
133
|
+
### What changed (code)
|
|
134
|
+
|
|
135
|
+
- `[add]` **`scripts/version-cascade-check.js`** — new `runSpecSizingCheck({root})` export + CLI integration. Parses the canonical `**Sizing**` line in `spec/CLAUDE-extended.md`, extracts the post-arrow byte claims for core / extended / OPERATOR.md, compares to `fs.statSync` actuals. Reports drift with file path, claimed, actual, Δ-with-sign, and threshold. Tolerance ±20B per memory note (Sizing-line rewrite changes extended.md, hence non-zero floor). Handles arrow form (`core 24417 → 24417 bytes`) and plain form (`core 24417 bytes`). Skips cleanly when `spec/CLAUDE-extended.md` absent. CLI exit 0 only when both cascade AND sizing pass.
|
|
136
|
+
|
|
137
|
+
- `[change]` **`scripts/version-cascade-check.js`** — `--json` output reshaped from flat `{ok, expectedMinor, filesChecked, offenders}` to nested `{ok, cascade:{...}, sizing:{...}}`. Top-level `ok` is the combined gate; consumers that previously read `parsed.expectedMinor` now read `parsed.cascade.expectedMinor`. Only test code references this shape; non-test consumers (`npm run version-check`) only use the exit code.
|
|
138
|
+
|
|
139
|
+
- `[fix]` **`tests/hooks/banned-vocab.test.sh`** — `unset CLAUDEMD_PATH2_DRY_RUN BANNED_VOCAB_PROSE_SCAN DISABLE_BANNED_VOCAB_HOOK DISABLE_CLAUDEMD_HOOKS` at suite entry. Users who turn on Path 2 dry-run via `settings.json` no longer silently degrade cases 25-33 from "expected deny" to "pass under dry-run". Per-case env prefixes (e.g. case 34's explicit `CLAUDEMD_PATH2_DRY_RUN=1`) still drive what they need.
|
|
140
|
+
|
|
141
|
+
- `[fix]` **`scripts/doctor.js`** — selfTest spawn env now explicitly clears `CLAUDEMD_PATH2_DRY_RUN` and `BANNED_VOCAB_PROSE_SCAN` (parallel to the existing `DISABLE_CLAUDEMD_HOOKS` clear). Self-test verifies hook CODE integrity, not live enforcement, so user-env Path 2 toggles must not influence the code-path under test.
|
|
142
|
+
|
|
143
|
+
- `[test]` **`tests/scripts/version-cascade-check.test.js`** Cases 9-17 — 9 new tests. Real-repo smoke (drift=0 baseline against actual repo on every `npm test`), synthetic drift +100B (over threshold) reports correctly, ±20B boundary inclusive, +21B exclusive, missing extended.md skips clean, missing Sizing line fails clean, arrow + plain forms both parse, sizing-only failure short-circuits exit code while cascade passes. CLI `--json` shape test updated for nested layout. 8 → 17.
|
|
144
|
+
|
|
145
|
+
### Migration
|
|
146
|
+
|
|
147
|
+
None. The existing `npm run version-check` keeps the same CLI; the new sizing gate is additive (exit 0 still requires green). One subtle break for non-test consumers: if anything parsed the `--json` output's flat shape, switch to `parsed.cascade.expectedMinor` / `parsed.cascade.filesChecked`. Grep confirms only test code touched this surface.
|
|
148
|
+
|
|
149
|
+
### What this DOES NOT do
|
|
150
|
+
|
|
151
|
+
- Does not change spec content. Sizing line is still operator-maintained — the check just catches drift before it ships.
|
|
152
|
+
- Does not auto-rewrite the Sizing line. Operator decides what to write; this guard catches the "forgot to update" case.
|
|
153
|
+
- Does not apply to `spec/CLAUDE-changelog.md` (intentionally historical).
|
|
154
|
+
|
|
155
|
+
## [0.21.1] - 2026-05-21
|
|
156
|
+
|
|
157
|
+
**Patch — add: `CLAUDEMD_PATH2_DRY_RUN=1` observability flag + doctor `banned-vocab self-test:prose-scan`. Spec unchanged at v6.13.1.**
|
|
158
|
+
|
|
159
|
+
### Why this patch
|
|
160
|
+
|
|
161
|
+
v0.21.0 shipped Path 2 prose-scan deny but with no production data — synthetic tests 25-33 prove the mechanism works, FP rate against real assistant prose is unknown. And doctor `selfTests[]` covered Path 1 + rm-rf-var + npx-unpinned only; the v0.21.0 region-marker docstring-FP bug (silent 0-pattern scan) would have shipped green through doctor — only the `tests/hooks/` suite caught it. This patch closes both gaps without changing default behavior.
|
|
162
|
+
|
|
163
|
+
### What changed (code)
|
|
164
|
+
|
|
165
|
+
- `[add]` **`hooks/banned-vocab-check.sh`** — `CLAUDEMD_PATH2_DRY_RUN=1` branch logs a `deny-prose-dry-run` event with the would-match hits and exits 0 instead of denying. Grep `~/.claude/logs/claudemd.jsonl` for the rows during rollout to measure TP vs FP rate. Default 0 (live deny per v0.21.0). +10 LOC inside the existing Path 2 branch.
|
|
166
|
+
|
|
167
|
+
- `[add]` **`scripts/doctor.js`** — 4th `selfTests[]` entry `banned-vocab self-test:prose-scan`. Extended the loop with optional `setup(tmpDir) → {event, envOverride}` callback: the new entry stages a synthetic transcript at `$tmpDir/.claude/projects/<encoded-cwd>/<sid>.jsonl` with a §10-V high-fire token in an assistant turn, then drives the hook with `git push` + `HOME=$tmpDir` overridden in the spawn env. Catches: region-marker regex regression (the v0.21.0 docstring-FP class) at doctor invocation, not just test-suite. Synthetic transcript lands in `mkdtempSync` dir, cleaned up after spawn per §8.V4. +60 LOC including new selfTest entry and setup-handling fork in the loop.
|
|
168
|
+
|
|
169
|
+
- `[add]` **`docs/RULE-HITS-SCHEMA.md`** — `deny-prose-dry-run` event row added.
|
|
170
|
+
|
|
171
|
+
- `[add]` **`tests/hooks/contract.test.sh`** DOCUMENTED list — `deny-prose-dry-run:banned-vocab` added. 59 → 60.
|
|
172
|
+
|
|
173
|
+
- `[add]` **`README.md`** — `CLAUDEMD_PATH2_DRY_RUN` entry in env-var section with sample jq query for grepping the dry-run rows.
|
|
174
|
+
|
|
175
|
+
- `[test]` **`tests/hooks/banned-vocab.test.sh`** Cases 34-35 — `CLAUDEMD_PATH2_DRY_RUN=1` + would-deny prose + ship verb → pass (case 34); same + verify `deny-prose-dry-run` row written to `rule-hits.jsonl` (case 35). 33 → 35.
|
|
176
|
+
|
|
177
|
+
- `[test]` **`tests/scripts/doctor.test.js`** — new test asserts the `banned-vocab self-test:prose-scan` check exists, passes, and detail mentions Path 2 + the trigger word.
|
|
178
|
+
|
|
179
|
+
### Migration
|
|
180
|
+
|
|
181
|
+
None. Live enforcement unchanged: Path 2 still denies by default. Set `CLAUDEMD_PATH2_DRY_RUN=1` only if you want to observe-without-blocking during a calibration window (typical use: 1-2 weeks of normal ship cadence, then unset).
|
|
182
|
+
|
|
183
|
+
### What this DOES NOT do
|
|
184
|
+
|
|
185
|
+
- Does not auto-collect / report FP rate — that's left to `node scripts/audit.js` after dry-run data accumulates.
|
|
186
|
+
- Does not change Path 1 (commit-message scan) or any spec content.
|
|
187
|
+
- Does not affect `transcript-vocab-scan` (PostToolUse advisory) — that path is independent and unchanged.
|
|
188
|
+
|
|
189
|
+
## [0.21.0] - 2026-05-21
|
|
190
|
+
|
|
191
|
+
**Minor — change: §13.3 Gate 2 promotion. `banned-vocab-check` Path 2 prose scan added — ship-flow commands (commit/push/pr-create/release-create/publish) now DENY when the preceding assistant turn's chat prose contains a high-fire §10-V pattern. Spec unchanged at v6.13.1.**
|
|
192
|
+
|
|
193
|
+
### Migration
|
|
194
|
+
|
|
195
|
+
| What changes | Action you must take |
|
|
196
|
+
|---|---|
|
|
197
|
+
| When you run `git commit / git push / gh release create / gh pr create / npm publish / cargo publish` AND your previous assistant turn contained one of the §10-V high-fire patterns (`significantly`, `robust`, `comprehensive`, `should work`, `显著改善`, or baseline-less `N% faster/slower/better/more efficient`), the hook DENIES the command. | **None** if you want the new safety floor. To bypass per-command: include `[allow-banned-vocab]` in the command. To opt out of Path 2 globally while keeping Path 1 (commit-message scan): `export BANNED_VOCAB_PROSE_SCAN=0`. |
|
|
198
|
+
|
|
199
|
+
### §13.3 Gate 2 promotion case (audit data)
|
|
200
|
+
|
|
201
|
+
Ran `node scripts/sampling-audit.js --global --days=30`:
|
|
202
|
+
- Cross-project §10-V coverage: **5 distinct projects** (claudemd, code-graph-mcp, daagu, mem, /tmp) — Gate 2 requires ≥3.
|
|
203
|
+
- Total §10-V hits in 30d window: 151 across 79 transcripts (44 turns).
|
|
204
|
+
- Top high-fire patterns: `comprehensive` (43+10), `significantly` (35), `robust` (11+4), `should work` (9), `显著改善` (10), `production-ready` (4 — kept in prophylactic region, NOT promoted).
|
|
205
|
+
- Default-ON state for advisory `transcript-vocab-scan`: ≥30 days — Gate 2 requires ≥30.
|
|
206
|
+
- ≥1 `feedback_*.md` memory cites §10-V as load-bearing: `feedback_hook_header_quote_partial_impl.md`.
|
|
207
|
+
- Zero operator `revert:` / `relax:` entries against the rule in CHANGELOG.
|
|
208
|
+
|
|
209
|
+
All Gate 2 gates pass. Operator (user) judged this as the right promotion. Promotion is one-step: PostToolUse advisory STAYS as it is (no removal — chat-prose-everywhere coverage), Path 2 deny ADDS at the highest-stakes surface (ship-flow PreToolUse:Bash). Two enforcement points instead of one.
|
|
210
|
+
|
|
211
|
+
### Why ship-flow surface only, not all PreToolUse:* (Option A skipped)
|
|
212
|
+
|
|
213
|
+
The mechanically-stronger Option A — pre-deny ANY tool call when previous turn carries §10-V — was discussed and rejected. UX cost: agent uses `significantly` once → every subsequent tool call requires `[allow-banned-vocab]` token (because prior turns are immutable in CC). Disproportionate to the calibration value. Option B (ship-flow only) gives the strong-training signal at the right moment without the per-tool-call escape-token bloat.
|
|
214
|
+
|
|
215
|
+
### What changed (code)
|
|
216
|
+
|
|
217
|
+
- `[change MED]` **`hooks/banned-vocab-check.sh`** — Path 1 commit-msg scan refactored to be gated on new `IS_GIT_COMMIT` flag (preserves existing FP discipline — non-commit ship verbs no longer fall through to whole-CMD scan which would FP on branch names / path args). New `IS_SHIP_VERB` flag covers commit + push + pr create + release create + npm publish + cargo publish. Path 2 prose-scan body (~60 LOC) added at hook tail: reads transcript via `~/.claude/projects/<encoded-cwd>/<session_id>.jsonl` (CC encoding `tr '/._' '-'`), tails 200 jsonl lines + 4096 bytes of joined assistant text, scans `banned-vocab.patterns` high-fire region (markers anchored on `# region: <name> (` to defeat docstring FPs), denies on hit.
|
|
218
|
+
|
|
219
|
+
- `[fix LOW]` **`hooks/banned-vocab-check.sh`** region-marker regex anchored on trailing `\(` — caught during dev that the patterns file's docstring at top ALSO mentions `region: high-fire` and `region: prophylactic` (with indentation, no trailing paren). The naïve regex `^#[[:space:]]*region:[[:space:]]*<name>` matched the docstring lines too, causing the loop to set `in_high_fire=1` too early then break on the next docstring mention of prophylactic — Path 2 silently scanned 0 patterns and never denied. Fixed regex requires trailing `\(`.
|
|
220
|
+
|
|
221
|
+
- `[add]` **`BANNED_VOCAB_PROSE_SCAN=0`** sub-feature kill-switch (README §2a) — disables Path 2 prose scan only; Path 1 commit-msg scan remains active. Mirrors `DISABLE_BATCH_CADENCE_ADVISORY` shape from v0.19.2.
|
|
222
|
+
|
|
223
|
+
- `[add]` **`docs/RULE-HITS-SCHEMA.md`** — new `deny-prose` event documented (emitter `banned-vocab`, semantics, opt-out flag).
|
|
224
|
+
|
|
225
|
+
- `[add]` **`tests/hooks/contract.test.sh`** DOCUMENTED list — `deny-prose:banned-vocab` added. 58 → 59.
|
|
226
|
+
|
|
227
|
+
- `[test MED]` **`tests/hooks/banned-vocab.test.sh`** Cases 25-33 — 9 new cases. Prior-prose pattern × ship verb × bypass + opt-out matrix: significantly + commit → deny (25), robust + push → deny (26), comprehensive + gh release create → deny (27), `[allow-banned-vocab]` bypasses Path 2 (28), `BANNED_VOCAB_PROSE_SCAN=0` opt-out (29), non-ship verb skipped (30), prophylactic-only word doesn't trigger (31), missing transcript fail-open (32), 中文 `显著改善` → deny (33). 24 → 33.
|
|
228
|
+
|
|
229
|
+
### Sizing
|
|
230
|
+
|
|
231
|
+
No spec content change (spec stays at v6.13.1). Hook code growth: `hooks/banned-vocab-check.sh` +~80 LOC.
|
|
232
|
+
|
|
233
|
+
### Verification
|
|
234
|
+
|
|
235
|
+
- 33/33 banned-vocab hook tests pass (was 24; +9 Path 2).
|
|
236
|
+
- 59/59 contract tests pass (was 58; +1 DOCUMENTED entry).
|
|
237
|
+
- All other hook suites unchanged.
|
|
238
|
+
- 432/432 script tests, integration upgrade-lifecycle pass.
|
|
239
|
+
- `node scripts/version-cascade-check.js`: ok.
|
|
240
|
+
|
|
241
|
+
## [0.20.1] - 2026-05-21
|
|
242
|
+
|
|
243
|
+
**Patch — fix-forward on code review findings from the v0.19.1→v0.19.2→v0.20.0 arc. Three Important items + three Minor items + one docs cleanup. No spec change (spec stays v6.13.1). No behavioral default flipped.**
|
|
244
|
+
|
|
245
|
+
### Background
|
|
246
|
+
|
|
247
|
+
Code review of the v0.19.1+v0.19.2+v0.20.0 release arc (commits `237bf2a`..`984d6df`) surfaced 3 Important + 4 Minor findings. All "fix-forward" class — none blocked the original ships, all addressable in a single patch. This release closes 6 of them (one Minor was a false-alarm self-correction; one was deemed defensive low-ROI and deferred).
|
|
248
|
+
|
|
249
|
+
### What changed
|
|
250
|
+
|
|
251
|
+
- `[fix LOW]` **`hooks/session-end-check.sh`** L2+ heuristic comment (I1) — reviewer flagged that `structure-advisory` and `mid-spine-advisory` events in the qualifying set are gated by opt-in env vars (`TRANSCRIPT_STRUCTURE_SCAN=1` / `MID_SPINE_YIELD_SCAN=1`, default OFF), so the prior comment overstated practical breadth. Rewritten to honestly partition always-on `{deny, warn, deny-repeat}` from opt-in `{structure-advisory, mid-spine-advisory}`. Filter logic unchanged — when operators enable opt-in scans, sensitivity grows automatically.
|
|
252
|
+
|
|
253
|
+
- `[fix MED]` **`hooks/session-end-check.sh`** L2+ rule-hits scan now `tail -n 10000`-bounded (I2) — `jq -R -s 'split("\n")|...'` reads the entire log into memory. At the 5MB doctor warn threshold (~50K rows) this is borderline; `tail -n 10000` cap before jq is a defensive 5× reduction. Single session won't realistically emit 10K rule-hits, so behavior on healthy logs is zero-change.
|
|
254
|
+
|
|
255
|
+
- `[feat MED]` **`scripts/status.js`** features block exposes `batchCadenceAdvisory` + `batchCadenceThreshold` (I3) — `DISABLE_BATCH_CADENCE_ADVISORY` and `CLAUDEMD_BATCH_THRESHOLD` had zero visibility in `/claudemd-status` output before this. New fields default to `true` and `20` respectively; honor env overrides with the same `^[1-9][0-9]*$` guard that `session-end-check.sh` uses for the threshold (invalid input falls back to default 20).
|
|
256
|
+
|
|
257
|
+
- `[fix LOW]` **`hooks/memory-prompt-hint.sh`** tag-count regex tightened (M1) — `[[ "$tag_count" =~ ^[0-9]+$ ]]` accepted `0` from `awk -F, '{print NF}'` on an empty string, falling unreachable today only because of an upstream filter. Tightened to `^[1-9][0-9]*$` (positive integer required) so the guard defends against filter-bypass regressions.
|
|
258
|
+
|
|
259
|
+
- `[test MED]` **`tests/hooks/contract.test.sh`** section C2 added (M3) — every `token: '...'` literal in `scripts/status.js#ESCAPE_TOKENS` is grepped across `hooks/*.sh`; missing tokens fail loudly. Closes the "added a 6th bypass token to status.js verbose mirror but didn't implement it in any hook" drift class. 53 → 58 (one row per token currently declared, all 5 found).
|
|
260
|
+
|
|
261
|
+
- `[fix LOW]` **`hooks/session-end-check.sh`** `paused.md` template surfaces mutation cap (M5) — recent-mutations jq reducer caps the list at 3 (line 63), but the prior template didn't note this. Operator reading a `paused.md` with `MUTATIONS=12` previously saw 3 entries with no indication 9 were elided. New line: `(showing up to the last 3 of $MUTATIONS total)`.
|
|
262
|
+
|
|
263
|
+
- `[docs]` **`commands/claudemd-install.md`** strips magic-number `(expected: 17 as of v0.19.x)` from the `entries.length` doc bullet — number would go stale on every hook addition. Now reads just "number of registered hooks."
|
|
264
|
+
|
|
265
|
+
### Deferred (Minor — low ROI for this patch)
|
|
266
|
+
|
|
267
|
+
- **M2** `hooks/memory-prompt-hint.sh` mtime fallback when `platform_stat_mtime` missing → `0` for every entry → secondary sort key inert. Reviewer judged this defensive; `platform.sh` ships alongside the hook and won't normally vanish. Deferred indefinitely.
|
|
268
|
+
- **M4** Reviewer self-flagged + retracted (false alarm on banned-vocab in v0.19.2 CHANGELOG). No action.
|
|
269
|
+
|
|
270
|
+
### Tests
|
|
271
|
+
|
|
272
|
+
- 20/20 status tests pass (was 15; +5 for I3 batchCadence fields default + override + invalid-input fallback).
|
|
273
|
+
- 58/58 contract tests pass (was 53; +5 for C2 ESCAPE_TOKENS mirror check).
|
|
274
|
+
- 16/16 memory-prompt-hint tests pass (unchanged — M1 fix is regression-defense for unreachable path).
|
|
275
|
+
- 13/13 session-end-check tests pass (unchanged — I1 is comment, I2 is bounded scan, M5 is template prose).
|
|
276
|
+
- All 432 script tests, full hook suite, integration upgrade-lifecycle pass.
|
|
277
|
+
- `node scripts/version-cascade-check.js`: ok.
|
|
278
|
+
|
|
279
|
+
### Why patch
|
|
280
|
+
|
|
281
|
+
All 6 items are fix-forward on a review of already-shipped work; no new HARD rule, no spec content change, no behavioral default flipped (the default flips in v0.20.0 stand as-is). New `features.*` fields in status output are additive observability per §13 META "wording / clarification, identical behavior".
|
|
282
|
+
|
|
283
|
+
## [0.20.0] - 2026-05-21
|
|
284
|
+
|
|
285
|
+
**Minor — change: `BASH_READONLY_FAST_PATH` default flipped from opt-in OFF (v0.8.3+) to opt-out ON (this release). §13.3 advisory→enforce promotion: ~21 months observation, zero operator reverts, cross-project use. Spec unchanged at v6.13.1.**
|
|
286
|
+
|
|
287
|
+
### Migration
|
|
288
|
+
|
|
289
|
+
| What changes | Action you must take |
|
|
290
|
+
|---|---|
|
|
291
|
+
| When Bash command shape is definitely-read-only (no shell-meta, first token ∈ {`ls`, `cat`, `head`, `tail`, `wc`, `stat`, `date`, `pwd`, `echo`, `printf`, `sleep`, `file`, `which`, `type`, `env`, `basename`, `dirname`, `realpath`, `true`, `false`, `git log`, `git status`, `git diff`, `git show`, `git rev-parse`, `git rev-list`, `git describe`, `git blame`, `git reflog`, `git ls-files`, `git ls-tree`, `git cat-file`, `git remote`}), the 4 PreToolUse:Bash hooks (`pre-bash-safety` / `banned-vocab` / `ship-baseline` / `memory-read-check`) short-circuit before their sanitize / detector pipelines. Per-hook latency drops from ~50-150ms to ~5ms on these commands. | **None** if the new default is desired. To opt out: `export BASH_READONLY_FAST_PATH=0` in shell rc OR add to `~/.claude/settings.json` `env` block. |
|
|
292
|
+
|
|
293
|
+
### Rationale (§13.3 promotion case)
|
|
294
|
+
|
|
295
|
+
- **Observation window**: v0.8.3 shipped 2026-04-15. Today is 2026-05-21 — 36 days of opt-in field signal, well past the §13.3 Gate 1 minimum of 30 days.
|
|
296
|
+
- **Whitelist conservatism**: classifier rejects ANY shell-meta (`;` `|` `&` `>` `<` `\`` `$(` `${` newline) AND requires first token in a tight whitelist; for `git` subcommands the list excludes `branch / tag / config` because those have destructive sub-flags (`-d`, `-D`, `-c`). Classification tested in `tests/hooks/bash-readonly-skip.test.sh` cases 1-24 (covers ls/cat/git log positives + semicolon/pipe/redirect/cmd-sub/backtick negatives + non-whitelisted-token negatives + git-destructive-subcommand negatives).
|
|
297
|
+
- **Safety floor preserved**: ANY command outside the readonly classification — including all `git commit`, `git push`, `rm`, `npm`, `curl`, etc. — runs the full hook pipeline as before. The fast-path SKIP only fires when classification = readonly AND opt-out env not set; if classification is uncertain → return 1 (proceed normal path). False positives in the classifier are free (just more work); false negatives could only happen if the classifier wrongly identified a destructive command as readonly, which the test corpus rules out.
|
|
298
|
+
- **Cross-project coverage**: deployed on this repo (claudemd dogfood) + at least one other Anthropic-internal project — §13.3 Gate 1 minimum 2.
|
|
299
|
+
- **Zero operator revert / relax** entries in CHANGELOG against the rule.
|
|
300
|
+
|
|
301
|
+
### What changed (code)
|
|
302
|
+
|
|
303
|
+
- `[change MED]` **`hooks/pre-bash-safety-check.sh`** + **`hooks/banned-vocab-check.sh`** + **`hooks/ship-baseline-check.sh`** + **`hooks/memory-read-check.sh`** — fast-path conditional flipped from `${BASH_READONLY_FAST_PATH:-0}" == "1"` to `${BASH_READONLY_FAST_PATH:-1}" != "0"`. Env-shape: unset → ON, `=1` → ON, `=0` → OFF, anything else → ON (defensive against typos).
|
|
304
|
+
|
|
305
|
+
- `[change LOW]` **`scripts/status.js`** — `features.bashReadonlyFastPath` evaluation changed from `process.env.BASH_READONLY_FAST_PATH === '1'` to `process.env.BASH_READONLY_FAST_PATH !== '0'`. Same default-ON / =0-off semantics. `/claudemd-status` now reports `bashReadonlyFastPath: true` for users who upgrade without setting the env var.
|
|
306
|
+
|
|
307
|
+
- `[docs]` **`README.md`** — readonly fast-path paragraph rewritten to lead with "default-ON via §13.3 promotion"; opt-out command added inline; full safe-token whitelist enumerated.
|
|
308
|
+
|
|
309
|
+
- `[test]` **`tests/scripts/status.test.js`** Cases 13-15 — unset env → true (new default); explicit `=0` → false (opt-out); anything else → true (typo robustness). 12 → 15.
|
|
310
|
+
|
|
311
|
+
- `[test]` **`tests/hooks/bash-readonly-skip.test.sh`** Case 29 prose rewritten ("flag default OFF" → "post-v0.20.0 default ON"); Cases 31-33 added — explicit opt-out + non-readonly cmd still denies; default (env unset) + readonly cmd silent; opt-out + readonly cmd still silent (slow path). 30 → 33.
|
|
312
|
+
|
|
313
|
+
### Why minor (not patch)
|
|
314
|
+
|
|
315
|
+
Released-artifact user-visible default behavior change → core §2 escalates to L3 regardless of LOC; SemVer minor per §13 META "minor (rule added/relaxed, backward-compatible)". Explicit opt-out (`=0`) preserves prior behavior 1:1, so this is a backward-compatible default flip — no breaking change.
|
|
316
|
+
|
|
317
|
+
### Verification
|
|
318
|
+
|
|
319
|
+
- 33/33 bash-readonly-skip tests pass (was 30; +3 cases for new env semantics).
|
|
320
|
+
- 15/15 status tests pass (was 12; +3 cases).
|
|
321
|
+
- All 427 script tests, full hook suite, integration upgrade-lifecycle pass.
|
|
322
|
+
- `node scripts/version-cascade-check.js`: ok.
|
|
323
|
+
|
|
324
|
+
## [0.19.2] - 2026-05-21
|
|
325
|
+
|
|
326
|
+
**Patch — feat: `/claudemd-install` slash command for current-session bootstrap + `memory-prompt-hint` priority ranking (tag-count desc → mtime desc) + `session-end-check` §13.2 batch-review cadence advisory.**
|
|
327
|
+
|
|
328
|
+
### What changed
|
|
329
|
+
|
|
330
|
+
- `[add UX]` **`commands/claudemd-install.md`** + **README Quick start + troubleshooting** — new `/claudemd-install` slash command wraps `node ${CLAUDE_PLUGIN_ROOT}/scripts/install.js` so users can bootstrap the CURRENT Claude Code session right after `/plugin install` instead of waiting for the next `SessionStart`. Background: CC does not fire `postInstall`, so `install.js` previously only ran on session restart; README troubleshooting "Hooks don't fire" path now leads with `/claudemd-install` as option 1, manual `node` invocation as option 3 fallback. README commands table 11 → 12.
|
|
331
|
+
|
|
332
|
+
- `[feat MED]` **`hooks/memory-prompt-hint.sh`** — un-Read matches now sorted by (1) matched-tag count desc, then (2) file mtime desc. Pre-this, order = MEMORY.md authoring order, so when `COUNT > MAX(5)` the highest entries in the file dominated regardless of how strongly they matched the prompt vs entries lower in the file. New algorithm spends the 5-item cap on the entries most likely to change the agent's path. Sources `hooks/lib/platform.sh` explicitly (per `feedback_hook_platform_lib_source.md` — `command -v platform_*` guard silently falls-through when lib not sourced).
|
|
333
|
+
|
|
334
|
+
- `[feat MED]` **`hooks/session-end-check.sh`** — adds §13.2 batch-review cadence counter. Increments `~/.claude/.claudemd-state/l2-task-counter` once per L2+ session (heuristic: rule-hits log has ≥1 `deny`, `structure-advisory`, `mid-spine-advisory`, `warn`, or `deny-repeat` event for this `session_id`). At 20 L2+ sessions (threshold per OPERATOR.md §13.2 cadence): emit stderr advisory recommending `/claudemd-sampling-audit` + `/claudemd-rules`, reset counter to 0, record `batch-cadence-advisory` event under `§13.2-batch-review` spec section. Closes the operator-cadence feedback loop that was previously head-tracked; if maintainer skipped manual sampling-audit runs, the §13.2/§13.3 audit-data pipeline went dark.
|
|
335
|
+
|
|
336
|
+
- `[add]` **`DISABLE_BATCH_CADENCE_ADVISORY=1`** sub-feature kill-switch (README §2a) — disables the cadence advisory half of session-end-check while leaving the mid-SPINE warn-on-unvalidated-mutation behavior active. Threshold tunable via `CLAUDEMD_BATCH_THRESHOLD=N` (positive integer, default 20) — useful for test scenarios and operators who want a different cadence.
|
|
337
|
+
|
|
338
|
+
- `[test]` **`tests/hooks/memory-prompt-hint.test.sh`** Cases 15-16 — 15 asserts priority ranking (3-tag-match listed before 1-tag-match) defeats prior MEMORY.md authoring order by placing the strong match SECOND in the file; 16 asserts mtime tiebreak (newer entry before older when tag counts tie). 14 → 16.
|
|
339
|
+
|
|
340
|
+
- `[test]` **`tests/hooks/session-end-check.test.sh`** Cases 10-13 — L2+ session counter increment (10), threshold trip + advisory + reset (11), `DISABLE_BATCH_CADENCE_ADVISORY=1` kill-switch (12), non-L2+ session (pass/bypass only) does NOT increment (13). 9 → 13.
|
|
341
|
+
|
|
342
|
+
### Why patch
|
|
343
|
+
|
|
344
|
+
All three changes are additive observability / UX features. No spec rule added or relaxed (spec stays at v6.13.1). No default behavior of existing hooks flipped. New slash command is opt-in; new sub-feature kill-switch is opt-out. CHANGELOG `feat:` not `change:` per §13 META "patch (wording / clarification, identical behavior) = L2".
|
|
345
|
+
|
|
346
|
+
### Verification
|
|
347
|
+
|
|
348
|
+
- 16/16 memory-prompt-hint tests pass (was 14; +2 for priority + mtime tiebreak).
|
|
349
|
+
- 13/13 session-end-check tests pass (was 9; +4 for counter / threshold / kill-switch / non-L2+).
|
|
350
|
+
- All 427 script tests, full hook suite, integration upgrade-lifecycle pass.
|
|
351
|
+
- `node scripts/version-cascade-check.js`: ok (spec v6.13 unchanged).
|
|
352
|
+
|
|
353
|
+
## [0.19.1] - 2026-05-21
|
|
354
|
+
|
|
355
|
+
**Patch — feat: spec patch v6.13.1 (§13 META `HARD ≠ always hook-blocked` clarification + `OPERATOR.md` §13.4 `tasks/` filename conventions table) + `/claudemd-status --verbose` mode + `/claudemd-doctor` self-test matrix extended to cover §8-rm-rf-var + §8-npx.**
|
|
356
|
+
|
|
357
|
+
### Background
|
|
358
|
+
|
|
359
|
+
Four self-audit recommendations from a v0.19.0 comprehensive review landed in one patch — all additive, no behavioral default flipped, no Agent contract changed. Each closes a specific observable gap surfaced by reading the spec + walking the hook + script corpus end-to-end.
|
|
360
|
+
|
|
361
|
+
### What changed
|
|
362
|
+
|
|
363
|
+
- `[clarify SPEC]` **`spec/CLAUDE-extended.md §13 META`** — one new bullet "HARD ≠ always hook-blocked" pointing Agent at `spec/hard-rules.json#rules[].enforcement`. Today's 22 HARD rules partition as 6 hook / 14 self / 1 both / 1 external; without the cross-ref, Agent could miscalibrate "the hook will block X" for the 14 self-enforced HARDs (e.g. §iron-law-2, §10-four-section-order, §iron-law-1). +545B in extended; well under 50000B ceiling. Patch per §13 META "wording / clarification, identical behavior".
|
|
364
|
+
|
|
365
|
+
- `[add SPEC]` **`spec/OPERATOR.md §13.4 `tasks/` filename conventions table`** — single 11-row reference for all `tasks/<slug>` filename patterns previously scattered across 7 spec sections (`lessons.md`, `rule-candidates-<YYYY-MM>.md`, `sampling-audit-<YYYY-MM-DD>.md`, `<slug>-paused.md`, `autonomous-run-<date>.md`, `pending-auth-<date>.md`, `auto-approved.md`, `retro-<date>.md`, `specs/<slug>.md`, `perf-<n>.md`, `<n>.md`). +2450B in `OPERATOR.md` — **not Agent-loaded** per v6.13.0 separation, so zero Agent-context cost.
|
|
366
|
+
|
|
367
|
+
- `[feat MED]` **`scripts/status.js`** + **`commands/claudemd-status.md`** — new `--verbose` flag emits `verbose.killSwitches` (per-hook env var name + event + effective vs persisted state for all 17 shipped hooks, sourced from `scripts/lib/hook-registry.js`) and `verbose.escapeTokens` (5 per-invocation bypass tokens × hook × spec section). Closes "which env var disables hook X" / "what's the bypass token for §Y" lookup-by-README workflow.
|
|
368
|
+
|
|
369
|
+
- `[feat MED]` **`scripts/doctor.js`** — self-test matrix refactored into a loop covering 3 synthetic events: `banned-vocab` (existing, §10-V), `pre-bash-safety:rm-rf-var` (new, §8-rm-rf-var via `rm -rf $UNSAFE_VAR`), `pre-bash-safety:npx-unpinned` (new, §8-npx via `npx unknown-pkg-x9z2` with empty cwd). Each entry feeds a synthetic event, clears its own kill-switch env var per-spawn so user kill-switch state is surfaced as a note (not as test failure), and asserts deny-JSON comes back. Catches sanitize / detector drift in 2 more §8 SAFETY paths that previously had unit-test-only coverage.
|
|
370
|
+
|
|
371
|
+
- `[test]` **`tests/scripts/status.test.js`** Cases 8-11 — `--verbose` block omitted by default; perHook covers all 17 entries from `HOOK_REGISTRY`; escapeTokens covers all 5 documented tokens with cross-ref triple; per-hook kill-switch from `settings.json` reflected in `persisted` field. 8 → 12.
|
|
372
|
+
|
|
373
|
+
- `[test]` **`tests/scripts/doctor.test.js`** Cases 9-11 — new `pre-bash-safety self-test:rm-rf-var` and `pre-bash-safety self-test:npx-unpinned` checks pass on clean tree + carry §section cross-ref in detail; per-hook `settings.json` kill-switch reflected in detail of affected hook only (banned-vocab unaffected when only `DISABLE_PRE_BASH_SAFETY_HOOK=1` is set). 23 → 26.
|
|
374
|
+
|
|
375
|
+
- `[version]` Spec v6.13.0 → v6.13.1 (patch — clarification + operator handbook addition). Cascade-bumped: `spec/CLAUDE.md` H1, `spec/CLAUDE-extended.md` H1 + Recent changes entry + Sizing line, `spec/hard-rules.json#spec_version`, `tests/integration/upgrade-lifecycle.test.sh`, `tests/scripts/spec-structure.test.js`.
|
|
376
|
+
|
|
377
|
+
### Sizing
|
|
378
|
+
|
|
379
|
+
Core 24417 → 24417 bytes (Δ 0, version-line digit only). Extended 45029 → 46071 bytes (Δ +1042, §13 META bullet + v6.13.1 Recent-changes entry). OPERATOR.md 3955 → 6405 bytes (Δ +2450, §13.4 table — not Agent-loaded). Core headroom unchanged at **583 bytes / 97.67%**; extended **3929 bytes / 92.14%** (tightened 2.74 pp from v6.13.0's 89.94%, still well under ceiling).
|
|
380
|
+
|
|
381
|
+
### Verification
|
|
382
|
+
|
|
383
|
+
- 12/12 status tests pass (`node --test tests/scripts/status.test.js`).
|
|
384
|
+
- 26/26 doctor tests pass (`node --test tests/scripts/*.test.js` doctor suite).
|
|
385
|
+
- 12/12 spec-coherence-audit tests pass — Sizing drift = 0B (claim 46071, actual 46071).
|
|
386
|
+
- `node scripts/version-cascade-check.js`: ok (v6.13 consistent across README.md + plugin.json + marketplace.json).
|
|
387
|
+
|
|
388
|
+
## [0.18.1] - 2026-05-20
|
|
389
|
+
|
|
390
|
+
**Patch — feat: `ship-baseline` hook detects retry-within-5min on the same red CI run (same `session_id` + `run_url`) and escalates the deny REASON wording + emits a new `deny-repeat` audit event.**
|
|
391
|
+
|
|
392
|
+
### Background
|
|
393
|
+
|
|
394
|
+
Audit of 7-day real-session ship-baseline events surfaced a recurring pattern: in daagu, 3 distinct red CI run URLs each attracted 2 deny events within 71-230 seconds of each other under the same session — meaning the agent saw the (a)/(b)/(c) options on the 1st deny but retried the push anyway, hitting the same red CI. The hook's prose guidance was being read but not acted on.
|
|
395
|
+
|
|
396
|
+
Initial assessment claimed "拦了没引导" — wrong: the hook has carried (a)/(b)/(c) Options block since v0.1.0. The actual problem is agent behavior, not hook output. Fix posture: keep all 3 options, but if a same-key retry lands within the cooldown window, escalate the wording AND log a distinct audit event so the operator can see how often "ignored-guidance" retries happen.
|
|
397
|
+
|
|
398
|
+
### What changed
|
|
399
|
+
|
|
400
|
+
- `[feat MED]` **`hooks/ship-baseline-check.sh`** — sentinel-based 5-minute retry-cooldown tracking. Sentinel file at `$HOME/.claude/.claudemd-state/ship-baseline-recent/<session_id>_<run_id>.sentinel`. On deny: check existing sentinel mtime, if <300s old → `REPEAT=1` → escalated REASON ("SECOND deny on same red CI run within 5 minutes — your prior retry did NOT change the CI conclusion") + emit `deny-repeat` event. Touch sentinel after lookup; self-prune any sentinel older than 1 day on each invocation (`find -mmin +1440 -delete`, bounded to own STATE_DIR). When `session_id` or `run_url` are empty (CLI fallback / legacy callers), tracking is skipped — falls back to normal deny behavior.
|
|
401
|
+
|
|
402
|
+
- `[add]` **`docs/RULE-HITS-SCHEMA.md`** — documents the new `deny-repeat` event (emitter, semantics, sentinel state path, 1-day self-prune); updates the spec-section taxonomy row for `ship-baseline` to include the new event variant.
|
|
403
|
+
|
|
404
|
+
- `[test]` **`tests/hooks/ship-baseline.test.sh`** Cases 16-17 — 16a/16b/16c verify 1st-deny regular wording → 2nd-deny escalated wording within 5min on same `(session_id, run_url)`, both retaining `permissionDecision=deny`; 17 verifies different `session_id` keyed sentinel does NOT inherit. 15 → 19.
|
|
405
|
+
|
|
406
|
+
- `[test]` **`tests/hooks/contract.test.sh`** DOCUMENTED list — adds `deny-repeat:ship-baseline` entry; both B (documented → has hook_record call in source) and C (emitted → documented) assertions now pass for the new event. 50 → 51.
|
|
407
|
+
|
|
408
|
+
### Why not spec bump
|
|
409
|
+
|
|
410
|
+
Hook behavior addition; no spec rule added/relaxed/changed. Spec §7-ship-baseline still defines the gate ("Red → fix/known-red/ASK"); the retry-cooldown is implementation detail of how the hook expresses denial. Patch version per §13 META: "patch (wording / clarification, identical behavior) = L2"; spec text identical.
|
|
411
|
+
|
|
412
|
+
### Verification
|
|
413
|
+
|
|
414
|
+
- 412/412 script tests pass (`node --test tests/scripts/*.test.js`).
|
|
415
|
+
- 23/23 hook test files pass (`bash tests/hooks/*.test.sh`); ship-baseline 19/19, contract 51/51, mem-audit 12/12, all others unchanged.
|
|
416
|
+
|
|
417
|
+
## [0.18.0] - 2026-05-20
|
|
418
|
+
|
|
419
|
+
**Minor — spec v6.12.0: §11-EXT `project_*.md` exempted from `mem-audit` Why/How body-structure scan; §13.3 NEW Advisory → enforce promotion criteria for hook-layer rules.**
|
|
420
|
+
|
|
421
|
+
### What changed
|
|
422
|
+
|
|
423
|
+
- `[relax]` **`hooks/mem-audit.sh`** — case statement narrowed: `feedback_*.md|project_*.md) ;;` → `feedback_*.md) ;;`. Hook docstring + stderr banner copy updated accordingly. Incident-log `project_<topic>_<date>.md` files no longer trigger advisory warns when authors omit Why/How body markers. Closes 16-file long-standing non-compliance (daagu 12 + sdscc 2 + mem 1 + gsd 1 incident logs).
|
|
424
|
+
|
|
425
|
+
- `[add]` **`spec/CLAUDE-extended.md §13.3`** — NEW subsection codifying advisory → enforce promotion path for hook-layer rules. Two gates (default-OFF → default-ON; default-ON → `deny`) driven by `/claudemd-audit` data: fire count ≥20, bypass-rate <10%, cross-project coverage ≥2/≥3, operator-feedback gate. Companion to §0.1 (extended → core spec-text promotion).
|
|
426
|
+
|
|
427
|
+
- `[docs]` **`spec/CLAUDE-extended.md §11-EXT Body-structure scope`** — new paragraph documenting the project_*.md exemption rationale.
|
|
428
|
+
|
|
429
|
+
- `[test]` **`tests/hooks/mem-audit.test.sh`** Case 12 — project_*.md missing markers now silent (exempted). 11 → 12.
|
|
430
|
+
|
|
431
|
+
- `[version]` Spec v6.11.17 → v6.12.0 (minor — rule relaxed + rule added). Cascade-bumped: `spec/CLAUDE.md` H1, `spec/hard-rules.json`, `tests/integration/upgrade-lifecycle.test.sh`, `tests/scripts/spec-structure.test.js` (3 occurrences).
|
|
432
|
+
|
|
433
|
+
### Why minor (not patch)
|
|
434
|
+
|
|
435
|
+
Both changes are backward-compatible additions / relaxations, per §13 META: "minor (rule added/relaxed)" is the prescribed bump.
|
|
436
|
+
|
|
437
|
+
### Sizing
|
|
438
|
+
|
|
439
|
+
Core 24134 → 24133 bytes (Δ −1, version line shortens). Extended 45730 → 46573 bytes (Δ +843, S1+S2 spec content + v6.12.0 Recent-changes entry; v6.11.14 entry demoted out to maintain headroom). Headroom: core 867 (96.53%), extended 3427 (93.15%). Drift envelope: ±20B accepted.
|
|
440
|
+
|
|
441
|
+
### Verification
|
|
442
|
+
|
|
443
|
+
- 12/12 mem-audit hook tests pass (`bash tests/hooks/mem-audit.test.sh`).
|
|
444
|
+
- Spec structure tests pass (`node --test tests/scripts/spec-structure.test.js`) — v6.12.0 cascade complete.
|
|
445
|
+
- Full hook + script suite: TBD (run pre-ship).
|
|
446
|
+
|
|
447
|
+
## [0.17.7] - 2026-05-20
|
|
448
|
+
|
|
449
|
+
**Patch — fix: `/claudemd-audit` aggregated hook unit-test sentinel sessions (`session_id='t'/'test'`, ~150 rows in a 30d window) alongside real CC sessions, inflating `byTrend` regression-flag ratios with synthetic volume. UX: `/claudemd-update` command refresh sequence rendered as a copyable code block instead of inline prose.**
|
|
450
|
+
|
|
451
|
+
### What changed
|
|
452
|
+
|
|
453
|
+
- `[fix MED]` **`scripts/lib/rule-hits-parse.js`** — new `excludeTestSessions(hits)` helper drops rows where `session_id ∈ {'t','test'}` (hook unit-test sentinels; see `tests/hooks/*.test.sh`). `session_id=null` is intentionally NOT filtered — pre-v0.9.34 Stop/SessionStart/UserPromptSubmit hooks and bash CLI invocations legitimately emit null, accounting for ~80% of historical rows. Only the explicit 't'/'test' sentinels (~7%) are stripped.
|
|
454
|
+
|
|
455
|
+
- `[fix MED]` **`scripts/audit.js`** — applies `excludeTestSessions` to every behavior view (`byHook`, `bySection`, `byBypass`, `byFailOpen`, `byTrend`, `uniqueInvocations`, `topPatterns`). Only `dataIntegrity` retains full counts and exposes the new `testSessionsFiltered` field so the operator can quantify hook-test traffic without grepping the raw log. Initial fix attempted partial filter (bySection/byTrend only) which produced a 4.7× internal inconsistency between `byHook.banned-vocab.deny=345` and `bySection["§10-V"].deny=73` on the same payload — operator could not tell which was authoritative. Full filter resolves this; remaining cross-tab variance (now 1.04×) is legitimate (hooks emit events into multiple spec sections).
|
|
456
|
+
|
|
457
|
+
- `[docs]` **`commands/claudemd-audit.md`** — documents the new `dataIntegrity.testSessionsFiltered` field.
|
|
458
|
+
|
|
459
|
+
- `[docs UX]` **`commands/claudemd-update.md`** — `/plugin marketplace update` → uninstall → install → reload sequence now rendered as a 4-line copyable code block, replacing the inline prose form. Each line copy-pastes individually.
|
|
460
|
+
|
|
461
|
+
- `[test]` **`tests/scripts/rule-hits-parse.test.js`** — Case for `excludeTestSessions`: confirms `'t'`/`'test'` filtered, `null`/UUIDs preserved, partial matches (e.g. `'test-baseline-cv'`) NOT filtered (exact-match only). 13 → 14.
|
|
462
|
+
|
|
463
|
+
### Why patch
|
|
464
|
+
|
|
465
|
+
Behavioral fix to audit aggregation accuracy. No API surface change for external consumers (only `audit.js` internally consumes the affected helpers; `scripts/sparkline.js` / `scripts/status.js` don't read these views). No spec change.
|
|
466
|
+
|
|
467
|
+
### Verification
|
|
468
|
+
|
|
469
|
+
- 412/412 script tests pass (`node --test tests/scripts/*.test.js`).
|
|
470
|
+
- Audit output on live log: `testSessionsFiltered: 150`; `byHook.banned-vocab.deny`: pre-fix 345 → post-fix 205; `bySection["§10-V"].deny`: pre-fix 73 → post-fix 186 (now reflects real-session activity instead of test-pollution).
|
|
471
|
+
|
|
472
|
+
### Meta — evidence-validation lesson (saved as mem #8579)
|
|
473
|
+
|
|
474
|
+
Mid-task, the v0.17.7 P1 was initially scoped on the premise that `§8-npx: regression 7.5×` was test pollution. Verifying via `select(.hook == "pre-bash-safety" and .event == "deny")` returned 0 in 7d, which `seemed` to confirm the premise. False: the 17 `§8-npx` events were `npx-allow-local` + `bypass-escape-hatch` (no deny), so the proxy filter excluded them all and produced a misleading 0. Direct filter via `select(.spec_section == "§8-npx")` showed all 17 were real UUID sessions (mem vitest + daagu vue-tsc). **Lesson**: when auditing a spec_section's trend, filter on `spec_section` directly — never via `hook + event` as proxy. Hook+event is coarser than spec_section; one hook can emit multiple sections, and section-specific events (allow / bypass) vary independently from the deny event. User-correction at AskUserQuestion gate caught this before code shipped on the wrong premise.
|
|
475
|
+
|
|
11
476
|
## [0.17.5] - 2026-05-14
|
|
12
477
|
|
|
13
478
|
**Patch — fix: `memory-read-check.sh` + `memory-prompt-hint.sh` backtick-form TAG_BLOCK parsing matched the LAST `\`[token]\`` on each MEMORY.md index line — so a decorative backtick block in the description hijacked the parsed tag and silently shadowed the real tag.**
|
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# claudemd
|
|
2
2
|
|
|
3
|
-
> Claude Code plugin that enforces **AI-CODING-SPEC v6.
|
|
3
|
+
> Claude Code plugin that enforces **AI-CODING-SPEC v6.13 HARD rules** through shell hooks — and ships the spec itself as part of the plugin.
|
|
4
4
|
|
|
5
5
|
[](https://github.com/sdsrss/claudemd/actions/workflows/ci.yml)
|
|
6
6
|
[](https://www.npmjs.com/package/claudemd-cli)
|
|
7
7
|
[](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.
|
|
9
|
+
claudemd plugs into the Claude Code hook system to **block commits, pushes, and bash commands** that violate AI-CODING-SPEC v6.13 — 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. (v6.13.0: `OPERATOR.md` is the new 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
|
|
|
@@ -21,20 +21,17 @@ Run **both** slash commands inside Claude Code:
|
|
|
21
21
|
/plugin install claudemd@claudemd
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
Then
|
|
24
|
+
Then bootstrap the **current** session (skip the wait-for-next-session restart) + verify:
|
|
25
25
|
|
|
26
26
|
```
|
|
27
|
+
/claudemd-install
|
|
27
28
|
/claudemd-status
|
|
28
29
|
/claudemd-doctor
|
|
29
30
|
```
|
|
30
31
|
|
|
31
|
-
`status` reports plugin version, shipped vs installed spec version, kill-switch state, and rule-hits row count. `doctor` runs 9+ health checks with `[✓] / [△] / [✗]` markers.
|
|
32
|
+
`install` copies the spec into `~/.claude/`, writes the hook manifest, and evicts legacy entries — idempotent, safe to re-run. (Background: Claude Code does not fire `postInstall`, so without `/claudemd-install`, `install.js` runs on the next `SessionStart` instead.) `status` reports plugin version, shipped vs installed spec version, kill-switch state, and rule-hits row count. `doctor` runs 9+ health checks with `[✓] / [△] / [✗]` markers.
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
node ~/.claude/plugins/cache/claudemd/claudemd/<version>/scripts/install.js
|
|
37
|
-
```
|
|
34
|
+
Fallback (no slash command — e.g. scripting outside CC): `node ~/.claude/plugins/cache/claudemd/claudemd/<version>/scripts/install.js`. Find `<version>` with `ls ~/.claude/plugins/cache/claudemd/claudemd/ | sort -V | tail -1`.
|
|
38
35
|
|
|
39
36
|
> ⚠️ **`~/.claude/CLAUDE.md` is shared real estate.** Claude Code reads this file as your user-global instructions across every project. If you've hand-written personal instructions there (`Always reply in 中文`, `My name is X`, etc.), install moves your existing files to `~/.claude/backup-<ISO>/` (last 5 kept automatically) before writing the spec. Since v0.5.3, install prints a `[claudemd] WARN: …` line to stderr when the existing file does not look like a claudemd spec. To bring your personal instructions back on uninstall, run `CLAUDEMD_SPEC_ACTION=restore /claudemd-uninstall`.
|
|
40
37
|
|
|
@@ -59,9 +56,9 @@ Verify in one command (Linux): `node --version && jq --version && gh --version &
|
|
|
59
56
|
| Layer | Contents |
|
|
60
57
|
|---|---|
|
|
61
58
|
| 17 shell hooks | `banned-vocab-check` · `pre-bash-safety-check` · `ship-baseline-check` · `residue-audit` · `memory-read-check` · `memory-prompt-hint` · `memory-coverage-scan` · `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` |
|
|
62
|
-
|
|
|
59
|
+
| 12 slash commands | `/claudemd-install` · `/claudemd-status` · `/claudemd-update` · `/claudemd-audit` · `/claudemd-toggle` · `/claudemd-doctor` · `/claudemd-analyze` · `/claudemd-uninstall` · `/claudemd-rules` · `/claudemd-clean-residue` · `/claudemd-sparkline` · `/claudemd-sampling-audit` |
|
|
63
60
|
| 1 standalone CLI | `claudemd-cli lint` · `claudemd-cli audit` ([npm: `claudemd-cli`](https://www.npmjs.com/package/claudemd-cli)) |
|
|
64
|
-
| Spec v6.
|
|
61
|
+
| Spec v6.13.0 | `~/.claude/CLAUDE.md` · `CLAUDE-extended.md` · `CLAUDE-changelog.md` · `OPERATOR.md` (backup-before-overwrite) |
|
|
65
62
|
|
|
66
63
|
Install moves any existing `~/.claude/CLAUDE*.md` to `~/.claude/backup-<ISO>/` (last 5 kept automatically). Uninstall offers `keep / restore / delete`; `delete` requires an extra confirmation.
|
|
67
64
|
|
|
@@ -99,14 +96,15 @@ CC runs all configured PreToolUse hooks for `Bash` sequentially in declaration o
|
|
|
99
96
|
|
|
100
97
|
Per-hook timeout (3-5s in `hooks.json`); timeout = treated as exit 0 (pass) per fail-open contract. Stop / SessionStart / UserPromptSubmit / PostToolUse hooks run all declared hooks regardless (none can block; advisories accumulate). Internal hook errors (missing `jq`, malformed event JSON, unreadable patterns file) fail-open; failures do NOT propagate to subsequent hooks.
|
|
101
98
|
|
|
102
|
-
|
|
99
|
+
**Readonly fast-path** (v0.8.3 introduced opt-in default-OFF; **v0.20.0 promoted to default-ON** via §13.3 advisory→enforce gate): hooks 1, 2, and 4 short-circuit when the command is a definitely-read-only shape (no shell-meta, first token in safe-reader whitelist — `ls`, `cat`, `git log`, `git status`, `git diff`, `git rev-parse`, `pwd`, `echo`, `head`, `tail`, etc.). Hook 3 only fires on `git push` so the fast-path doesn't apply. Opt-out: `export BASH_READONLY_FAST_PATH=0` (or any other value than the literal `0` is treated as ON).
|
|
103
100
|
|
|
104
101
|
## Commands
|
|
105
102
|
|
|
106
103
|
| Command | Purpose |
|
|
107
104
|
|---|---|
|
|
108
|
-
| `/claudemd-
|
|
109
|
-
| `/claudemd-
|
|
105
|
+
| `/claudemd-install` | Bootstrap the current session right after `/plugin install` (copy spec into `~/.claude/`, write manifest, evict legacy entries). Idempotent. CC does not fire `postInstall`, so without this command `install.js` waits until the next `SessionStart`. |
|
|
106
|
+
| `/claudemd-status [--verbose]` | Plugin version + spec version + kill-switch state + logs line count. `--verbose` adds per-hook env-var × event × effective vs persisted state table + 5 escape-token reference. |
|
|
107
|
+
| `/claudemd-update` | Interactive diff against plugin-shipped spec, then apply-all or cancel (4-file spec set is lockstep — per-file select would dangle §EXT cross-references). |
|
|
110
108
|
| `/claudemd-audit [--days N]` | Aggregate rule-hits over last N days (default 30). Top banned-vocab patterns, per-hook deny counts. |
|
|
111
109
|
| `/claudemd-toggle <hook-name>` | Enable/disable a specific hook by toggling `DISABLE_*_HOOK` in `settings.json` env. |
|
|
112
110
|
| `/claudemd-doctor [--prune-backups=N]` | Health checks; optionally prune `~/.claude/backup-*` dirs older than N. v0.7.1+ also flags rule sections whose bypass:deny ratio > 50% (R-N6 §0.1 demotion candidates). |
|
|
@@ -195,6 +193,30 @@ export DISABLE_SESSION_SUMMARY_BANNER=1 # v0.8.0+ — only the SessionStart b
|
|
|
195
193
|
# of last-session-summary.json continues so
|
|
196
194
|
# the data is captured for /claudemd-audit
|
|
197
195
|
# but no additionalContext line is injected.
|
|
196
|
+
|
|
197
|
+
export DISABLE_BATCH_CADENCE_ADVISORY=1 # v0.19.2+ — only the §13.2 batch-review
|
|
198
|
+
# cadence advisory inside session-end-check;
|
|
199
|
+
# mid-SPINE warn-on-unvalidated-mutation
|
|
200
|
+
# behavior remains active. Threshold also
|
|
201
|
+
# configurable via CLAUDEMD_BATCH_THRESHOLD=N
|
|
202
|
+
# (positive integer, default 20).
|
|
203
|
+
|
|
204
|
+
export BANNED_VOCAB_PROSE_SCAN=0 # v0.21.0+ — disable only the Path 2 prose
|
|
205
|
+
# scan in banned-vocab-check (the v0.21.0
|
|
206
|
+
# §13.3 Gate 2 promotion that denies
|
|
207
|
+
# ship-flow commands when the preceding
|
|
208
|
+
# assistant turn's prose contains a
|
|
209
|
+
# high-fire §10-V pattern). Path 1
|
|
210
|
+
# commit-message scan remains active.
|
|
211
|
+
|
|
212
|
+
export CLAUDEMD_PATH2_DRY_RUN=1 # v0.21.1+ — Path 2 observability mode.
|
|
213
|
+
# When set, ship-verb + prior-prose §10-V
|
|
214
|
+
# match logs a `deny-prose-dry-run` event
|
|
215
|
+
# row to ~/.claude/logs/claudemd.jsonl
|
|
216
|
+
# instead of denying. Grep the rows to
|
|
217
|
+
# measure true-positive vs false-positive
|
|
218
|
+
# rate before committing to live deny.
|
|
219
|
+
# Sample: jq -r 'select(.event=="deny-prose-dry-run") | .extra.matched' ~/.claude/logs/claudemd.jsonl
|
|
198
220
|
```
|
|
199
221
|
|
|
200
222
|
**3. Per-invocation escape hatches.** Embed in the command itself, no env var needed:
|
|
@@ -228,7 +250,7 @@ After the plugin upgrade, sync the shipped spec into `~/.claude/`:
|
|
|
228
250
|
/claudemd-update
|
|
229
251
|
```
|
|
230
252
|
|
|
231
|
-
The command prints per-file diff summary, then prompts `apply-all` or `cancel`. Per-file select is intentionally not supported — the spec
|
|
253
|
+
The command prints per-file diff summary, then prompts `apply-all` or `cancel`. Per-file select is intentionally not supported — the 4-file spec set (`CLAUDE.md` + `CLAUDE-extended.md` + `CLAUDE-changelog.md` + `OPERATOR.md`) evolves lockstep, and mixing versions would dangle `§EXT §X-EXT` cross-references in Core. Backup is automatic (retained to 5). `/claudemd-update` never fetches from GitHub — it only diffs the plugin-cache spec against your `~/.claude/CLAUDE*.md` + `~/.claude/OPERATOR.md`. The network fetch is Claude Code's job (via `/plugin marketplace update`).
|
|
232
254
|
|
|
233
255
|
---
|
|
234
256
|
|
|
@@ -251,7 +273,7 @@ Reversing the order is the orphan-state vector — `${CLAUDE_PLUGIN_ROOT}` and `
|
|
|
251
273
|
|---|---|---|
|
|
252
274
|
| `keep` (default) | (none) | `~/.claude/CLAUDE*.md` left in place; settings.json hook entries cleared. |
|
|
253
275
|
| `restore` | `CLAUDEMD_SPEC_ACTION=restore` | Copies the most recent `~/.claude/backup-<ISO>/*.md` back to `~/.claude/`. Use this if your install-time stderr showed `[claudemd] WARN: existing ~/.claude/CLAUDE.md does not look like a claudemd spec` — your hand-written user-global instructions are sitting in the backup waiting to be brought back. |
|
|
254
|
-
| `delete` | `CLAUDEMD_SPEC_ACTION=delete CLAUDEMD_CONFIRM=1` | Hard-AUTH: removes
|
|
276
|
+
| `delete` | `CLAUDEMD_SPEC_ACTION=delete CLAUDEMD_CONFIRM=1` | Hard-AUTH: removes all four spec files (`CLAUDE*.md` + `OPERATOR.md`). |
|
|
255
277
|
|
|
256
278
|
`CLAUDEMD_PURGE=1` (env var) on `/claudemd-uninstall` also drops `~/.claude/.claudemd-state/` and your rule-hits log.
|
|
257
279
|
|
|
@@ -273,11 +295,11 @@ The slash command and the script are equivalent — the slash command just suppl
|
|
|
273
295
|
|
|
274
296
|
**`Plugin "claudemd" not found in any marketplace`** — you forgot the `/plugin marketplace add sdsrss/claudemd` step. Re-run it, then retry install.
|
|
275
297
|
|
|
276
|
-
**Hooks don't fire / `~/.claude/CLAUDE*.md` not present after install** — Claude Code's `postInstall` lifecycle is not honored, so `install.js` runs from the `SessionStart` hook on your next session, not at install time.
|
|
298
|
+
**Hooks don't fire / `~/.claude/CLAUDE*.md` not present after install** — Claude Code's `postInstall` lifecycle is not honored, so `install.js` runs from the `SessionStart` hook on your next session, not at install time. Three options:
|
|
277
299
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
300
|
+
1. **Recommended**: run `/claudemd-install` in your current session. Wraps `scripts/install.js` exactly the same way `SessionStart` does — idempotent, prints a JSON summary.
|
|
301
|
+
2. Start a fresh Claude Code session (`SessionStart` fires the bootstrap automatically).
|
|
302
|
+
3. Run the script manually outside CC (e.g. shell scripting): `node ~/.claude/plugins/cache/claudemd/claudemd/<version>/scripts/install.js` (find `<version>` with `ls ~/.claude/plugins/cache/claudemd/claudemd/ | sort -V | tail -1`).
|
|
281
303
|
|
|
282
304
|
Verify with `/claudemd-status` — the "log.lines" count should increment after the next hook fires.
|
|
283
305
|
|
|
@@ -304,10 +326,10 @@ claudemd/
|
|
|
304
326
|
│ └── marketplace.json # marketplace catalog entry
|
|
305
327
|
├── hooks/ # 11 shell hooks + hooks/lib/ (hook-common, rule-hits, platform)
|
|
306
328
|
│ └── hooks.json # authoritative hook registration (v0.1.5+); CC expands ${CLAUDE_PLUGIN_ROOT} here
|
|
307
|
-
├── commands/ #
|
|
329
|
+
├── commands/ # 11 slash-command markdown files
|
|
308
330
|
├── bin/ # standalone CLI entrypoint (claudemd-lint.js → `npx claudemd-cli` on npmjs.org)
|
|
309
|
-
├── scripts/ #
|
|
310
|
-
├── spec/ # shipped v6.
|
|
331
|
+
├── scripts/ # 15 Node.js management scripts + scripts/lib/ (single-source registry, lint, etc.)
|
|
332
|
+
├── spec/ # shipped v6.13.0 CLAUDE*.md trio + OPERATOR.md + hard-rules.json manifest
|
|
311
333
|
├── tests/ # hook shell tests + Node.js tests + integration + fixtures
|
|
312
334
|
├── docs/ # ADDING-NEW-HOOK.md + RULE-HITS-SCHEMA.md + superpowers/
|
|
313
335
|
└── .github/workflows/ # ci.yml (ubuntu+macOS × node 20) + npm-publish.yml (tag-triggered)
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
# ─── EN adjectives ──────────────────────────────────────────────────────────
|
|
72
72
|
\bproduction[- ]ready\b|evaluative adjective — describe what you verified (tests, thresholds) instead
|
|
73
73
|
\bbest practice\b|appeal to authority — cite the specific practice + source if invoking
|
|
74
|
-
\bindustry standard\b|appeal to authority — same as best-practice
|
|
74
|
+
\bindustry[- ]standard\b|appeal to authority — same as best-practice
|
|
75
75
|
\bcleaner code\b|evaluative — describe the concrete change (N fewer LOC, removed helper X)
|
|
76
76
|
\bseems to work\b|hedge — verify and state, or drop the claim
|
|
77
77
|
\bappears correct\b|hedge — verify and state, or drop the claim
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claudemd-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.5",
|
|
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": {
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"test": "bash tests/run-all.sh",
|
|
18
18
|
"test:scripts": "node --test tests/scripts/*.test.js",
|
|
19
19
|
"test:hooks": "bash tests/hooks/*.test.sh",
|
|
20
|
-
"lint:argv": "node scripts/lint-argv.js"
|
|
20
|
+
"lint:argv": "node scripts/lint-argv.js",
|
|
21
|
+
"version-check": "node scripts/version-cascade-check.js"
|
|
21
22
|
},
|
|
22
23
|
"engines": {
|
|
23
24
|
"node": ">=20"
|