claudemd-cli 0.9.4 → 0.9.6

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 +64 -0
  2. package/README.md +103 -95
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -8,6 +8,70 @@ 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.6] - 2026-05-10
12
+
13
+ **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.
14
+
15
+ ### Fixed
16
+
17
+ - `[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.
18
+ - `[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.
19
+
20
+ ### Added
21
+
22
+ - `[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.
23
+
24
+ ### Versioning
25
+
26
+ - `package.json` 0.9.5 → **0.9.6** (npm).
27
+ - `.claude-plugin/plugin.json` 0.9.5 → **0.9.6**.
28
+ - `.claude-plugin/marketplace.json` two version fields 0.9.5 → **0.9.6**.
29
+ - Spec headers unchanged (v6.11.7 still current); no `spec/CLAUDE-changelog.md` entry — plugin-only patch.
30
+
31
+ ### Validation
32
+
33
+ - `node --test tests/scripts/*.test.js` → 217 passed (211 prior + 6 new), 0 failed.
34
+ - `bash hooks/session-start-check.sh < /dev/null` → exit 0; banner emitted as expected; semver gate not exercised on green path (operator-side smoke).
35
+ - 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:
36
+ - `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.
37
+ - `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.
38
+ - `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.
39
+
40
+ ## [0.9.5] - 2026-05-10
41
+
42
+ **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.
43
+
44
+ ### Fixed
45
+
46
+ - `[fix]` **Stop event schema mismatch (CRITICAL)** — v0.9.4 emitted `{"hookSpecificOutput": {"hookEventName": "Stop", "additionalContext": "..."}}` to stdout, but Claude Code's hook schema only accepts `hookSpecificOutput` for `PreToolUse` / `UserPromptSubmit` / `PostToolUse` / `PostToolBatch`. **Stop is not on the list.** Every emit was rejected with `Hook JSON output validation failed — (root): Invalid input`. The fix mirrors `residue-audit.sh`: write to stderr only, no JSON output. CC harness surfaces stderr as advisory; Stop cannot block by design either way.
47
+ - `[fix]` **Path double-slash** — error banner showed paths like `<encoded>//memory/feedback_x.md` (double slash after the project dir). Root cause: glob `"$PROJECTS_ROOT"/*/` produces values with trailing slashes, so the join `"$proj_dir/memory"` became `"<dir>/memory"` with `<dir>` already ending `/`. Fix: `proj_dir="${proj_dir%/}"` strips the trailing slash before the join.
48
+ - `[fix]` **Regex matched only one Why-form** — v0.9.4 regex `^\*\*Why:\*\*` matched only `**Why:**` (colon inside bolding), but most memories in the wild use `**Why**:` (colon outside bolding). Both forms are valid per CC `memoryTypes.ts:58` body_structure example. v0.9.5 regex `^\*\*Why(:\*\*|\*\*:)` accepts either; same change for `**How to apply:**` / `**How to apply**:`. This eliminates the v0.9.4 26-file false-positive batch on `~/.claude/projects/-mnt-data-ssd-dev-projects-claudemd/memory/feedback_*.md`.
49
+
50
+ ### Added
51
+
52
+ - `[test]` **`tests/hooks/mem-audit.test.sh` — 9 cases**: (1) no projects dir → silent, (2) empty memory dir → silent, (3) **Why:** colon-inside-bolding accepted, (4) **Why**: colon-outside-bolding accepted, (5) missing markers → stderr warn, no stdout (locks Stop schema fix), (6) path single-slash format (locks the trailing-slash bug), (7) 24h sentinel debounce, (8) kill-switch DISABLE_MEM_AUDIT_HOOK=1, (9) MEMORY.md index file skipped.
53
+
54
+ ### Compatibility
55
+
56
+ - **No claude-mem-lite required.** `mem-audit.sh` audits CC built-in auto-memory under `~/.claude/projects/<encoded>/memory/` only. It does NOT depend on `claude-mem-lite`, `claude-mem`, or any other recall-layer plugin. If a user only has the claudemd plugin installed (no recall plugin):
57
+ - Hook still operates correctly, scanning whatever CC built-in 4-types memories exist.
58
+ - Zero memory files → silent exit (no projects dir / no relevant files = no output).
59
+ - Spec wording (§11 + §11-EXT) consistently uses "recall-layer plugin **if present**" — no plugin specifically required.
60
+ - **Comment in `mem-audit.sh:13-21`** documents the independence property explicitly.
61
+
62
+ ### Versioning
63
+
64
+ - `package.json` 0.9.4 → **0.9.5** (npm).
65
+ - `.claude-plugin/plugin.json` 0.9.4 → **0.9.5**.
66
+ - `.claude-plugin/marketplace.json` two version fields 0.9.4 → **0.9.5**.
67
+ - Spec headers unchanged (v6.11.7 still current); no `spec/CLAUDE-changelog.md` entry — plugin-only patch.
68
+
69
+ ### Validation
70
+
71
+ - `bash tests/hooks/mem-audit.test.sh` → 9/9 passed.
72
+ - `npm run test:scripts` → 211/211 passed.
73
+ - `bash tests/run-all.sh` → all suites passed including upgrade-lifecycle (v0.2.3/v6.10.1 → current/v6.11.7).
74
+
11
75
  ## [0.9.4] - 2026-05-10
12
76
 
13
77
  **Minor — spec v6.11.7 (CC-source comparative audit) + new `mem-audit` Stop hook.** Driven by side-by-side analysis of upstream `sdscc/src/constants/prompts.ts` + `src/memdir/memoryTypes.ts` against AI-CODING-SPEC v6.11.6 — five spec additions where CC's eval-validated rules were stronger or absent in spec. Plugin manifest version skips `0.9.3` (npm-only release that didn't bump `plugin.json` / `marketplace.json`); v0.9.4 brings all manifests back to a coherent state.
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:
17
18
 
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.
19
+ ```
20
+ /plugin marketplace add sdsrss/claudemd
21
+ /plugin install claudemd@claudemd
22
+ ```
19
23
 
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`.
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.
32
+
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`):
34
+
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,45 @@ 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. |
82
89
 
83
- ### Commands
90
+ ## Commands
84
91
 
85
92
  | Command | Purpose |
86
93
  |---|---|
87
94
  | `/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). |
