Repository navigation
claudemd 0.111.0 — fixes from playing the plugin as a user, spec unchanged (v8.1.0)
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.mdworks after a default uninstall: the advice in install's warning, the SessionStart banner and README is now/claudemd-uninstall restorefollowed 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 arestoreargument. A restore no longer overwrites a homeCLAUDE.mdthat is not the spec and differs from the backup (your edits after an earlier restore), and lists it in a newkeptfield. - 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-SPECheading and only thespec-modules/files claudemd generated (their# AI-CODING-SPEC … — module: <name>heading), and lists the rest inkept. Before, it either deleted a personalCLAUDE.mdor did nothing while answering as if done. - Install decides per file whether a home
CLAUDE-extended.md,CLAUDE-changelog.mdorOPERATOR.mdis 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 whileCLAUDE.mdis 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 ofsettings.json. With a reason it drops the/claudemd-refreshadvice, 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-toggleadds anotewhen 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-sparklinecounts the hooks' own*-advisoryevents andreply-language-restate(230rework-advisoryrows 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 gainhits.fired, and the demote lists read it instead ofhits.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 carriesknown-red baseline:, and anrm -rfon a var the agent guarded or made withmktemp, are. Before,§7-ship-baselineand§11-session-exitcould not become demote candidates while pushes or banners continued. Today's candidates on the maintainer's log are unchanged./claudemd-clean-residuedoes not descend aclaude-<uid>entry of~/.claude/tmpthat is a symlink, and does not scan~/.claude/tmpat 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--applydeleted old files in the link's target, which need not be Claude Code's.- Memory gates (
memory-read-check,memory-prompt-hint, andbanned-vocab's prose scan) findMEMORY.mdand the transcript under the directory the session started in (CLAUDE_PROJECT_DIR), not the currentcd. After acdinto 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 keysmemory/by the git root there). /claudemd-doctornamesorigin,upstream,master,remote,develop,stagingandproductionas memory tags too generic to keep:memory-read-checkmatches tags against a push command's arguments, and a memory taggedorigindeniedgit push origin masterin two real sessions.evidence-gate(opt-in): its Done-claim pattern read\nas 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 auditskips anullcontent 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 indocs/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.mdand a personalCLAUDE.mdrestore 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-clion 0.110.0 → this tree:lint ""exit 0 → 2,audit a.jsonl b.jsonlexit 0 → 2. - On b744fd3 (the code before the review repairs; 49d6286 adds the version and this entry):
npm run checkexit 0 with a built bash 3.2.57 (node 1,523 of 1,524 passed, 1 skipped becauseSHFMTis 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 smoke2/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.