claudemd-cli 0.47.4 → 0.48.1

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 +20 -0
  2. package/README.md +13 -4
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -8,6 +8,26 @@ 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.48.1] - 2026-07-15
12
+
13
+ **Patch — three §8 pre-detector silent bypasses closed (D1 brace-subpath, D2 arithmetic/quoted `<<` fakes a heredoc). Bugfix restoring intended deny behavior; no new gate, no §8 detector added.**
14
+
15
+ - **D1 — `canon_cmd_words` tore `${VAR}/subpath` apart.** It set command position on `{`, so `rm -rf "${SP}/build"` was basenamed to `rm -rf "${build"`, erasing the `${SP}` the var-detector greps for → the segment was skipped (ALLOW). Only shapes with a `/` after the `{` were mangled, landing the bypass precisely on the dangerous subpath class; `rm -rf "${HOME}/"` (empty `HOME` = `rm -rf /`, the ValveSoftware/steam-for-linux#3671 residue case) bypassed too. Fix: `{` re-opens command position only as a real brace-group introducer (`{ rm; }`), guarded on the preceding char not being `$`. Introduced with `canon_cmd_words` in v0.42.0 (SEC-2).
16
+ - **D2 — a `<<` left-shift / quoted `<<` faked a heredoc and blanked the following command.** `heredoc_re` matched any `<<word`, so `$((1<<bits))`, `$[a<<b]`, `let a<<b`, and a `<<` inside a quoted string (`echo "a<<b"; rm -rf $EVIL`) all set `in_heredoc`, truncated the line at `<<`, and blanked every following line — deleting the rm/npx/curl from the text **all three** detectors scan (blinds the whole gate, not one detector). Fix (root cause, closes the class without enumerating shell syntaxes): treat `<<TAG` as a heredoc only when a matching terminator line actually exists later. A genuine heredoc always closes with its tag; a shift / quoted `<<` / comparison never does. The new heredoc-detection condition is a strict `AND`-narrowing of the old one, so it can only make detection *less* aggressive → expose more text to the deny-on-match detectors → never a new bypass (proven by a differential old-vs-new sweep: 0 inputs newly allowed).
17
+ - **Found by** an adversarial fresh-subagent review (2026-07-15). D1/D2 were reproduced against the shipped hook, fixed, and RED-proven; the review's follow-up (`$[…]` / `let` / quoted `<<`, all the same class) folded into the same terminator-guard fix and a second review round confirmed the invariant holds.
18
+ - **Also — multi-window refresh note.** `/claudemd-refresh`, `refresh-plugin.sh`, and README §Update now tell users to run `/reload-plugins` in **other open Claude Code windows**: a refresh removes the old versioned plugin-cache dir those sessions pinned their hook paths to at startup, so they error on every hook event (enforcement absent) until reloaded.
19
+ - **Residuals** (documented in the hook; deliberate-crafting territory that does not clear the "ordinary mistake" bar, so not chased): indirect-name rebind (`unset "$T"`, `trap 'S=' DEBUG`), and a fake heredoc whose tag is repeated as a bare line to acquire a coincidental terminator (present in the original code too — this fix does not widen it). `IFS=/; rm -rf $SP/build` was probed and denies.
20
+ - **Tests**: pre-bash-safety corpus +17 rows (10 deny + 7 FP-pass controls) in `tests/fixtures/bash-safety/corpus.tsv`, all RED-proven against the committed pre-fix hook (347→357 with the fix). Full `npm test` green.
21
+
22
+ ## [0.48.0] - 2026-07-15
23
+
24
+ **Minor — one-command plugin refresh: `/claudemd-refresh`.** Closes the update-UX gap: the upgrade banner used to teach a 4-command paste sequence; now it names one command. Detection (v0.4.0 `upstream_check`, 24h-throttled `git ls-remote`) and post-refresh spec/manifest sync (version-sync hook / SessionStart bootstrap) were already automatic — this release ships the missing middle step.
25
+
26
+ - **New `scripts/refresh-plugin.sh` + `/claudemd-refresh`**: `claude plugin marketplace update claudemd` → `claude plugin uninstall claudemd@claudemd -y` → `claude plugin install claudemd@claudemd` in one shot (`set -euo pipefail`; loud exit 1 when the `claude` CLI is not on PATH). Restart Claude Code afterwards — nothing else needed; `/claudemd-install` was never part of the update flow. Replaces the author's local-only untracked `update.sh`.
27
+ - **Banner copy**: upgrade banner and stale-registration banner now say "run /claudemd-refresh, then restart Claude Code" instead of listing 4 commands. Sweep also covers `commands/claudemd-update.md`, `scripts/install.js` refusing-downgrade message, both `scripts/doctor.js` fix strings, and README (§Update leads with the one-command path).
28
+ - **Migration**: nothing to do. The manual 4-command sequence still works and stays documented in README §Update as the fallback. Banner opt-out unchanged: `DISABLE_UPSTREAM_CHECK=1` (via `/claudemd-toggle`).
29
+ - **Tests**: +3 (`tests/scripts/refresh-plugin.test.js` — PATH-shim CLI: 3-call order, fail-stops-pipeline, missing-CLI loud failure; controls-first pair up front). Banner assertions in `tests/hooks/session-start.test.sh` Cases 8/18 re-pinned RED→GREEN. Suite: node 681 → 684 tests; full `npm test` green.
30
+
11
31
  ## [0.47.4] - 2026-07-15
12
32
 
13
33
  **Patch — `package.json` had been stale since v0.47.0, and it is the file that decides what the installed manifest reports.** No hook-code change; §8 behavior identical to v0.47.3.
package/README.md CHANGED
@@ -58,7 +58,7 @@ Verify in one command (Linux): `node --version && jq --version && gh --version &
58
58
  | Layer | Contents |
59
59
  |---|---|
60
60
  | 16 shell hooks | `banned-vocab-check` · `pre-bash-safety-check` · `ship-baseline-check` · `residue-audit` · `memory-read-check` · `memory-prompt-hint` · `mid-spine-yield-scan` · `sandbox-disposal-check` · `session-start-check` · `session-extended-read` · `session-summary` · `session-end-check` · `transcript-vocab-scan` · `transcript-structure-scan` · `version-sync` · `mem-audit` |
61
- | 15 slash commands | `/claudemd-install` · `/claudemd-status` · `/claudemd-update` · `/claudemd-audit` · `/claudemd-toggle` · `/claudemd-doctor` · `/claudemd-analyze` · `/claudemd-uninstall` · `/claudemd-rules` · `/claudemd-clean-residue` · `/claudemd-sparkline` · `/claudemd-sampling-audit` · `/claudemd-bypass-audit` · `/claudemd-design-adopt` · `/claudemd-statusline` |
61
+ | 16 slash commands | `/claudemd-install` · `/claudemd-status` · `/claudemd-update` · `/claudemd-refresh` · `/claudemd-audit` · `/claudemd-toggle` · `/claudemd-doctor` · `/claudemd-analyze` · `/claudemd-uninstall` · `/claudemd-rules` · `/claudemd-clean-residue` · `/claudemd-sparkline` · `/claudemd-sampling-audit` · `/claudemd-bypass-audit` · `/claudemd-design-adopt` · `/claudemd-statusline` |
62
62
  | 1 standalone CLI | `claudemd-cli lint` · `claudemd-cli audit` ([npm: `claudemd-cli`](https://www.npmjs.com/package/claudemd-cli)) |
63
63
  | Spec v6.19 | `~/.claude/CLAUDE.md` · `CLAUDE-extended.md` · `CLAUDE-changelog.md` · `OPERATOR.md` (backup-before-overwrite) |
64
64
  | StatusLine (opt-out) | PS1-style line — `user@host:dir (branch) Model [ctx:N% · 5h:N% · 7d:N%]` (`dir` = cwd basename; context / 5-hour quota / weekly quota, all **used %**, read from Claude Code's `rate_limits` payload; quota segments auto-hide when the data is absent, or force-hide with `DISABLE_STATUSLINE_QUOTA=1`) — wired into `~/.claude/settings.json` on install **only when the slot is empty**; an existing statusline is left untouched. Skip entirely with `CLAUDEMD_NO_STATUSLINE=1`. Manage via `/claudemd-statusline`. |
@@ -108,6 +108,7 @@ Per-hook timeout (3-5s in `hooks.json`); timeout = treated as exit 0 (pass) per
108
108
  | `/claudemd-install` | Bootstrap the current session right after `/plugin install` (copy spec into `~/.claude/`, write manifest, evict legacy entries). Idempotent. CC does not fire `postInstall`, so without this command `install.js` waits until the next `SessionStart`. |
109
109
  | `/claudemd-status [--verbose]` | Plugin version + spec version + kill-switch state + logs line count. `--verbose` adds per-hook env-var × event × effective vs persisted state table + 5 escape-token reference. |
110
110
  | `/claudemd-update` | Interactive diff against plugin-shipped spec, then apply-all or cancel (4-file spec set is lockstep — per-file select would dangle §EXT cross-references). |
111
+ | `/claudemd-refresh` | v0.48.0 — one-shot plugin refresh (marketplace update → uninstall → install via the `claude` CLI). Restart Claude Code afterwards; spec + manifest sync is automatic. Fired by the SessionStart upgrade banner. |
111
112
  | `/claudemd-audit [N]` | Aggregate rule-hits over last N days (default 30). Top banned-vocab patterns, per-hook deny counts. Slash form takes a bare number (`/claudemd-audit 90`); direct script invocation takes `--days=N` (= form only). |
112
113
  | `/claudemd-toggle <hook-name>` | Enable/disable a specific hook by toggling `DISABLE_*_HOOK` in `settings.json` env. |
113
114
  | `/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). |