95
+ | `/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
96
  | `/claudemd-audit [--days N]` | Aggregate rule-hits over last N days (default 30). Top banned-vocab patterns, per-hook deny counts. |
90
97
  | `/claudemd-toggle <hook-name>` | Enable/disable a specific hook by toggling `DISABLE_*_HOOK` in `settings.json` env. |
91
98
  | `/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 +103,20 @@ Once installed, the hooks run silently in the background:
96
103
 
97
104
  ---
98
105
 
99
- ## Standalone CLI (v0.9.0+ R-N7)
106
+ ## Standalone CLI
100
107
 
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.
108
+ 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
109
 
103
110
  ```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
111
  # After npm publish (operator-driven, not part of plugin install):
109
112
  npx claudemd-cli lint "your commit message here"
110
113
  npx claudemd-cli lint --stdin < message.txt
111
- npx claudemd-cli audit transcript.jsonl
114
+ npx claudemd-cli audit ~/.claude/projects/<encoded>/<session>.jsonl
112
115
  npx claudemd-cli audit transcript.jsonl --json
116
+
117
+ # Dev mode (this repo, before npm publish):
118
+ node bin/claudemd-lint.js lint "your commit message here"
119
+ node bin/claudemd-lint.js audit ~/.claude/projects/<encoded>/<session>.jsonl
113
120
  ```
114
121
 
115
122
  | Subcommand | Purpose |
@@ -120,6 +127,7 @@ npx claudemd-cli audit transcript.jsonl --json
120
127
  | `--version` / `--help` | Standard. |
121
128
 
