claudemd-cli 0.68.0 → 0.68.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 (2) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -8,6 +8,17 @@ 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.68.1] - 2026-08-16
12
+
13
+ Patch found by dogfooding the *user journey* rather than the scripts: a sandbox HOME, a versioned marketplace cache dir, real event JSON piped into the hooks from that dir, and the detached background bootstrap polled for. Sixteen phases — install, health check, enforcement, auto-upgrade, stale-registration, upstream notice, self-heal, uninstall, collision with another plugin, truncated cache, degraded machine, kill switches, concurrency — land as `tests/integration/user-journey.test.sh`. Two of the three defects share one root: a precondition validated at its *point of use*, deep inside a mutation sequence, instead of in the pre-flight block that exists for exactly that. No spec change; no behavior change on any healthy install.
14
+
15
+ - **fix: an unparseable `~/.claude/settings.json` left a half-installed claudemd that never self-healed.** `install.js` pre-flight validated the PLUGIN side — spec files complete, `hooks/hooks.json` present and non-empty — under a comment reading *"Fail before touching anything the user owns"*. The one USER-side precondition was not in that block: `readSettings()` ran ~80 lines later, **after** `createBackup()` had `renameSync`'d the user's personal `~/.claude/CLAUDE.md` into `backup-<ts>/` and the spec had overwritten it. A trailing comma from a hand-edit — the common shape for this file, which is shared real estate with Claude Code's own settings — therefore produced: personal `CLAUDE.md` moved, spec installed, **manifest never written**. The manifest is what the SessionStart bootstrap keys on, so every subsequent session re-spawned the same doomed background install. The check now sits with its two siblings, before anything moves. What changes for the user: no file of theirs is touched by the refused install, and the message naming the actual repair reaches `~/.claude/logs/claudemd-bootstrap.log` — the file the in-session banner already points at. The banner text itself is unchanged and still leads with `/claudemd-refresh`, which does not fix JSON syntax; `/claudemd-doctor` names the real cause, as it did before. RED-verified: pre-fix the refused install overwrote the personal file and left an orphan `backup-*` dir; post-fix both are untouched and repairing the JSON is sufficient to get unstuck.
16
+ - **fix: the same file made the plugin impossible to uninstall.** `uninstall.js` called `readSettings()` at the top of its side-effect sequence, so the same trailing comma aborted the whole run: exit 1, manifest still present, state dir still present, spec disposition never reached — with an error that named the file but offered no way forward. Since v0.1.5 the hooks live in the plugin's own `hooks/hooks.json` and `settings.json` normally carries **zero** claudemd entries, which makes that eviction the least load-bearing step in the function. It now degrades to a **reported** skip — new `settingsWarning` field in the JSON result (an additive field, hence still a patch) plus a stderr `[claudemd] WARN:` line — and the manifest / state / spec disposition completes. The user's unparseable file is left byte-for-byte as they wrote it; a silent skip would have been worse than the abort. The pre-tag review caught that the first cut of this was **honest about the wrong half**: `removeStatusline()` reads the same file and fails on the same input, so claudemd kept owning the statusLine while a warning that named only "hook entries" implied otherwise. Both residues are now named, and a `statusline.action === 'error'` — which previously existed only in the returned JSON, surfaced nowhere on the human path — emits its own stderr WARN. `commands/claudemd-uninstall.md` was pointing the slash command at a field name that does not exist (`warning`, not `settingsWarning`) and is corrected to enumerate all three, with the note that `specAction: "keep"` alone does not mean the uninstall was complete.
17
+ - **fix: `X=$(cd "$(mktemp -d)" && pwd -P)` fails OPEN, and five tracked shell files used it.** The one-liner reads as "make a sandbox, resolve its physical path" (the `pwd -P` is needed because macOS `mktemp` returns a `/var` symlink). But when `mktemp` fails, the inner substitution is empty, `cd ""` is a bash **no-op returning 0**, and `pwd -P` prints the *current* directory — so the variable becomes the repo root and the suite's own `rm -rf "$X"` or EXIT trap deletes the working tree. A `[[ -n "$X" ]]` guard is inert against it: the string is non-empty, just wrong. Verified: `TMPDIR=/nonexistent bash -c 'X=$(cd "$(mktemp -d)" && pwd -P); echo "$X"'` prints the cwd. The trigger is not hypothetical — this repo's own `/tmp`-writes gate cites *"Read-only file system wherever /tmp is not writable (agent sandbox, hardened CI image)"* as an observed condition, and `mktemp` honors `TMPDIR`. It is also the exact §8 clause the plugin's `pre-bash-safety-check.sh` denies for everyone else. Three of the five sites pre-date this release (`upgrade-lifecycle.test.sh` ×2, `session-end-check.test.sh`); all five are now two statements with an explicit `|| exit 1`, and `run-all.sh` gains a class gate over every tracked `.sh` so the next person reaching for the one-liner is stopped. Control-verified: the gate fires on a planted occurrence and ignores it inside a comment.
18
+ - **fix: every `npm test` run leaked 4 sandbox dirs into the user's real `TMPDIR`.** Two in `sampling-audit.test.js` (DRIFT-1 / DRIFT-1b `mkdtempSync` with no disposal at all, while the test immediately above them disposes in a `finally`), two in `toggle.test.js` — where the `mkdtempSync` was inlined into a `spawnSync` env literal, so the path was never bound to a name and could not be removed even in principle. They accumulate forever; 48 had piled up in one working scratchpad. This is the §8.V4 residue class the plugin's own Stop hook exists to flag, shipped inside the plugin's own test suite.
19
+ - **test: `run-all.sh` now runs the Node leg under an isolated `TMPDIR` and fails on anything left behind.** Counting `mkdtempSync` against `rmSync` per file does **not** find this class — a `beforeEach`/`afterEach` pair covers many tests, so the counts legitimately disagree, and the two worst offenders sat in files whose ratios looked fine (`sampling-audit` read 6 vs 25). Running the suite in a known-empty dir and inventorying what survives is the only reading that means anything. Node's own `node-compile-cache` is allowlisted by **exact name**: the first cut allowlisted `node-*`, which — as the pre-tag review demonstrated with a planted `node-fixture-XXXX` — silently ignores anything a real leak could be named, the gate being wider than its stated subject. `find -printf` is deliberately avoided (GNU-only; on the macOS leg it would have produced an empty, always-passing list). Control-verified in both directions: a planted leftover turns the gate red, and the real suite went 4 leftovers → 0.
20
+ - Tests 776 → 780 (+4, 0 regressions); integration suites 3 → 4; shellcheck 54 files clean at warning+; bash 3.2 construct gate clean; fail-open-mktemp gate clean over 54 files; `version-cascade-check` ok. The new E2E suite emits 152 assertions over 16 phases. An independent pre-tag review of the staged diff returned one blocker (the fail-open `mktemp` above) and three should-fixes, all folded in here rather than deferred; it also established by reverting `install.js` + `uninstall.js` in a repo copy that the new Phase 15 genuinely goes RED on the pre-fix code, and re-derived the test counts and leak reproduction independently. Two of its findings were assertions in the new suite that **could not fail** — a residue check matching `*.tmp` when the product only ever writes `*.tmp-<pid>` / `*.tmp.<pid>` / `*.tail.$$`, at a depth the check did not reach; and a warm-vs-cold hook sweep whose comment claimed a distinction the measurements do not show. Both are corrected rather than removed.
21
+
11
22
  ## [0.68.0] - 2026-08-16
12
23
 
13
24
  End-to-end dogfooding pass driven from the *external* surfaces — the npm CLI and a real `git commit-msg` hook — rather than from the test suite. Two of the four defects were unreachable from inside Claude Code, which is why five audits had not seen them: the CLI's documented pre-commit entry point had never been exercised against a **real** `COMMIT_EDITMSG`, and the Node sanitizer had never been handed a file large enough to expose its complexity class. Minor, not patch: two banned-vocab patterns now deny shapes that previously passed (released-artifact checklist below), and `lint` gains flags.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudemd-cli",
3
- "version": "0.68.0",
3
+ "version": "0.68.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": {