claudemd-cli 0.9.5 → 0.9.8

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +83 -0
  2. package/README.md +116 -95
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -8,6 +8,89 @@ 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.9.8] - 2026-05-10
12
+
13
+ **Patch — v0.9.7 CI hotfix.** v0.9.7 shipped `hooks/mem-audit.sh` drift detection + new `tests/hooks/mem-audit.test.sh` cases 9-11, but the test-file edit was not staged in the v0.9.7 commit. Result: v0.9.7 CI failed at `Run test suite` step on both ubuntu-latest and macos-latest because old case 9 expected `silent` from a hook that now correctly emits an `index_orphan` warn. Hook behavior in v0.9.7 is correct; only the test sync was missing.
14
+
15
+ ### Fixed
16
+
17
+ - `[fix]` **`tests/hooks/mem-audit.test.sh`** — sync test file with v0.9.7 mem-audit.sh drift detection. Case 9 reused for `index_orphan` (MEMORY.md links file that doesn't exist), case 10 added for `file_orphan` (memory file present, no MEMORY.md link), case 11 added for aligned-no-drift baseline. Test count 9/9 → 11/11. Pre-fix on v0.9.7 CI: `FAIL: 9 (stderr='[claudemd] §11-EXT mem-audit: 1 MEMORY.md drift entries...')` — the failure was the test asserting old behavior, not a hook regression.
18
+
19
+ ### Versioning
20
+
21
+ - `package.json` 0.9.7 → **0.9.8** (npm).
22
+ - `.claude-plugin/plugin.json` 0.9.7 → **0.9.8**.
23
+ - `.claude-plugin/marketplace.json` two version fields 0.9.7 → **0.9.8**.
24
+ - Spec headers unchanged (v6.11.7 still current); no `spec/CLAUDE-changelog.md` entry — plugin-only patch.
25
+
26
+ ### Validation
27
+
28
+ - `bash tests/hooks/mem-audit.test.sh` → 11/11 passed locally pre-commit.
29
+ - `node --test tests/scripts/*.test.js` → 217 passed, 0 failed.
30
+ - `bash tests/run-all.sh` → all suites passed.
31
+
32
+ ### Lesson recorded
33
+
34
+ The v0.9.7 atomic-ship sequence used `git add <files...>` with an explicit list and forgot `tests/hooks/mem-audit.test.sh`. Local `bash tests/run-all.sh` passed because it ran against the working-tree state, not the staged state. Future ships covering hook + test edits should `git diff --cached` before commit, or use `git add -u` after touching tracked files. Recorded inline here rather than as a separate memory entry — the test fixture format-drift memory (`feedback_test_fixture_format_drift`) already covers the test-real-file-divergence pattern; this is its sibling failure mode (test edit forgotten, not test fixture drift).
35
+
36
+ ## [0.9.7] - 2026-05-10
37
+
38
+ **Patch — P1.3 + P2 batch from audit follow-up.** Spec v6.11.7 unchanged. Three additive changes; no behavior change for users on the green path.
39
+
40
+ ### Added
41
+
42
+ - `[doc]` **README.md "Execution order (PreToolUse:Bash)" subsection** — closes the audit observation that the 4 PreToolUse:Bash hook execution order was undocumented. Lists the 4 hooks in `hooks/hooks.json` declaration order with their spec section, fail-stop semantics (first deny stops the rest), per-hook timeout fail-open contract, and the opt-in `BASH_READONLY_FAST_PATH=1` short-circuit. Local-only `docs/HOOK-PROTOCOL.md` (gitignored per `.gitignore` policy) carries the same content for internal reference.
43
+ - `[feat]` **`hooks/mem-audit.sh` MEMORY.md ↔ files drift detection** — adds two reverse-direction checks to the existing Why/How marker scan: (a) `index_orphan` — MEMORY.md link target file doesn't exist (stale index), (b) `file_orphan` — memory file present but no MEMORY.md link points to it. Both advisory; reported alongside Why/How marker counts on the same 24 h sentinel debounce. Independent of `claude-mem-lite` per the existing hook contract — pure CC built-in memory scan. Test cases 9-11 cover index_orphan, file_orphan, and aligned-no-drift; 11/11 cases pass (was 9/9; case 9 reused for new behavior, cases 10+11 new).
44
+ - `[feat]` **`scripts/perf-baseline.sh`** — measures hook chain overhead on 6 representative bash commands (`ls` / `git_log` / `git_status` / `git_commit_noop` / `echo` / `cat_head`). Median of N=10 runs (configurable) with hooks ON vs hooks OFF (`DISABLE_CLAUDEMD_HOOKS=1`). Initial run on this repo (Linux 6.17, N=3): `ls 1→48 ms (delta 47)`, `git_log 3→50 ms (delta 47)`, `git_status 8→55 ms (delta 47)`, `git_commit_noop 11→93 ms (delta 82)` — `git_commit_noop` is the only command where banned-vocab actually scans, hence the higher delta. Pre-this-script estimate was "200-400 ms" (5月9日 audit, never measured). Caveat: script measures direct stdin invocation, NOT CC harness round-trip — real overhead is above + per-event JSON construction + timeout enforcement.
45
+ - `[doc]` **`docs/cross-project-pilot.md`** — checklist framework for adopting claudemd in an unrelated project. Closes the audit observation that claudemd was self-tested only. 6-axis observation table, pilot-duration guidance (≥2 weeks), 3 exit-outcome criteria, empty pilot-results table for future fills. Added to `.gitignore` exception list so the doc ships with the repo.
46
+
47
+ ### Versioning
48
+
49
+ - `package.json` 0.9.6 → **0.9.7** (npm).
50
+ - `.claude-plugin/plugin.json` 0.9.6 → **0.9.7**.
51
+ - `.claude-plugin/marketplace.json` two version fields 0.9.6 → **0.9.7**.
52
+ - Spec headers unchanged (v6.11.7 still current); no `spec/CLAUDE-changelog.md` entry — plugin-only patch.
53
+
54
+ ### Validation
55
+
56
+ - `node --test tests/scripts/*.test.js` → 217 passed, 0 failed.
57
+ - `bash tests/hooks/mem-audit.test.sh` → 11/11 passed (was 9/9; +2 drift cases).
58
+ - `bash tests/run-all.sh` → all suites passed (upgrade-lifecycle final check).
59
+ - `bash scripts/perf-baseline.sh --runs 3` → 6 commands measured; output format matches the column layout documented in the script header.
60
+
61
+ ### Audit follow-up status (after v0.9.6 + v0.9.7)
62
+
63
+ P0 done (v0.9.6: hard-rules-audit error context + tests; session-start tag semver gate). P1 partially done — P1.3 doc shipped here; P1.1 spec net-delete deferred (core 442 chars headroom; passive trigger per §13.1, not active); P1.2 agent observation extension deferred (spec-pattern-drift would require 3-file lockstep spec change + transcript scanner FP risk on every PostToolUse). P2 done (this release: mem drift detector, perf baseline, cross-project pilot framework). The unresolved audit signal is P1.2 — agent self-rule observation needs better detection logic before adding patterns; logged for next iteration.
64
+
65
+ ## [0.9.6] - 2026-05-10
66
+
67
+ **Patch — defensive hardening + test coverage gap closure.** Spec unchanged (still v6.11.7); plugin-only patch. Triggered by an audit pass that found `scripts/hard-rules-audit.js` had zero test coverage and no error context on `JSON.parse` failure, plus `hooks/session-start-check.sh` embedded the upstream tag in a banner without a strict semver gate. Three small changes; no behavior change for users on the green path.
68
+
69
+ ### Fixed
70
+
71
+ - `[fix]` **`hard-rules-audit.js` opaque error on broken / missing `spec/hard-rules.json`** — pre-fix: `JSON.parse(fs.readFileSync(manifestPath, 'utf8'))` at L21 surfaced bare `ENOENT` / `SyntaxError` with no path context, leaving the operator to guess which file broke. Post-fix: try/catch wraps the parse, throws `hard-rules-audit: failed to load <path>: <reason>`; an additional shape-check (`Array.isArray(manifest.rules)`) throws `hard-rules-audit: <path> missing required 'rules' array` when the JSON is valid but the manifest body is wrong.
72
+ - `[fix]` **`session-start-check.sh` lacked semver gate before embedding `remote_tag` in `additionalContext`** — pre-fix: `jq --arg new "$remote_tag"` already safe-quotes the value, so JSON injection wasn't reachable, but a malformed remote tag (newline-injected, exotic glyphs from a compromised mirror) would still produce a confusing upgrade banner. Post-fix: a `[[ "$remote_tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] || return 0` gate after `head -1 | awk | sed`. Defense-in-depth, not a fix to a live exploit.
73
+
74
+ ### Added
75
+
76
+ - `[test]` **`tests/scripts/hard-rules-audit.test.js` — 6 cases, 0 → 6 coverage**: (1) byte-exact production fixture (reads real `spec/hard-rules.json` per project memory `feedback_test_fixture_format_drift` — locks against test/impl drift), (2) missing manifest path → error names the file, (3) malformed JSON → error names the file, (4) JSON valid but `rules` array missing → error explains what's missing, (5) cross-ref of `rule_hits_section` to live log rows (verifies `§10-V` deny rows reach `§10-specificity.hits.deny`), (6) `demoteCandidates` correctly excludes self-enforced rules (avoids false-positive `§iron-law-2` demotion suggestion). All 6 pass; total Node test count 211 → 217.
77
+
78
+ ### Versioning
79
+
80
+ - `package.json` 0.9.5 → **0.9.6** (npm).
81
+ - `.claude-plugin/plugin.json` 0.9.5 → **0.9.6**.
82
+ - `.claude-plugin/marketplace.json` two version fields 0.9.5 → **0.9.6**.
83
+ - Spec headers unchanged (v6.11.7 still current); no `spec/CLAUDE-changelog.md` entry — plugin-only patch.
84
+
85
+ ### Validation
86
+
87
+ - `node --test tests/scripts/*.test.js` → 217 passed (211 prior + 6 new), 0 failed.
88
+ - `bash hooks/session-start-check.sh < /dev/null` → exit 0; banner emitted as expected; semver gate not exercised on green path (operator-side smoke).
89
+ - Verification log of why three of four originally suspected HIGH hook bugs turned out to be false positives recorded in this session's audit transcript:
90
+ - `sandbox-disposal-check.sh` printf `\x1e` — `printf '/tmp|claudemd_only\x1e%s|both' "$HOME"` produces 1 RS byte; `read -d $'\x1e'` correctly splits into 2 specs.
91
+ - `pre-bash-safety-check.sh` heredoc terminator — bash itself treats `EOF # comment` as heredoc body (not terminator), so the regex matching this behavior is correct, not a bypass.
92
+ - `banned-vocab-check.sh` baseline regex — `code→123ms` and `code → 123ms` both correctly fail the `[0-9].*(→|->|=>).*[0-9]` test (left side requires a number); previously-claimed asymmetry doesn't exist.
93
+
11
94
  ## [0.9.5] - 2026-05-10
12
95
 
13
96
  **Patch — `mem-audit.sh` hotfix (3 bugs introduced in v0.9.4).** Spec unchanged (still v6.11.7); plugin-only patch. The new Stop hook shipped in v0.9.4 silently failed validation on every Stop event ("Hook JSON output validation failed — Invalid input") and produced false-positive missing-marker warnings on legitimate memories. v0.9.5 fixes all three issues and adds `tests/hooks/mem-audit.test.sh` (9/9 cases) to lock them down.
package/README.md CHANGED
@@ -1,23 +1,42 @@
1
1
  # claudemd
2
2
 
3
- Claude Code plugin that enforces **AI-CODING-SPEC v6.11 HARD rules** through shell hooks and ships the spec as part of the plugin.
3
+ > Claude Code plugin that enforces **AI-CODING-SPEC v6.11 HARD rules** through shell hooks and ships the spec itself as part of the plugin.
4
4
 
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
+ [![npm](https://img.shields.io/npm/v/claudemd-cli.svg)](https://www.npmjs.com/package/claudemd-cli)
5
7
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
6
8
 
9
+ claudemd plugs into the Claude Code hook system to **block commits, pushes, and bash commands** that violate AI-CODING-SPEC v6.11 — 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`) ships with the plugin and installs into `~/.claude/`, so the rules Claude Code reads at session start match the rules the hooks enforce.
10
+
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
+
7
13
  ---
8
14
 
9
- ## What it installs
15
+ ## Quick start
10
16
 
11
- | Layer | Contents |
12
- |---|---|
13
- | 11 shell hooks | `banned-vocab-check` · `pre-bash-safety-check` · `ship-baseline-check` · `residue-audit` · `memory-read-check` · `sandbox-disposal-check` · `session-start-check` · `session-summary` · `transcript-vocab-scan` · `version-sync` · `mem-audit` (v0.9.4) |
14
- | 9 slash commands | `/claudemd-status` · `/claudemd-update` · `/claudemd-audit` · `/claudemd-toggle` · `/claudemd-doctor` · `/claudemd-uninstall` · `/claudemd-rules` · `/claudemd-clean-residue` · `/claudemd-sparkline` |
15
- | 1 standalone CLI | `claudemd-cli lint` · `claudemd-cli audit` (v0.9.0+ — for git pre-commit / CI / cross-agent use; npm package: [`claudemd-cli`](https://www.npmjs.com/package/claudemd-cli); same `banned-vocab.patterns` source as the in-CC hook) |
16
- | Spec v6.11.7 | `~/.claude/CLAUDE.md` · `CLAUDE-extended.md` · `CLAUDE-changelog.md` (backup-before-overwrite) |
17
+ Run **both** slash commands inside Claude Code:
18
+
19
+ ```
20
+ /plugin marketplace add sdsrss/claudemd
21
+ /plugin install claudemd@claudemd
22
+ ```
23
+
24
+ Then verify on your next session:
25
+
26
+ ```
27
+ /claudemd-status
28
+ /claudemd-doctor
29
+ ```
30
+
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.
17
32
 
18
- If you already have `~/.claude/CLAUDE.md`, install moves your existing files to `~/.claude/backup-<ISO>/` (last 5 kept automatically) before writing the plugin version. Uninstall offers `keep / delete / restore`; `delete` requires an extra confirmation.
33
+ The spec-file copy into `~/.claude/` runs from the `SessionStart` hook on the next session. To install in the **current** session, run the script directly (find `<version>` with `ls ~/.claude/plugins/cache/claudemd/claudemd/ | sort -V | tail -1`):
19
34
 
20
- > ⚠️ **`~/.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 will back them up to `~/.claude/backup-<ISO>/CLAUDE.md` and replace them with 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`.
35
+ ```bash
36
+ node ~/.claude/plugins/cache/claudemd/claudemd/<version>/scripts/install.js
37
+ ```
38
+
39
+ > ⚠️ **`~/.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`.
21
40
 
22
41
  ---
23
42
 
@@ -35,57 +54,58 @@ Verify in one command (Linux): `node --version && jq --version && gh --version &
35
54
 
36
55
  ---
37
56
 
38
- ## Install
39
-
40
- Run **both** slash commands inside Claude Code. First registers the GitHub marketplace, second installs the plugin:
41
-
42
- ```
43
- /plugin marketplace add sdsrss/claudemd
44
- /plugin install claudemd@claudemd
45
- ```
46
-
47
- That's it for normal use. The plugin's own `hooks/hooks.json` is registered by Claude Code immediately, and the `SessionStart` hook bootstraps `install.js` in the background on your **next** Claude Code session — copying `spec/CLAUDE*.md` into `~/.claude/` (backup-before-overwrite) and writing the install manifest. Verify with `/claudemd-status` after the next session.
57
+ ## What it installs
48
58
 
49
- **Optional fast-path — activate in the current session without restarting.** If you want the spec files in `~/.claude/` immediately (e.g. you opened Claude Code specifically to install this plugin and don't want to `/exit` first), run the install script directly. Find `<version>` with `ls ~/.claude/plugins/cache/claudemd/claudemd/ | sort -V | tail -1`:
59
+ | Layer | Contents |
60
+ |---|---|
61
+ | 11 shell hooks | `banned-vocab-check` · `pre-bash-safety-check` · `ship-baseline-check` · `residue-audit` · `memory-read-check` · `sandbox-disposal-check` · `session-start-check` · `session-summary` · `transcript-vocab-scan` · `version-sync` · `mem-audit` |
62
+ | 9 slash commands | `/claudemd-status` · `/claudemd-update` · `/claudemd-audit` · `/claudemd-toggle` · `/claudemd-doctor` · `/claudemd-uninstall` · `/claudemd-rules` · `/claudemd-clean-residue` · `/claudemd-sparkline` |
63
+ | 1 standalone CLI | `claudemd-cli lint` · `claudemd-cli audit` ([npm: `claudemd-cli`](https://www.npmjs.com/package/claudemd-cli)) |
64
+ | Spec v6.11.7 | `~/.claude/CLAUDE.md` · `CLAUDE-extended.md` · `CLAUDE-changelog.md` (backup-before-overwrite) |
50
65
 
51
- ```bash
52
- node ~/.claude/plugins/cache/claudemd/claudemd/<version>/scripts/install.js
53
- ```
66
+ 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.
54
67
 
55
68
  > Since v0.1.5, hook registration lives in the plugin's own `hooks/hooks.json` — the Claude Code harness expands `${CLAUDE_PLUGIN_ROOT}` there automatically on every invocation, so hooks track the active plugin version without manual re-registration. `install.js`'s remaining jobs are (1) copy `spec/CLAUDE*.md` into `~/.claude/` (with backup-before-overwrite), (2) evict any legacy claudemd hook entries from prior installs (≤0.1.1 absolute-path form, 0.1.2-0.1.4 `${CLAUDE_PLUGIN_ROOT}`-in-settings.json form), and (3) write the installed manifest. It never touches other-plugin hooks. Claude Code's plugin-lifecycle `postInstall` field is not honored, so the script runs from `SessionStart` instead.
56
69
 
57
- ### Verify
58
-
59
- ```
60
- /claudemd-status
61
- /claudemd-doctor
62
- ```
63
-
64
- `status` reports plugin version, shipped vs installed spec version, kill-switch state, and rule-hits row count. `doctor` runs 9+ health checks with `[✓] / [△] / [✗]` markers.
65
-
66
70
  ---
67
71
 
68
- ## Daily use
72
+ ## Hooks (what fires when)
69
73
 
70
- Once installed, the hooks run silently in the background:
74
+ Once installed, hooks run silently in the background. Verbose log: `~/.claude/logs/claudemd.jsonl` (one row per hook decision). Aggregate via `/claudemd-audit`.
71
75
 
72
76
  | Trigger | Hook | What happens |
73
77
  |---|---|---|
74
- | `git commit` with banned vocab (e.g. `significantly`, `70% faster`, `should work`) | `banned-vocab-check` | Blocks the commit with a message pointing to §10-V spec rule. |
78
+ | `git commit` with banned vocab (e.g. `significantly`, `70% faster`, `should work`) | `banned-vocab-check` | Blocks the commit with a message pointing to the §10-V spec rule. |
75
79
  | Bash command with `rm -rf $VAR` (unvalidated expansion) or unpinned `npx <pkg>` | `pre-bash-safety-check` (v0.5.0+) | Blocks at PreToolUse:Bash per §8 SAFETY. Bypass via `[allow-rm-rf-var]` / `[allow-npx-unpinned]` token in the command, or pin/validate the variable. |
76
80
  | `git push` while base-branch CI is red | `ship-baseline-check` | Blocks the push (2-second `gh run list` timeout; fail-open if `gh` absent or times out). |
77
- | Session end with `~/.claude/tmp/` growth > 20 entries | `residue-audit` | Advisory stderr warning; never blocks. |
78
81
  | Bash command matching ship/push/deploy/release with an unread matched `MEMORY.md` entry | `memory-read-check` | Blocks the command with a list of memory files to Read first. |
82
+ | Session end with `~/.claude/tmp/` growth > 20 entries | `residue-audit` | Advisory stderr warning; never blocks. |
79
83
  | Session end with fresh `tmp.XXXXXX`-style directories | `sandbox-disposal-check` | Advisory stderr warning. |
80
- | New session start with GitHub remote tag newer than local cache max version | `session-start-check` (v0.4.0+) | Injects an "upgrade available" banner via `additionalContext` listing the 4-step upgrade sequence. Rate-limited to once per 24h via `~/.claude/.claudemd-state/upstream-check.lastrun` sentinel. 3-second `git ls-remote` timeout, fail-open. |
81
- | First `UserPromptSubmit` after a mid-session `/plugin install` upgrade | `version-sync` (v0.3.1+) | Backgrounds `install.js` once per session when the manifest version diverges from the active plugin's `package.json`, so `~/.claude/CLAUDE*.md` syncs without `/exit`. Sentinel-gated; fail-open. |
84
+ | Stop event with `Why:`-less hard-rule citations in the assistant turn | `mem-audit` (v0.9.4+) | Detects spec-citation patterns missing the `Why:` rationale token; advisory log. |
85
+ | Session end | `session-summary` (v0.8.0+) | Writes `~/.claude/.claudemd-state/last-session-summary.json`; banner emit at next `SessionStart`. |
86
+ | New session start with GitHub remote tag newer than local cache max version | `session-start-check` (v0.4.0+) | Injects an "upgrade available" banner via `additionalContext`. Rate-limited to once per 24h via `~/.claude/.claudemd-state/upstream-check.lastrun` sentinel. 3-second `git ls-remote` timeout, fail-open. |
87
+ | First `UserPromptSubmit` after a mid-session `/plugin install` upgrade | `version-sync` (v0.3.1+) | Backgrounds `install.js` once per session when the manifest version diverges from the active plugin's `package.json`. Sentinel-gated; fail-open. |
88
+ | `PostToolUse` after assistant text containing banned vocab | `transcript-vocab-scan` | Advisory; logs to rule-hits without blocking. |
89
+
90
+ ### Execution order (PreToolUse:Bash)
91
+
92
+ CC runs all configured PreToolUse hooks for `Bash` sequentially in declaration order. **First deny stops the rest** and the tool call is denied. The 4 Bash hooks fire in this order (declared in `hooks/hooks.json`):
93
+
94
+ 1. `pre-bash-safety-check` (§8 SAFETY immutable) — `rm -rf $VAR`, unpinned `npx <pkg>`. First so a §8 violation can never be overridden by a downstream hook.
95
+ 2. `banned-vocab-check` (§10-V) — `git commit` message scan.
96
+ 3. `ship-baseline-check` (§7) — `git push` while base CI is red.
97
+ 4. `memory-read-check` (§11) — ship/release/deploy commands matching unread MEMORY.md tags.
98
+
99
+ 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.
82
100
 
83
- ### Commands
101
+ Opt-in `BASH_READONLY_FAST_PATH=1` short-circuits hooks 1, 2, and 4 when the command is a definitely-read-only shape (no shell-meta, first token in safe-reader whitelist — `ls`, `cat`, `git log`, etc.). Hook 3 only fires on `git push` so the fast-path doesn't apply.
102
+
103
+ ## Commands
84
104
 
85
105
  | Command | Purpose |
86
106
  |---|---|
87
107
  | `/claudemd-status` | Plugin version + spec version + kill-switch state + logs line count. |
88
- | `/claudemd-update` | Interactive diff against plugin-shipped spec, then apply-all or cancel (spec trio is lockstep — per-file select would break §EXT cross-references). |
108
+ | `/claudemd-update` | Interactive diff against plugin-shipped spec, then apply-all or cancel (spec trio is lockstep — per-file select would dangle §EXT cross-references). |
89
109
  | `/claudemd-audit [--days N]` | Aggregate rule-hits over last N days (default 30). Top banned-vocab patterns, per-hook deny counts. |
90
110
  | `/claudemd-toggle <hook-name>` | Enable/disable a specific hook by toggling `DISABLE_*_HOOK` in `settings.json` env. |
91
111
  | `/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). |
@@ -96,20 +116,20 @@ Once installed, the hooks run silently in the background:
96
116
 
97
117
  ---
98
118
 
99
- ## Standalone CLI (v0.9.0+ R-N7)
119
+ ## Standalone CLI
100
120
 
101
- The same `banned-vocab.patterns` source the in-CC hook uses is also exposed as a standalone Node CLI for **git pre-commit hooks, GitHub Actions, and other agents** (Codex, Cursor, OpenClaw) — i.e. anywhere outside the Claude Code process.
121
+ The same `banned-vocab.patterns` source the in-CC hook uses is also exposed as a Node CLI for **git pre-commit hooks, GitHub Actions, and other agents** (Codex, Cursor, OpenClaw) — anywhere outside the Claude Code process.
102
122
 
103
123
  ```bash
104
- # Dev mode (this repo, before npm publish):
105
- node bin/claudemd-lint.js lint "your commit message here"
106
- node bin/claudemd-lint.js audit ~/.claude/projects/<encoded>/<session>.jsonl
107
-
108
124
  # After npm publish (operator-driven, not part of plugin install):
109
125
  npx claudemd-cli lint "your commit message here"
110
126
  npx claudemd-cli lint --stdin < message.txt
111
- npx claudemd-cli audit transcript.jsonl
127
+ npx claudemd-cli audit ~/.claude/projects/<encoded>/<session>.jsonl
112
128
  npx claudemd-cli audit transcript.jsonl --json
129
+
130
+ # Dev mode (this repo, before npm publish):
131
+ node bin/claudemd-lint.js lint "your commit message here"
132
+ node bin/claudemd-lint.js audit ~/.claude/projects/<encoded>/<session>.jsonl
113
133
  ```
114
134
 
115
135
  | Subcommand | Purpose |
@@ -120,6 +140,7 @@ npx claudemd-cli audit transcript.jsonl --json
120
140
  | `--version` / `--help` | Standard. |
121
141
 
122
142
  **Pre-commit example (`.git/hooks/commit-msg`)**:
143
+
123
144
  ```bash
124
145
  #!/usr/bin/env bash
125
146
  npx claudemd-cli lint --stdin < "$1" || exit 1
@@ -129,17 +150,17 @@ The CLI does NOT depend on `~/.claude/` state — pure stateless input → stdou
129
150
 
130
151
  ---
131
152
 
132
- ## Kill-switches (three tiers)
153
+ ## Kill-switches
133
154
 
134
- All visible in `/claudemd-status`.
155
+ All visible in `/claudemd-status`. Three tiers:
135
156
 
136
- **1. Plugin-wide.** All 9 hooks short-circuit before any logic:
157
+ **1. Plugin-wide.** Short-circuits every hook before any logic runs:
137
158
 
138
159
  ```bash
139
160
  export DISABLE_CLAUDEMD_HOOKS=1
140
161
  ```
141
162
 
142
- **2. Per-hook.** Disable one hook, leave others active:
163
+ **2. Per-hook.** Disable one, leave others active:
143
164
 
144
165
  ```bash
145
166
  export DISABLE_BANNED_VOCAB_HOOK=1 # or
@@ -149,8 +170,8 @@ export DISABLE_RESIDUE_AUDIT_HOOK=1 # or
149
170
  export DISABLE_MEMORY_READ_HOOK=1 # or
150
171
  export DISABLE_SANDBOX_DISPOSAL_HOOK=1 # or
151
172
  export DISABLE_SESSION_START_HOOK=1 # or
152
- export DISABLE_SESSION_SUMMARY_HOOK=1 # or (v0.8.0+, Stop hook writing summary)
153
- export DISABLE_USER_PROMPT_SUBMIT_HOOK=1
173
+ export DISABLE_SESSION_SUMMARY_HOOK=1 # v0.8.0+ Stop hook writing summary
174
+ export DISABLE_USER_PROMPT_SUBMIT_HOOK=1 # transcript-vocab-scan
154
175
  ```
155
176
 
156
177
  **2a. Per-sub-feature** (v0.4.0+). Sub-flags inside an enabled hook, named without the `_HOOK` suffix:
@@ -167,7 +188,7 @@ export DISABLE_SESSION_SUMMARY_BANNER=1 # v0.8.0+ — only the SessionStart b
167
188
  # but no additionalContext line is injected.
168
189
  ```
169
190
 
170
- **3. Per-invocation escape hatches** (no env var needed; embed in the command itself):
191
+ **3. Per-invocation escape hatches.** Embed in the command itself, no env var needed:
171
192
 
172
193
  | Escape | Where | Bypasses |
173
194
  |---|---|---|
@@ -179,9 +200,32 @@ export DISABLE_SESSION_SUMMARY_BANNER=1 # v0.8.0+ — only the SessionStart b
179
200
 
180
201
  ---
181
202
 
203
+ ## Update
204
+
205
+ Claude Code has **no** `/plugin update` slash command — it's silently ignored as unrecognized. The canonical upgrade sequence is:
206
+
207
+ ```
208
+ /plugin marketplace update claudemd # refresh local marketplace clone (git fetch)
209
+ /plugin uninstall claudemd@claudemd # remove old plugin version
210
+ /plugin install claudemd@claudemd # install latest from refreshed clone
211
+ /reload-plugins # apply changes to current session
212
+ ```
213
+
214
+ Or open the interactive UI via `/plugin` → **Installed** tab → select `claudemd` → follow upgrade prompts.
215
+
216
+ After the plugin upgrade, sync the shipped spec into `~/.claude/`:
217
+
218
+ ```
219
+ /claudemd-update
220
+ ```
221
+
222
+ The command prints per-file diff summary, then prompts `apply-all` or `cancel`. Per-file select is intentionally not supported — the spec trio (`CLAUDE.md` + `CLAUDE-extended.md` + `CLAUDE-changelog.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`. The network fetch is Claude Code's job (via `/plugin marketplace update`).
223
+
224
+ ---
225
+
182
226
  ## Uninstall
183
227
 
184
- CC marketplace lifecycle does not fire `preUninstall`, so `/plugin uninstall claudemd@claudemd` alone leaves orphan state behind (`~/.claude/.claudemd-manifest.json`, `~/.claude/.claudemd-state/`, `~/.claude/logs/claudemd.jsonl`). Use the **two-step flow**:
228
+ Claude Code's marketplace lifecycle does not fire `preUninstall`, so `/plugin uninstall claudemd@claudemd` alone leaves orphan state behind (`~/.claude/.claudemd-manifest.json`, `~/.claude/.claudemd-state/`, `~/.claude/logs/claudemd.jsonl`). Use the **two-step flow**:
185
229
 
186
230
  ```
187
231
  /claudemd-uninstall # clear manifest + state + log (plugin still installed)
@@ -197,7 +241,7 @@ Reversing the order is the orphan-state vector — `${CLAUDE_PLUGIN_ROOT}` and `
197
241
  | Option | Env vars | Behavior |
198
242
  |---|---|---|
199
243
  | `keep` (default) | (none) | `~/.claude/CLAUDE*.md` left in place; settings.json hook entries cleared. |
200
- | `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` — it means your hand-written user-global instructions are sitting in the backup waiting to be brought back. |
244
+ | `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. |
201
245
  | `delete` | `CLAUDEMD_SPEC_ACTION=delete CLAUDEMD_CONFIRM=1` | Hard-AUTH: removes the three spec files. |
202
246
 
203
247
  `CLAUDEMD_PURGE=1` (env var) on `/claudemd-uninstall` also drops `~/.claude/.claudemd-state/` and your rule-hits log.
@@ -216,34 +260,11 @@ The slash command and the script are equivalent — the slash command just suppl
216
260
 
217
261
  ---
218
262
 
219
- ## Update
220
-
221
- Claude Code has **no** `/plugin update` slash command — it's silently ignored as unrecognized. The canonical upgrade sequence is:
222
-
223
- ```
224
- /plugin marketplace update claudemd # refresh local marketplace clone (git fetch)
225
- /plugin uninstall claudemd@claudemd # remove old plugin version
226
- /plugin install claudemd@claudemd # install latest from refreshed clone
227
- /reload-plugins # apply changes to current session
228
- ```
229
-
230
- Or open the interactive UI via `/plugin` → **Installed** tab → select `claudemd` → follow upgrade prompts.
231
-
232
- After the plugin upgrade, sync the shipped spec into `~/.claude/`:
233
-
234
- ```
235
- /claudemd-update
236
- ```
237
-
238
- The command prints per-file diff summary, then prompts `apply-all` or `cancel`. Per-file select is intentionally not supported — the spec trio (`CLAUDE.md` + `CLAUDE-extended.md` + `CLAUDE-changelog.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`. The network fetch is Claude Code's job (via `/plugin marketplace update`).
239
-
240
- ---
241
-
242
263
  ## Troubleshooting
243
264
 
244
265
  **`Plugin "claudemd" not found in any marketplace`** — you forgot the `/plugin marketplace add sdsrss/claudemd` step. Re-run it, then retry install.
245
266
 
246
- **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. Either start a fresh Claude Code session, or run the script manually right now (replace `<version>` with the installed version dir — see the [Install](#install) section):
267
+ **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. Either start a fresh Claude Code session, or run the script manually right now (replace `<version>` with the installed version dir):
247
268
 
248
269
  ```bash
249
270
  node ~/.claude/plugins/cache/claudemd/claudemd/<version>/scripts/install.js
@@ -251,9 +272,9 @@ node ~/.claude/plugins/cache/claudemd/claudemd/<version>/scripts/install.js
251
272
 
252
273
  Verify with `/claudemd-status` — the "log.lines" count should increment after the next hook fires.
253
274
 
254
- **`/plugin update claudemd` does nothing / empty stdout** — `/plugin update` is not a valid Claude Code slash command; CC silently ignores unrecognized commands. Use the canonical sequence instead (see **Update** section above): `/plugin marketplace update claudemd` → `/plugin uninstall claudemd@claudemd` → `/plugin install claudemd@claudemd` → `/reload-plugins`. If that also fails (marketplace clone refuses to refresh), manually `git -C ~/.claude/plugins/marketplaces/claudemd fetch origin main --tags && git merge --ff-only origin/main`, then `git archive v<version> | tar -x -C ~/.claude/plugins/cache/claudemd/claudemd/<version>/`, then run that version's `scripts/install.js`.
275
+ **`/plugin update claudemd` does nothing / empty stdout** — `/plugin update` is not a valid Claude Code slash command; CC silently ignores unrecognized commands. Use the canonical sequence in the [Update](#update) section: `/plugin marketplace update claudemd` → `/plugin uninstall claudemd@claudemd` → `/plugin install claudemd@claudemd` → `/reload-plugins`. If that also fails (marketplace clone refuses to refresh), manually `git -C ~/.claude/plugins/marketplaces/claudemd fetch origin main --tags && git merge --ff-only origin/main`, then `git archive v<version> | tar -x -C ~/.claude/plugins/cache/claudemd/claudemd/<version>/`, then run that version's `scripts/install.js`.
255
276
 
256
- **`Hook command references ${CLAUDE_PLUGIN_ROOT} but the hook is not associated with a plugin`** (5 errors on every `Bash` tool call + every session end) — you're on claudemd 0.1.2 / 0.1.3 / 0.1.4. Those releases wrote hook commands into `~/.claude/settings.json` under the literal `${CLAUDE_PLUGIN_ROOT}` token, but the CC harness only expands that variable for hooks defined in a plugin's own `hooks/hooks.json` — never in `settings.json`. The fix is v0.1.5+, which moves hook registration into the plugin's `hooks/hooks.json` (where the token expands correctly) and evicts the stale settings.json entries on install. Upgrade via the canonical sequence in the **Update** section above, then restart the Claude Code session to clear the cached hook registry.
277
+ **`Hook command references ${CLAUDE_PLUGIN_ROOT} but the hook is not associated with a plugin`** (5 errors on every `Bash` tool call + every session end) — you're on claudemd 0.1.2 / 0.1.3 / 0.1.4. Those releases wrote hook commands into `~/.claude/settings.json` under the literal `${CLAUDE_PLUGIN_ROOT}` token, but the CC harness only expands that variable for hooks defined in a plugin's own `hooks/hooks.json` — never in `settings.json`. The fix is v0.1.5+, which moves hook registration into the plugin's `hooks/hooks.json` (where the token expands correctly) and evicts the stale settings.json entries on install. Upgrade via the canonical sequence in the [Update](#update) section, then restart the Claude Code session to clear the cached hook registry.
257
278
 
258
279
  **`ship-baseline-check` silently passes on red CI** — `gh` CLI is not installed, or authentication failed. Install with `brew install gh` / `apt-get install gh` and run `gh auth login`. Check with `/claudemd-doctor` — it reports `gh: missing` if absent.
259
280
 
@@ -265,14 +286,6 @@ Verify with `/claudemd-status` — the "log.lines" count should increment after
265
286
 
266
287
  ---
267
288
 
268
- ## Extending
269
-
270
- - **Add a 6th hook**: see `docs/ADDING-NEW-HOOK.md` for the 5-step guide (hook script + test + plugin registration + doc + version bump).
271
- - **Rule-hits log schema**: see `docs/RULE-HITS-SCHEMA.md` for the JSONL row format used by `/claudemd-audit`.
272
- - **Design rationale + decisions log**: `docs/superpowers/specs/2026-04-21-claudemd-plugin-design.md`.
273
-
274
- ---
275
-
276
289
  ## Project layout
277
290
 
278
291
  ```
@@ -280,19 +293,27 @@ claudemd/
280
293
  ├── .claude-plugin/
281
294
  │ ├── plugin.json # minimal manifest (name, version, author, license, keywords)
282
295
  │ └── marketplace.json # marketplace catalog entry
283
- ├── hooks/ # 10 shell hooks + hooks/lib/ (hook-common, rule-hits, platform)
296
+ ├── hooks/ # 11 shell hooks + hooks/lib/ (hook-common, rule-hits, platform)
284
297
  │ └── hooks.json # authoritative hook registration (v0.1.5+); CC expands ${CLAUDE_PLUGIN_ROOT} here
285
298
  ├── commands/ # 9 slash-command markdown files
286
299
  ├── bin/ # standalone CLI entrypoint (claudemd-lint.js → `npx claudemd-cli` on npmjs.org)
287
300
  ├── scripts/ # 10 Node.js management scripts + scripts/lib/ (single-source registry, lint, etc.)
288
- ├── spec/ # shipped v6.11.7 CLAUDE*.md trio
301
+ ├── spec/ # shipped v6.11.7 CLAUDE*.md trio + hard-rules.json manifest
289
302
  ├── tests/ # hook shell tests + Node.js tests + integration + fixtures
290
303
  ├── docs/ # ADDING-NEW-HOOK.md + RULE-HITS-SCHEMA.md + superpowers/
291
- └── .github/workflows/ci.yml # ubuntu + macOS × node 20
304
+ └── .github/workflows/ # ci.yml (ubuntu+macOS × node 20) + npm-publish.yml (tag-triggered)
292
305
  ```
293
306
 
294
307
  ---
295
308
 
309
+ ## Extending
310
+
311
+ - **Add a new hook** — see [`docs/ADDING-NEW-HOOK.md`](docs/ADDING-NEW-HOOK.md) for the 5-step guide (hook script + test + plugin registration + doc + version bump).
312
+ - **Rule-hits log schema** — [`docs/RULE-HITS-SCHEMA.md`](docs/RULE-HITS-SCHEMA.md) for the JSONL row format used by `/claudemd-audit`.
313
+ - **Design rationale + decisions log** — [`docs/superpowers/specs/2026-04-21-claudemd-plugin-design.md`](docs/superpowers/specs/2026-04-21-claudemd-plugin-design.md).
314
+
315
+ ---
316
+
296
317
  ## License
297
318
 
298
- MIT. See [LICENSE](LICENSE).
319
+ MIT see [LICENSE](LICENSE).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudemd-cli",
3
- "version": "0.9.5",
3
+ "version": "0.9.8",
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": {