122
129
  **Pre-commit example (`.git/hooks/commit-msg`)**:
130
+
123
131
  ```bash
124
132
  #!/usr/bin/env bash
125
133
  npx claudemd-cli lint --stdin < "$1" || exit 1
@@ -129,17 +137,17 @@ The CLI does NOT depend on `~/.claude/` state — pure stateless input → stdou
129
137
 
130
138
  ---
131
139
 
132
- ## Kill-switches (three tiers)
140
+ ## Kill-switches
133
141
 
134
- All visible in `/claudemd-status`.
142
+ All visible in `/claudemd-status`. Three tiers:
135
143
 
136
- **1. Plugin-wide.** All 9 hooks short-circuit before any logic:
144
+ **1. Plugin-wide.** Short-circuits every hook before any logic runs:
137
145
 
138
146
  ```bash
139
147
  export DISABLE_CLAUDEMD_HOOKS=1
140
148
  ```
141
149
 
142
- **2. Per-hook.** Disable one hook, leave others active:
150
+ **2. Per-hook.** Disable one, leave others active:
143
151
 
144
152
  ```bash
145
153
  export DISABLE_BANNED_VOCAB_HOOK=1 # or
@@ -149,8 +157,8 @@ export DISABLE_RESIDUE_AUDIT_HOOK=1 # or
149
157
  export DISABLE_MEMORY_READ_HOOK=1 # or
150
158
  export DISABLE_SANDBOX_DISPOSAL_HOOK=1 # or
151
159
  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
160
+ export DISABLE_SESSION_SUMMARY_HOOK=1 # v0.8.0+ Stop hook writing summary
161
+ export DISABLE_USER_PROMPT_SUBMIT_HOOK=1 # transcript-vocab-scan
154
162
  ```
155
163
 
156
164
  **2a. Per-sub-feature** (v0.4.0+). Sub-flags inside an enabled hook, named without the `_HOOK` suffix:
@@ -167,7 +175,7 @@ export DISABLE_SESSION_SUMMARY_BANNER=1 # v0.8.0+ — only the SessionStart b
167
175
  # but no additionalContext line is injected.
168
176
  ```
169
177
 
170
- **3. Per-invocation escape hatches** (no env var needed; embed in the command itself):
178
+ **3. Per-invocation escape hatches.** Embed in the command itself, no env var needed:
171
179
 
172
180
  | Escape | Where | Bypasses |
173
181
  |---|---|---|
@@ -179,9 +187,32 @@ export DISABLE_SESSION_SUMMARY_BANNER=1 # v0.8.0+ — only the SessionStart b
179
187
 
180
188
  ---
181
189
 
190
+ ## Update
191
+
192
+ Claude Code has **no** `/plugin update` slash command — it's silently ignored as unrecognized. The canonical upgrade sequence is:
193
+
194
+ ```
195
+ /plugin marketplace update claudemd # refresh local marketplace clone (git fetch)
196
+ /plugin uninstall claudemd@claudemd # remove old plugin version
197
+ /plugin install claudemd@claudemd # install latest from refreshed clone
198
+ /reload-plugins # apply changes to current session
199
+ ```
200
+
201
+ Or open the interactive UI via `/plugin` → **Installed** tab → select `claudemd` → follow upgrade prompts.
202
+
203
+ After the plugin upgrade, sync the shipped spec into `~/.claude/`:
204
+
205
+ ```
206
+ /claudemd-update
207
+ ```
208
+
209
+ 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`).
210
+
211
+ ---
212
+
182
213
  ## Uninstall
183
214
 
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**:
215
+ 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
216
 
186
217
  ```
187
218
  /claudemd-uninstall # clear manifest + state + log (plugin still installed)
@@ -197,7 +228,7 @@ Reversing the order is the orphan-state vector — `${CLAUDE_PLUGIN_ROOT}` and `
197
228
  | Option | Env vars | Behavior |
198
229
  |---|---|---|
199
230
  | `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. |