@@ -242,7 +243,13 @@ export CLAUDEMD_PATH2_DRY_RUN=1 # v0.21.1+ — Path 2 observability m
242
243
 
243
244
  ## Update
244
245
 
245
- Claude Code has **no** `/plugin update` slash command — it's silently ignored as unrecognized. The canonical upgrade sequence is:
246
+ Claude Code has **no** `/plugin update` slash command — it's silently ignored as unrecognized. The canonical upgrade is one command (v0.48.0+):
247
+
248
+ ```
249
+ /claudemd-refresh # marketplace update → uninstall → install, then restart
250
+ ```
251
+
252
+ Manual fallback (e.g. the `claude` CLI is not on PATH):
246
253
 
247
254
  ```
248
255
  /plugin marketplace update claudemd # refresh local marketplace clone (git fetch)
@@ -253,6 +260,8 @@ Claude Code has **no** `/plugin update` slash command — it's silently ignored
253
260
 
254
261
  Or open the interactive UI via `/plugin` → **Installed** tab → select `claudemd` → follow upgrade prompts.
255
262
 
263
+ > **Other open Claude Code windows:** run `/reload-plugins` in each of them too. A refresh removes the old versioned plugin-cache dir, but every already-running session pinned its hook paths to that dir at startup — those windows error on every hook event (claudemd enforcement is off there) until they reload or restart.
264
+
256
265
  After the plugin upgrade, sync the shipped spec into `~/.claude/`:
257
266
 
258
267
  ```
