claudemd-cli 0.57.0 → 0.58.0

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 CHANGED
@@ -8,6 +8,47 @@ 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.58.0] - 2026-07-25
12
+
13
+ Fix batch from the 2026-07-25 deep four-dimension audit (`docs/comprehensive-audit-2026-07-25-v0.57.0-deep.md`, local-only per the `docs/` gitignore convention). Ships spec **v6.23.0**. Minor bump: the §8 deny surface widens across all three gates and two §7/§11 gates start firing on shapes they previously let through — user-visible default behavior change.
14
+
15
+ **What you must do**: nothing — the upgrade is drop-in. **What changes for you**: commands that previously slipped past the §8 gate now stop for confirmation (multi-target `rm -rf` with a variable, `exec`/`env -i`/`command -p`-wrapped commands, a redirection before the command word, `npm --yes exec`, `curl … |& sh`), and `git push` on its own line of a multi-line block now reaches the §7 and §11 gates it used to miss. **Opt-out** is unchanged and per-invocation: `[allow-rm-rf-var]` / `[allow-npx-unpinned]` / `[allow-curl-sh]` / `[skip-memory-check]` in the command, or `DISABLE_PRE_BASH_SAFETY_HOOK=1` / `DISABLE_SHIP_BASELINE_HOOK=1` / `DISABLE_MEMORY_READ_HOOK=1` to turn a hook off entirely. **Revert**: `/plugin install claudemd@claudemd` after pinning the marketplace entry to `v0.57.0`, or `git checkout v0.57.0 -- hooks/` in a local clone. One behavior contract also changed for scripted callers: `node scripts/doctor.js` now exits **3** when a health check fails (it always exited 0).
16
+
17
+ That audit broke a five-round streak of "production-grade, same conclusion" reports. The streak was probe homogeneity, not saturation: every prior round sampled the neighborhood of the existing corpus taxonomy, so a whole class of ordinary command spellings had never been tried.
18
+
19
+ ### §8 — one CRITICAL and four HIGH false negatives, all reproduced on the live hook
20
+
21
+ - **`rm` gate inspected only the FIRST positional target.** `rm_target` was bound once under a `[[ -z … ]]` guard, so `rm -rf ./build $VAR`, `rm -rf /tmp/a "$VAR"` and `: "${SAFE:?}" && rm -rf "$SAFE" "$EVIL"` all passed. This is the plain unvalidated-`$VAR` class the gate exists for, in an ordinary multi-target cleanup spelling. Every positional is now analyzed independently, so a validated target no longer vouches for its neighbours.
22
+ - **Wrapper taxonomy had three independent holes**, and because `s8_strip_wrappers` is correctly single-sourced across the rm / npx / curl-sh gates, each hole pierced all three at once: `exec` was absent entirely; `env` / `command` / `time` were filed ARGLESS so the first flag (`env -i`, `command -p`, `time -p`) ended the strip; and a redirection at command position (`>/tmp/log rm -rf $VAR`) was basename-canonicalized into a bare word that then read as the command. Adds `s8_wrap_optarg` for space-separated option arguments, which also retires the `sudo -u svc` and `timeout -s KILL 5` residuals.
23
+ - **npx family missed a global flag between tool and subcommand** — `npm --yes exec pkg`, `pnpm --silent dlx pkg` (both accepted by the real package managers).
24
+ - **curl-sh missed three delivery shapes**: `|&`, a sink behind a group opener (`| { bash; }`, `| (bash)`), and `bash < <(curl …)`.
25
+ - Evidence: corpus 330 → 374 rows, suite 469/469; differential scan against the pre-fix hook shows **0 verdict changes across all 330 pre-existing rows** — the widening is additive. Ten FP guards (`find … -exec rm {} +`, `npm run exec-tests`, `command -v rm`, `exec 3>&1`, mktemp-provenance cleanup, …) verified still allowed.
26
+
27
+ ### Gates that were not reading what they claimed to read
28
+
29
+ - **The heredoc-stripper fix had landed in one of three hand copies.** `memory-read-check` and `ship-baseline-check` never received pre-bash-safety's terminator lookahead, so `echo $((1<<n)) && git push` opened a phantom heredoc that swallowed the trigger — the §11 MEMORY.md gate and the §7 red-CI gate both stopped seeing it. Now a single `hook_strip_heredoc_bodies` in `hooks/lib/`. The shared version also keeps the opener line's tail, which the old copy discarded (`cat <<EOF && git push` runs that push).
30
+ - **A newline is a command separator, and both gates flattened it to a space.** Their trigger anchors require `^` or `[;&|]`, so a push on its own line of a multi-line block — the ordinary shape — reached neither gate. Shared `hook_flatten_cmd` now converts newlines to `;` and joins backslash continuations.
31
+ - **`ship-baseline` recorded an in-flight CI run as `pass`.** It read `.[0].conclusion`, which is null while a run executes, and null fell through to the pass arm. Under atomic ship that is the normal timing (pushing main starts CI; the tag push follows seconds later), so the gate reported a green baseline at exactly the moment it had no answer, and every count built on those rows was inflated. Now selects the newest *completed* run from a 5-run window; when nothing has completed it records `pending-no-baseline` instead of `pass`. Fixtures added for `in_progress`, `queued`, no-completed, and the two red conclusions (`action_required`, `startup_failure`) that the arm listed but nothing exercised.
32
+ - **`contract.test.sh` never opened `RULE-HITS-SCHEMA.md`** — the `SCHEMA` path was commented out with the note "was never read", and both invariants validated a hand-copied array whose failure message named the document. `mem-audit` had been emitting into the live log for months while absent from the schema, and the gate stayed green. It now parses the Events table and compares **(event, emitter) pairs**; matching bare event names meant a new emitter of an existing event checked out against the wrong row. Same treatment for `KNOWN_HOOK_SECTIONS` (covered 10 of ~16 sections) — now parsed from the taxonomy table.
33
+ - **`hard-rules-4` asserted the wrong invariant and locked the error in.** It required self-enforced entries to keep `rule_hits_section: null`, on a rationale ("until R-N8 lands") that expired two feature releases earlier — so five rules carried null while hooks emitted under their sections, and §13.1 demote review computed 0 hits for them *by construction*. Inverted: a rule whose section receives rows must declare it. `hard-rules-8` widens from deny-verbs to every emitted section. `hard-rules-audit.js` keys `hits` off the declared section rather than `enforcement`, so §11-session-exit (27 rows/30d) and §11-post-compaction (50) are visible for the first time.
34
+ - **`run-all.sh`'s repo-write guard checked only the commit count.** A suite that modified a tracked file or dropped an untracked artifact never commits, so the counter matched and the guard passed — on the atomic-ship path that artifact lands in the release commit. Now snapshots `git status --porcelain` too.
35
+ - **`npm run test:hooks` ran 1 of 23 suites and exited 0** (`bash tests/hooks/*.test.sh` treats the first glob match as the script and the rest as arguments).
36
+
37
+ ### Lifecycle and instruments
38
+
39
+ - **`install.js` accepted a plugin cache with no `hooks/hooks.json`**, registering zero hooks and reporting success; because the manifest version still matched, the SessionStart bootstrap read that as healthy and never retried. Now validated alongside the spec files, before anything user-owned is touched, with malformed-JSON and zero-hook cases covered.
40
+ - **`uninstall --purge` left three claudemd-owned files** in `~/.claude/logs/` (`claudemd.jsonl.1`, `.2`, `claudemd-bootstrap.log`), which also kept the empty-directory check from firing.
41
+ - **`doctor` always exited 0** — 4 of 42 failing checks still reported success, so anything gating on it was a no-op. Now exits **3** (not 1, which already means "argv rejected"). Its liveness kill-switch names come from `HOOK_REGISTRY` instead of a fourth parallel list, and a hook that exits at its kill-switch guard no longer counts as "ran clean".
42
+ - **`lesson-bypass-audit` joined a global log against a single project directory**, discarding 76% of the population as "missing transcript". Resolving each row against its own `project` field takes the measurable set from 57 to 165 suggestions; cite-recall moves 0.75 → 0.61 on the corrected denominator.
43
+ - **`sampling-audit --days` filtered file mtime only**, never turn timestamps, so a resumed session contributed every turn it ever held to a window that claimed otherwise — and `audit.js#selfCompliance` republished that number.
44
+ - **`version-cascade-check` passed a missing `spec/CLAUDE-extended.md`** with `ok: true` while the sibling unreadable-spec case failed. `spec-coherence-audit` had a second copy of the MEMORY.md link regex that took every match per line, which `memory-tags.js` documents as the bug it fixed.
45
+ - `npm-publish` now gates on a node 20 + 22 matrix with `fetch-depth: 0` before `publish` runs; previously the publish gate ran node 20 only (so the node:sqlite path was invisible to it) with a shallow checkout that made `upgrade-lifecycle` loud-skip, and had no relationship to `ci.yml`.
46
+ - Node test suites are scrubbed by `env-hygiene` when run via `npm run test:scripts`, not only under `run-all.sh`.
47
+
48
+ ### Spec v6.23.0
49
+
50
+ Detail in `spec/CLAUDE-changelog.md`. Headline: core no longer claims a Stop hook enforces §10's four-section order (it is advisory, opt-in, default-OFF, and cannot block); the §5.1 `aggressive` contradiction between core and §5.1-EXT is resolved in the stricter direction; §3's Order line ranks harness / MCP / skill instructions; the evidence ladder and cold-start are tagged L2+ where core points at them; §0.1's cap is stated in the bytes it has always been measured in and its headroom pointer names the file that holds the live Sizing line. `hard-rules.json` gains `§11-post-compaction` and declares the sections four self-enforced rules already emit under. Paired net-delete per §0.1: C7, C8, C12 — core 24714 → 24574B.
51
+
11
52
  ## [0.57.0] - 2026-07-25
