claudemd-cli 0.74.2 → 0.76.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/README.md CHANGED
@@ -35,7 +35,7 @@ Then bootstrap the **current** session (skip the wait-for-next-session restart)
35
35
 
36
36
  Fallback (no slash command — e.g. scripting outside CC): `node ~/.claude/plugins/cache/claudemd/claudemd/<version>/scripts/install.js`. Find `<version>` with `ls ~/.claude/plugins/cache/claudemd/claudemd/ | sort -V | tail -1`.
37
37
 
38
- > ⚠️ **`~/.claude/CLAUDE.md` is shared real estate.** Claude Code reads this file as your user-global instructions across every project. If you've hand-written personal instructions there (`Always reply in 中文`, `My name is X`, etc.), install moves your existing files to `~/.claude/backup-<ISO>/` (last 5 kept automatically) before writing the spec. Since v0.5.3, install prints a `[claudemd] WARN: …` line to stderr when the existing file does not look like a claudemd spec. To bring your personal instructions back on uninstall, run `CLAUDEMD_SPEC_ACTION=restore /claudemd-uninstall`.
38
+ > ⚠️ **`~/.claude/CLAUDE.md` is shared real estate.** Claude Code reads this file as your user-global instructions across every project. If you've hand-written personal instructions there (`Always reply in 中文`, `My name is X`, etc.), install moves your existing files to `~/.claude/backup-<ISO>/` (last 5 kept automatically) before writing the spec. Since v0.5.3, install prints a `[claudemd] WARN: …` line to stderr when the existing file does not look like a claudemd spec — visible when you run `/claudemd-install` (or `install.js`) yourself. Since v0.75.0 the **SessionStart** bootstrap, where that stderr goes to `claudemd-bootstrap.log` instead, raises the same notice as an in-session banner naming the backup path (silence it with `DISABLE_USER_CONTENT_BANNER=1`). To bring your personal instructions back on uninstall, run `CLAUDEMD_SPEC_ACTION=restore /claudemd-uninstall`.
39
39
 
40
40
  ---
41
41
 
@@ -209,6 +209,29 @@ export DISABLE_COMPACT_REREAD_REMINDER=1 # v0.27.0+ — only the post-compacti
209
209
  # source=="compact"); compact events still
210
210
  # skip bootstrap/upgrade-banner either way.
211
211
 
212
+ export CLAUDEMD_FORCE_ASYNC_BOOTSTRAP=1 # v0.75.0+ — restores the pre-0.75.0 detached
213
+ # bootstrap on the FRESH-install path (no
214
+ # manifest yet), which otherwise runs install.js
215
+ # synchronously inside the 5s SessionStart
216
+ # budget so ~/.claude/CLAUDE.md is on disk
217
+ # before Claude Code assembles context. Set it
218
+ # if a slow/networked $HOME makes that wait
219
+ # unacceptable; the cost is that the spec first
220
+ # reaches the model one session later. Upgrades
221
+ # are detached either way.
222
+
223
+ export DISABLE_USER_CONTENT_BANNER=1 # v0.75.0+ — only the SessionStart notice
224
+ # that install.js moved a hand-written
225
+ # ~/.claude/CLAUDE.md into a backup dir.
226
+ # The backup and the install-time stderr
227
+ # WARN still happen; what stops is the
228
+ # in-session banner naming the backup
229
+ # path and the restore command. The hook
230
+ # still consumes its sentinel on this
231
+ # path, so opting out leaves no residue
232
+ # and cannot fire a late banner naming a
233
+ # since-pruned backup dir.
234
+
212
235
  export DISABLE_BOOTSTRAP_FAIL_BANNER=1 # v0.50.0+ — only the SessionStart banner
213
236
  # reporting that a PRIOR session's background
214
237
  # install.js upgrade failed; the failure
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudemd-cli",
3
- "version": "0.74.2",
3
+ "version": "0.76.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": {
@@ -11,6 +11,9 @@
11
11
  // Caller catches `ArgvError` and exits 2 (distinct from numeric-validation
12
12
  // exit 1) so wrappers can tell parsing-shape errors from validation errors.
13
13
 
14
+ import fs from 'node:fs';
15
+ import { fileURLToPath } from 'node:url';
16
+
14
17
  export class ArgvError extends Error {
15
18
  constructor(message) {
16
19
  super(message);
@@ -18,6 +21,31 @@ export class ArgvError extends Error {
18
21
  }
19
22
  }
20
23
 
24
+ // invokedAsMain(import.meta.url) — "did node start the program at THIS file?"
25
+ //
26
+ // The obvious spelling compares `import.meta.url` against a `file://` string
27
+ // built from process.argv[1]. Both halves of that comparison lie:
28
+ // - node resolves import.meta.url through symlinks, argv[1] stays as typed,
29
+ // so a plugin dir reached through a link (a dotfiles ~/.claude, a
30
+ // `git worktree`, macOS /var -> /private/var) compares unequal;
31
+ // - a URL percent-encodes what a path spells literally, so one space in the
32
+ // path is `%20` on one side and ` ` on the other.
33
+ // Either way the main block does not run: the CLI exits 0 having printed
34
+ // nothing, which every caller reads as success. install.js failing this way
35
+ // returns "ok" while writing no manifest, so SessionStart re-enters the
36
+ // fresh-install branch forever (2026-09-05 audit P0-1).
37
+ //
38
+ // realpath BOTH sides and the two failure modes collapse into one comparison.
39
+ // Throwing inputs (argv[1] undefined under `node -e`, a path that no longer
40
+ // exists) mean "not the program" — false, never a crash.
41
+ export function invokedAsMain(importMetaUrl) {
42
+ try {
43
+ return fs.realpathSync(fileURLToPath(importMetaUrl)) === fs.realpathSync(process.argv[1]);
44
+ } catch {
45
+ return false;
46
+ }
47
+ }
48
+
21
49
  // Discoverability helper: when `--help` or `-h` is the first non-empty arg
22
50
  // (or anywhere in argv for scripts with no flags), print usage to stdout and
23
51
  // exit 0. Caller invokes BEFORE parseStrict so unknown-arg rejection doesn't
@@ -122,6 +150,23 @@ export function parseStrict(argv, { bools = [], values = [] } = {}) {
122
150
  }
123
151
  return out;
124
152
  }
153
+ // parseStrictOrExit — parseStrict with the exit contract every CLI in this repo
154
+ // repeated verbatim: an ArgvError is a usage error, so print the one-line reason
155
+ // and exit 2; anything else is a bug and keeps its stack. Seventeen main blocks
156
+ // spelled this out by hand (jscpd's nine largest non-test clones were all this
157
+ // block), which is seventeen chances for one of them to swallow the rethrow or
158
+ // exit with a different code than its own USAGE documents.
159
+ export function parseStrictOrExit(argv, spec = {}) {
160
+ try {
161
+ return parseStrict(argv, spec);
162
+ } catch (e) {
163
+ if (e instanceof ArgvError) {
164
+ console.error(e.message);
165
+ process.exit(2);
166
+ }
167
+ throw e;
168
+ }
169
+ }
125
170
 
126
171
  // Space-form argv validator for the published `claudemd-cli` binary.
127
172
  //