@@ -312,7 +321,7 @@ The slash command and the script are equivalent — the slash command just suppl
312
321
 
313
322
  Verify with `/claudemd-status` — the "log.lines" count should increment after the next hook fires.
314
323
 
315
- **`/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`.
324
+ **`/plugin update claudemd` does nothing / empty stdout** — `/plugin update` is not a valid Claude Code slash command; CC silently ignores unrecognized commands. Use `/claudemd-refresh` — or the manual 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`.
316
325
 
317
326
  **`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.
318
327
 
@@ -335,7 +344,7 @@ claudemd/
335
344
  │ └── marketplace.json # marketplace catalog entry
336
345
  ├── hooks/ # 16 shell hooks + hooks/lib/ (hook-common, rule-hits, platform)
337
346
  │ └── hooks.json # authoritative hook registration (v0.1.5+); CC expands ${CLAUDE_PLUGIN_ROOT} here
338
- ├── commands/ # 15 slash-command markdown files
347
+ ├── commands/ # 16 slash-command markdown files
339
348
  ├── bin/ # standalone CLI entrypoint (claudemd-lint.js → `npx claudemd-cli` on npmjs.org)
340
349
  ├── scripts/ # 18 Node.js scripts + scripts/lib/ (single-source registry, lint, etc.)
341
350
  ├── spec/ # shipped v6.19.0 CLAUDE*.md trio + OPERATOR.md + hard-rules.json manifest
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudemd-cli",
3
- "version": "0.47.4",
3
+ "version": "0.48.1",
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": {