231
+ | `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
232
  | `delete` | `CLAUDEMD_SPEC_ACTION=delete CLAUDEMD_CONFIRM=1` | Hard-AUTH: removes the three spec files. |
202
233
 
203
234
  `CLAUDEMD_PURGE=1` (env var) on `/claudemd-uninstall` also drops `~/.claude/.claudemd-state/` and your rule-hits log.
@@ -216,34 +247,11 @@ The slash command and the script are equivalent — the slash command just suppl
216
247
 
217
248
  ---
218
249
 
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
250
  ## Troubleshooting
243
251
 
244
252
  **`Plugin "claudemd" not found in any marketplace`** — you forgot the `/plugin marketplace add sdsrss/claudemd` step. Re-run it, then retry install.
245
253
 
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):
254
+ **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
255
 
248
256
  ```bash
249
257
  node ~/.claude/plugins/cache/claudemd/claudemd/<version>/scripts/install.js
@@ -251,9 +259,9 @@ node ~/.claude/plugins/cache/claudemd/claudemd/<version>/scripts/install.js
251
259
 
252
260
  Verify with `/claudemd-status` — the "log.lines" count should increment after the next hook fires.
253
261
 
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`.
262
+ **`/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
263
 
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.
264
+ **`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
265
 
258
266
  **`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
267
 
@@ -265,14 +273,6 @@ Verify with `/claudemd-status` — the "log.lines" count should increment after
265
273
 
266
274
  ---
267
275
 
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
276
  ## Project layout
277
277
 
278
278
  ```
@@ -280,19 +280,27 @@ claudemd/
280
280
  ├── .claude-plugin/
281
281
  │ ├── plugin.json # minimal manifest (name, version, author, license, keywords)
282
282
  │ └── marketplace.json # marketplace catalog entry
283
- ├── hooks/ # 10 shell hooks + hooks/lib/ (hook-common, rule-hits, platform)
283
+ ├── hooks/ # 11 shell hooks + hooks/lib/ (hook-common, rule-hits, platform)
284
284
  │ └── hooks.json # authoritative hook registration (v0.1.5+); CC expands ${CLAUDE_PLUGIN_ROOT} here
285
285
  ├── commands/ # 9 slash-command markdown files
286
286
  ├── bin/ # standalone CLI entrypoint (claudemd-lint.js → `npx claudemd-cli` on npmjs.org)
287
287
  ├── scripts/ # 10 Node.js management scripts + scripts/lib/ (single-source registry, lint, etc.)
288
- ├── spec/ # shipped v6.11.7 CLAUDE*.md trio
288
+ ├── spec/ # shipped v6.11.7 CLAUDE*.md trio + hard-rules.json manifest
289
289
  ├── tests/ # hook shell tests + Node.js tests + integration + fixtures
290
290
  ├── docs/ # ADDING-NEW-HOOK.md + RULE-HITS-SCHEMA.md + superpowers/
291
- └── .github/workflows/ci.yml # ubuntu + macOS × node 20
291
+ └── .github/workflows/ # ci.yml (ubuntu+macOS × node 20) + npm-publish.yml (tag-triggered)
292
292
  ```
293
293
 
294
294
  ---
295
295
 
296
+ ## Extending
297
+
298
+ - **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).
299
+ - **Rule-hits log schema** — [`docs/RULE-HITS-SCHEMA.md`](docs/RULE-HITS-SCHEMA.md) for the JSONL row format used by `/claudemd-audit`.
300
+ - **Design rationale + decisions log** — [`docs/superpowers/specs/2026-04-21-claudemd-plugin-design.md`](docs/superpowers/specs/2026-04-21-claudemd-plugin-design.md).
301
+
302
+ ---
303
+
296
304
  ## License
297
305
 
298
- MIT. See [LICENSE](LICENSE).
306
+ MIT see [LICENSE](LICENSE).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudemd-cli",
3
- "version": "0.9.4",
3
+ "version": "0.9.6",
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": {