12
53
 
13
54
  Closes the 2026-07-25 audit's remaining open items — five decided as "do", three explicitly closed as "won't do" (see `tasks/audit-2026-07-25-deferred.md`). Spec unchanged (**v6.22.0**). Minor bump: an opt-in hook is removed.
package/README.md CHANGED
@@ -1,12 +1,12 @@
1
1
  # claudemd
2
2
 
3
- > A **personal AI-coding discipline harness**: one developer's opinionated **AI-CODING-SPEC v6.22**, encoded as Claude Code shell hooks and shipped with the plugin. Built and dogfooded on my own repos — fork and adapt, don't adopt wholesale.
3
+ > A **personal AI-coding discipline harness**: one developer's opinionated **AI-CODING-SPEC v6.23**, encoded as Claude Code shell hooks and shipped with the plugin. Built and dogfooded on my own repos — fork and adapt, don't adopt wholesale.
4
4
 
5
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
6
  [![npm](https://img.shields.io/npm/v/claudemd-cli.svg)](https://www.npmjs.com/package/claudemd-cli)
7
7
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
8
8
 
9
- claudemd plugs into the Claude Code hook system to **block commits, pushes, and bash commands** that violate AI-CODING-SPEC v6.22 — banned vocabulary in commit messages, `rm -rf $VAR` without variable validation, ship-on-red-CI, unread `MEMORY.md` entries during release flows, and more. The spec itself (`CLAUDE.md` + `CLAUDE-extended.md` + `CLAUDE-changelog.md` + `OPERATOR.md`) ships with the plugin and installs into `~/.claude/`, so the rules Claude Code reads at session start match the rules the hooks enforce. (`OPERATOR.md` is the human-only spec-maintenance handbook — Agent-loaded files are the CLAUDE trio.)
9
+ claudemd plugs into the Claude Code hook system to **block commits, pushes, and bash commands** that violate AI-CODING-SPEC v6.23 — banned vocabulary in commit messages, `rm -rf $VAR` without variable validation, ship-on-red-CI, unread `MEMORY.md` entries during release flows, and more. The spec itself (`CLAUDE.md` + `CLAUDE-extended.md` + `CLAUDE-changelog.md` + `OPERATOR.md`) ships with the plugin and installs into `~/.claude/`, so the rules Claude Code reads at session start match the rules the hooks enforce. (`OPERATOR.md` is the human-only spec-maintenance handbook — Agent-loaded files are the CLAUDE trio.)
10
10
 
11
11
  A standalone CLI (`npx claudemd-cli`) reuses the same `banned-vocab.patterns` source for git pre-commit hooks, GitHub Actions, and other agents that don't run inside Claude Code.
12
12
 
@@ -60,7 +60,7 @@ Verify in one command (Linux): `node --version && jq --version && gh --version &
60
60
  | 15 shell hooks | `banned-vocab-check` · `pre-bash-safety-check` · `ship-baseline-check` · `residue-audit` · `memory-read-check` · `memory-prompt-hint` · `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
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
- | Spec v6.22 | `~/.claude/CLAUDE.md` · `CLAUDE-extended.md` · `CLAUDE-changelog.md` · `OPERATOR.md` (backup-before-overwrite) |
63
+ | Spec v6.23 | `~/.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`. |
65
65
 
66
66
  Install backs up a hand-written `~/.claude/CLAUDE.md` (any file without the `# AI-CODING-SPEC` H1) to `~/.claude/backup-<ISO>/` before overwriting (last 5 kept automatically). An already-installed claudemd spec is overwritten **without** a backup — deliberate (v0.23.11): the sole backup is always your own content, so `restore` can never return a stale spec instead. Uninstall offers `keep / restore / delete`; `delete` requires an extra confirmation.
@@ -352,7 +352,7 @@ claudemd/
352
352
  ├── commands/ # 16 slash-command markdown files
353
353
  ├── bin/ # standalone CLI entrypoint (claudemd-lint.js → `npx claudemd-cli` on npmjs.org)
354
354
  ├── scripts/ # 18 Node.js scripts + scripts/lib/ (single-source registry, lint, etc.)
355
- ├── spec/ # shipped v6.22.0 CLAUDE*.md trio + OPERATOR.md + hard-rules.json manifest
355
+ ├── spec/ # shipped v6.23.0 CLAUDE*.md trio + OPERATOR.md + hard-rules.json manifest
356
356
  ├── tests/ # hook shell tests + Node.js tests + integration + fixtures
357
357
  ├── docs/ # ADDING-NEW-HOOK.md + RULE-HITS-SCHEMA.md + superpowers/
358
358
  └── .github/workflows/ # ci.yml (ubuntu+macOS × node 20) + npm-publish.yml (tag-triggered)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudemd-cli",
3
- "version": "0.57.0",
3
+ "version": "0.58.0",
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": {
@@ -23,8 +23,8 @@
23
23
  ],
24
24
  "scripts": {
25
25
  "test": "bash tests/run-all.sh",
26
- "test:scripts": "node --test tests/scripts/*.test.js",
27
- "test:hooks": "bash tests/hooks/*.test.sh",
26
+ "test:scripts": "bash -c 'source tests/lib/env-hygiene.sh && claudemd_reset_test_env && node --test tests/scripts/*.test.js'",
27
+ "test:hooks": "for t in tests/hooks/*.test.sh; do bash \"$t\" || exit 1; done",
28
28
  "lint:argv": "node scripts/lint-argv.js",
29
29
  "version-check": "node scripts/version-cascade-check.js"
30
30
  },