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.
- package/CHANGELOG.md +83 -0
- package/README.md +116 -95
- 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
|
+
[](https://github.com/sdsrss/claudemd/actions/workflows/ci.yml)
|
|
6
|
+
[](https://www.npmjs.com/package/claudemd-cli)
|
|
5
7
|
[](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
|
-
##
|
|
15
|
+
## Quick start
|
|
10
16
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
72
|
+
## Hooks (what fires when)
|
|
69
73
|
|
|
70
|
-
Once installed,
|
|
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
|
-
|
|
|
81
|
-
|
|
|
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
|
-
|
|
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
|
|
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
|
|
119
|
+
## Standalone CLI
|
|
100
120
|
|
|
101
|
-
The same `banned-vocab.patterns` source the in-CC hook uses is also exposed as a
|
|
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
|
|
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
|
|
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.**
|
|
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
|
|
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 #
|
|
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
|
|
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
|
-
|
|
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` —
|
|
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
|
|
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
|
|
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
|
|
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/ #
|
|
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
|
|
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
|
|
319
|
+
MIT — see [LICENSE](LICENSE).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claudemd-cli",
|
|
3
|
-
"version": "0.9.
|
|
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": {
|