claudemd-cli 0.75.0 → 0.76.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudemd-cli",
3
- "version": "0.75.0",
3
+ "version": "0.76.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": {
@@ -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
  //