Skip to content

claudemd 0.111.0 — fixes from playing the plugin as a user, spec unchanged (v8.1.0)

Choose a tag to compare

@sdsrss sdsrss released this 17 Oct 01:00
· 175 commits to main since this release

Upgrade notes — fixes from playing the plugin as a user; two claudemd-cli exit codes change; spec unchanged (v8.1.0). Plugin users have nothing to do. Two default behaviours of the published claudemd-cli change. claudemd-cli lint "" (an empty or blank positional) now exits 2 instead of 0: a .git/hooks/pre-commit running claudemd-cli lint "$1" gets no $1, scanned the empty string and let every commit through, so if your hook is a pre-commit hook, move the same line to .git/hooks/commit-msg, which git hands the message file. claudemd-cli audit a.jsonl b.jsonl now exits 2 instead of scanning the first file and ignoring the rest, so a CI step that passes a glob runs the command once per file and stops at the first file with hits: for f in logs/*.jsonl; do claudemd-cli audit "$f" || exit; done (without || exit, a loop exits with the last file's status, unless the shell runs with -e). To keep the old exit codes, pin the CLI to 0.110.0 (npm i -D claudemd-cli@0.110.0). To take the plugin back to 0.110.0, follow docs/ROLLBACK.md ("Local machine needs the previous version back"): its installer refuses a downgrade unless CLAUDEMD_ALLOW_DOWNGRADE=1 is set.

Why. Five read-only agents installed, used and uninstalled the plugin in sandbox HOMEs the way a user would, on 0.110.0 plus one offline-eval fix. They found two P0s (a personal CLAUDE.md backup that six installs could push out of existence; clean-residue --apply deleting through a symlink) and thirteen P1s. Each fix below was reproduced first with a test that failed; two blind reviews of the fixes found 8 Medium and 17 Low issues in them, and the Mediums are repaired here. Working record: tasks/e2e-user-paths-2026-10-16.md (local).

What changes for you.

  • Restoring your own CLAUDE.md works after a default uninstall: the advice in install's warning, the SessionStart banner and README is now /claudemd-uninstall restore followed by /plugin uninstall claudemd@claudemd. The old advice, CLAUDEMD_SPEC_ACTION=restore /claudemd-uninstall, does not start with /, so it was not a slash command, and a restore while the plugin stayed installed was undone at the next session start. The uninstall command now takes a restore argument. A restore no longer overwrites a home CLAUDE.md that is not the spec and differs from the backup (your edits after an earlier restore), and lists it in a new kept field.
  • Deleting the spec without a manifest (after a default uninstall, or with a corrupt manifest) removes only the spec files whose first line is the # AI-CODING-SPEC heading and only the spec-modules/ files claudemd generated (their # AI-CODING-SPEC … — module: <name> heading), and lists the rest in kept. Before, it either deleted a personal CLAUDE.md or did nothing while answering as if done.
  • Install decides per file whether a home CLAUDE-extended.md, CLAUDE-changelog.md or OPERATOR.md is claudemd's (it carries the spec heading or equals the shipped bytes; a UTF-8 byte-order mark is skipped) or yours (backed up where restore finds it). It no longer writes a new backup of byte-identical files on every install while CLAUDE.md is missing, which is how the sixth such install pruned the backup holding your notes.
  • SessionStart banners. A failed first install says "install failed", not "upgrade failed", and shows install's own reason (for example invalid JSON in settings.json) to you only: the model is told a cause was shown, because the parser's message can quote part of settings.json. With a reason it drops the /claudemd-refresh advice, which cannot fix that file. The session-summary line says "last 24 h, all sessions" when that is the window it counted, where it said "since last turn".
  • /claudemd-toggle adds a note when the hook is one of the opt-in ones (EVIDENCE_GATE, REWORK_BREAKER, …) and its opt-in variable is not set to a value that turns it on: clearing the kill switch alone leaves such a hook inactive, and the old output said "enabled".
  • /claudemd-sparkline counts the hooks' own *-advisory events and reply-language-restate (230 rework-advisory rows in 30 days on the maintainer's machine were missing), takes each bucket's rate over the days the log actually covers, and, when no older bucket is covered for at least half its span (at most a week), prints no arrow except ↘ (silenced) for a rule that fired before and not since. On a log shorter than the oldest window every rule read ↗ before, falling ones included.
  • /claudemd-rules: rule rows gain hits.fired, and the demote lists read it instead of hits.total. A check that found nothing (pass, pending-no-baseline, an npx allowed by a lockfile) and a banner repeating a checkpoint are not firings; a push let through because the commit carries known-red baseline:, and an rm -rf on a var the agent guarded or made with mktemp, are. Before, §7-ship-baseline and §11-session-exit could not become demote candidates while pushes or banners continued. Today's candidates on the maintainer's log are unchanged.
  • /claudemd-clean-residue does not descend a claude-<uid> entry of ~/.claude/tmp that is a symlink, and does not scan ~/.claude/tmp at all when the directory itself is a symlink (claudeTmp.skipped: "symlink", with a line on stderr saying why). Every stale entry there is a candidate whatever its name, so --apply deleted old files in the link's target, which need not be Claude Code's.
  • Memory gates (memory-read-check, memory-prompt-hint, and banned-vocab's prose scan) find MEMORY.md and the transcript under the directory the session started in (CLAUDE_PROJECT_DIR), not the current cd. After a cd into a subdirectory they used to find neither and let everything through. Replaying the real commit, push and publish calls made from a subdirectory on the maintainer's machine (19 with the author's filter, 127 with the pre-tag reviewer's): 2 now denied in both, each as it would be from the project root, 0 newly allowed. A session started in a subdirectory or a linked worktree is still not covered (Claude Code keys memory/ by the git root there).
  • /claudemd-doctor names origin, upstream, master, remote, develop, staging and production as memory tags too generic to keep: memory-read-check matches tags against a push command's arguments, and a memory tagged origin denied git push origin master in two real sessions.
  • evidence-gate (opt-in): its Done-claim pattern read \n as the letter n, so a sentence such as "…the CHANGELOG section Main Changes: or Fixes?" counted as a report label. Over 13,665 real assistant messages the old and new patterns match the same 349.
  • claudemd-cli audit skips a null content block and counts a non-array content in its integrity warning, where it crashed with a stack trace and exit 1 (its "hits found" code).
  • §8 (pre-bash-safety): verdicts unchanged. npx <pkg>@latest / @next / @beta / @alpha, and a range that starts with a digit (@1, @1.x), stay allowed as pinned, and are now recorded as accepted residual family F17 in docs/S8-RESIDUALS.md: 0 of the 4,696 package-runner calls in the maintainer's transcripts used a dist-tag or a range, and the deny's own remedy would pin the version the tag resolves to today, running the same code.
  • Offline A/B harness (maintainers): runs no longer inherit claudemd's hook switches from the parent environment; an arm sets them with --env=NAME=value.

Tests.

  • Every fix above came with a test that failed before it; each commit message names the test and the failure it showed.
  • Run as a user in sandbox HOMEs on the release tree: 13 of 13 checks of the first-round fixes, 16 of 16 of the review repairs, and 5 of 5 upgrade paths in which the released 0.110.0 writes the state and this version reads and acts on it (a 0.110.0 personal backup restores; reinstalling over 0.110.0's spec adds no backup; a confirmed delete after the upgrade removes the spec; a hand-written OPERATOR.md and a personal CLAUDE.md restore after installs by both versions; a 0.110.0 failure sentinel without a reason reads correctly).
  • The downgrade in the upgrade notes, in a sandbox HOME with this tree installed as 0.111.0: 0.110.0's installer exits 1 with "refusing downgrade" without the variable and exits 0 with it, leaving the 0.110.0 manifest. claudemd-cli on 0.110.0 → this tree: lint "" exit 0 → 2, audit a.jsonl b.jsonl exit 0 → 2.
  • On b744fd3 (the code before the review repairs; 49d6286 adds the version and this entry): npm run check exit 0 with a built bash 3.2.57 (node 1,523 of 1,524 passed, 1 skipped because SHFMT is not set; bash 2,364 of 2,364; all 44 hook suites also executed under bash 3.2; shellcheck clean on 94 files); npm run smoke 2/2. On 49d6286: the same, exit 0, with SIGPIPE ignored; CI 7 of 7 jobs green (run 38983895325).

Pre-tag review. Round 1, two reviewers in parallel on b04c288..49d6286: one checked this entry and every other place its subjects are described, one the twelve commits of the last three converge rounds, which no review had seen. 0 Critical, 0 High; 4 Medium and 19 Low across the two reports, three of them the same defect found twice. The Mediums: a delete without a manifest also removed a personal CLAUDE.md whose # AI-CODING-SPEC line was not its first (the test read the first 256 characters), and every file in spec-modules/ whatever it was (found by both); and the loop this entry first recommended for CI exited with the last file's status. Repaired: the first-line and module-heading tests above (df30fe6), the loop, the known-red-style rows for guarded rm -rf (d70f077), F17's ranges (5fea188), a trailing / getting past the symlink skip and a timing gap in a hook test (892748d), the hook-less install refusal now naming its fix (fc920d6), and stale text in README, docs/, command docs, the CLI header and three comments (000c027 and this entry). Not changed: a no-manifest delete or a restore still treats a spec file with the user's notes appended as the spec, as the manifest path always has; the state-file pattern's digit suffix also matches a date-stamped copy a user might keep in claudemd's state dir, as hook-root.json's has; spec/OPERATOR.md still says "0 hits" (spec text is not edited in this release); the uninstall command's description still names the variables the script reads. Round 2, a new reviewer on the repairs (49d6286..cc75fa4): 0 Critical, 0 High, 0 Medium, 10 Low. It checked the new ownership tests against every release tag (211 shipped module files and 1,013 spec files, none read as the user's), showed each repair's test red on the old code, and ran the CI loop under dash, bash 5 and bash 3.2 with and without -e. Its text findings are fixed with no further review, as this repository does after the second round: an empty --stdin or --file input still exits 0, so only an empty or blank text argument exits 2; a symlink in spec-modules/ is left in place and not listed in kept; and stale wording in the uninstall and rules command docs, README, docs/ and code comments. Two are left for a later release: the first-line test also accepts # followed by a newline and then the heading text, as the old test did, and the module test accepts a quoted module heading on any of the first lines, in a directory install already clears of files it did not ship. One sentence of df30fe6's message overstates the change: a restore with a manifest also keeps a home file whose first line is not the heading, the same direction as the rest.