claudemd-cli 0.88.0 → 0.90.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 +12 -2
- package/package.json +2 -1
- package/scripts/lib/lint.js +36 -6
package/README.md
CHANGED
|
@@ -57,7 +57,7 @@ Verify in one command (Linux): `node --version && jq --version && gh --version &
|
|
|
57
57
|
|
|
58
58
|
| Layer | Contents |
|
|
59
59
|
|---|---|
|
|
60
|
-
|
|
|
60
|
+
| 17 shell hooks | `banned-vocab-check` · `pre-bash-safety-check` · `ship-baseline-check` · `residue-audit` · `memory-read-check` · `memory-prompt-hint` · `sandbox-disposal-check` · `session-start-check` · `session-extended-read` · `session-summary` · `session-end-check` · `transcript-vocab-scan` · `rework-breaker` · `evidence-gate` · `transcript-structure-scan` · `version-sync` · `mem-audit` |
|
|
61
61
|
| 16 slash commands | `/claudemd-install` · `/claudemd-status` · `/claudemd-update` · `/claudemd-refresh` · `/claudemd-audit` · `/claudemd-toggle` · `/claudemd-doctor` · `/claudemd-analyze` · `/claudemd-uninstall` · `/claudemd-rules` · `/claudemd-clean-residue` · `/claudemd-sparkline` · `/claudemd-sampling-audit` · `/claudemd-bypass-audit` · `/claudemd-design-adopt` · `/claudemd-statusline` |
|
|
62
62
|
| 1 standalone CLI | `claudemd-cli lint` · `claudemd-cli audit` ([npm: `claudemd-cli`](https://www.npmjs.com/package/claudemd-cli)) |
|
|
63
63
|
| Spec v6.30 | `~/.claude/CLAUDE.md` · `CLAUDE-extended.md` · `CLAUDE-changelog.md` · `OPERATOR.md` (backup-before-overwrite) |
|
|
@@ -87,6 +87,8 @@ Once installed, hooks run silently in the background. Verbose log: `~/.claude/lo
|
|
|
87
87
|
| New session start with a `~/.claude/CLAUDE*.md` / `OPERATOR.md` that is edited, or **missing** | `session-start-check` (v0.84.0+ for the missing half) | Two banners, because the fixes differ: an edited file says `/claudemd-update`, a deleted one says `/claudemd-install`. A deleted spec is the louder case — Claude Code reads these as your user-global instructions, so its absence silently unloads the spec. Silence both with `DISABLE_SPEC_DRIFT_BANNER=1`. |
|
|
88
88
|
| New session start with a `~/.claude/.claudemd-manifest.json` that exists but does not parse | `session-start-check` (v0.84.0+) | Re-runs the bootstrap, which rewrites the manifest atomically. Previously this state exited silently on every session, forever. |
|
|
89
89
|
| First `UserPromptSubmit` after a mid-session `/plugin install` upgrade | `version-sync` (v0.3.1+) | Backgrounds `install.js` once per session when the manifest version diverges from the active plugin's `package.json`. Sentinel-gated; fail-open. |
|
|
90
|
+
| Session stop with a completion claim in the last assistant message and no verification output behind it | `evidence-gate` (v0.90.0+) | Stop advisory — judges the TRANSCRIPT (is there a non-error Bash result after the last code edit whose command names a runner, or whose output looks like one) rather than the prose, which is what the prose-reading detector closed in the 2026-07-24 labeling pass, one of six closed there at a pooled precision bound of 0.17 were doing. Opt-in (`EVIDENCE_GATE=1`, default OFF) for FP signal collection. |
|
|
91
|
+
| `PostToolUse` on `Edit`/`Write`, at each 8th edit of one file in one session | `rework-breaker` (v0.90.0+) | Advisory; injects one line naming the count (§1 Root cause over patch). Opt-in (`REWORK_BREAKER=1`, default OFF) for FP signal collection — a large legitimate refactor looks identical from here. |
|
|
90
92
|
| `PostToolUse` after assistant text containing banned vocab | `transcript-vocab-scan` | Advisory; logs to rule-hits without blocking. Opt-in (`TRANSCRIPT_VOCAB_SCAN=1`, default OFF) for FP signal collection. |
|
|
91
93
|
| Session end with last assistant turn carrying §10 four-section out of order, `Done:` lines lacking evidence fingerprints, or `Uncertain:` short hedges without `because` | `transcript-structure-scan` (v0.9.10+) | Stop advisory — closes the audit gap that ~7 self-enforced HARD rules (§iron-law-2 / §10-four-section-order / §10-honesty) had no hook-side feedback signal. Opt-in (`TRANSCRIPT_STRUCTURE_SCAN=1`, default OFF) for FP signal collection; FP-tightened so single-section `Done:` lines never trigger. |
|
|
92
94
|
|
|
@@ -187,6 +189,8 @@ export DISABLE_SESSION_START_HOOK=1 # or
|
|
|
187
189
|
export DISABLE_SESSION_SUMMARY_HOOK=1 # v0.8.0+ — Stop hook writing summary
|
|
188
190
|
export DISABLE_USER_PROMPT_SUBMIT_HOOK=1 # version-sync (mid-session upgrade re-install)
|
|
189
191
|
export DISABLE_TRANSCRIPT_VOCAB_SCAN_HOOK=1 # PostToolUse §10-V advisory scan
|
|
192
|
+
export DISABLE_REWORK_BREAKER_HOOK=1 # v0.90.0+ — PostToolUse:Edit|Write G2 rework advisory (§1 root-cause; opt-in REWORK_BREAKER=1)
|
|
193
|
+
export DISABLE_EVIDENCE_GATE_HOOK=1 # v0.90.0+ — Stop G1b Iron Law #2 evidence-existence advisory (opt-in EVIDENCE_GATE=1)
|
|
190
194
|
export DISABLE_TRANSCRIPT_STRUCTURE_SCAN_HOOK=1 # v0.9.10+ — Stop §10 four-section advisory
|
|
191
195
|
export DISABLE_MEM_AUDIT_HOOK=1 # v0.9.4+ — Stop Why:-less citation advisory
|
|
192
196
|
export DISABLE_SESSION_END_CHECK_HOOK=1 # v0.9.27+ — SessionEnd §11-session-exit mid-SPINE check
|
|
@@ -211,6 +215,12 @@ export DISABLE_COMPACT_REREAD_REMINDER=1 # v0.27.0+ — only the post-compacti
|
|
|
211
215
|
# source=="compact"); compact events still
|
|
212
216
|
# skip bootstrap/upgrade-banner either way.
|
|
213
217
|
|
|
218
|
+
export DISABLE_LEDGER_INJECT=1 # v0.90.0+ — only the G7 long-task ledger
|
|
219
|
+
# injection (SessionStart source=="compact" or
|
|
220
|
+
# "resume" re-injects tasks/<slug>-ledger.md's
|
|
221
|
+
# Decisions + Next); the re-read reminder above
|
|
222
|
+
# still fires.
|
|
223
|
+
|
|
214
224
|
export CLAUDEMD_FORCE_ASYNC_BOOTSTRAP=1 # v0.75.0+ — restores the pre-0.75.0 detached
|
|
215
225
|
# bootstrap on the FRESH-install path (no
|
|
216
226
|
# manifest yet), which otherwise runs install.js
|
|
@@ -394,7 +404,7 @@ claudemd/
|
|
|
394
404
|
├── .claude-plugin/
|
|
395
405
|
│ ├── plugin.json # minimal manifest (name, version, author, license, keywords)
|
|
396
406
|
│ └── marketplace.json # marketplace catalog entry
|
|
397
|
-
├── hooks/ #
|
|
407
|
+
├── hooks/ # 17 shell hooks + hooks/lib/ (hook-common, rule-hits, platform, memory-tags)
|
|
398
408
|
│ └── hooks.json # authoritative hook registration (v0.1.5+); CC expands ${CLAUDE_PLUGIN_ROOT} here
|
|
399
409
|
├── commands/ # 16 slash-command markdown files
|
|
400
410
|
├── bin/ # standalone CLI entrypoint (claudemd-lint.js → `npx claudemd-cli` on npmjs.org)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claudemd-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.90.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": {
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
],
|
|
24
24
|
"scripts": {
|
|
25
25
|
"test": "bash tests/run-all.sh",
|
|
26
|
+
"smoke": "bash tests/smoke.sh",
|
|
26
27
|
"test:scripts": "bash -c 'source tests/lib/env-hygiene.sh && claudemd_reset_test_env && node --test tests/scripts/*.test.js'",
|
|
27
28
|
"test:hooks": "bash -c 'source tests/lib/env-hygiene.sh && claudemd_reset_test_env && source tests/lib/run-suite.sh && for t in tests/hooks/*.test.sh; do run_suite \"$t\" 300 || exit 1; done'",
|
|
28
29
|
"lint:argv": "node scripts/lint-argv.js",
|
package/scripts/lib/lint.js
CHANGED
|
@@ -80,6 +80,40 @@ function posixClassesToJs(regex) {
|
|
|
80
80
|
return out;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
// Compiling the patterns is per-call work whose result never varies: the
|
|
84
|
+
// pattern string plus the fixed `i` flag fully determine the regex, so the
|
|
85
|
+
// same string always compiles to the same matcher. scan() runs once per
|
|
86
|
+
// assistant turn in the audit family, which recompiled the same 23 patterns
|
|
87
|
+
// on every one of them — 184,000 compilations for an 8,000-turn run.
|
|
88
|
+
//
|
|
89
|
+
// The key is the pattern STRING, deliberately not the pattern object: two
|
|
90
|
+
// callers can hold different objects (a fresh readPatterns() array per run)
|
|
91
|
+
// carrying the same regex, and the same regex can arrive with a different
|
|
92
|
+
// `reason` or `isRatio`. Those two fields stay with the caller's object and
|
|
93
|
+
// are read from it below — only the compiled matcher is shared.
|
|
94
|
+
//
|
|
95
|
+
// A pattern that does not compile caches as null, so the fail-open skip in
|
|
96
|
+
// scan() keeps costing one lookup instead of one throw per call.
|
|
97
|
+
//
|
|
98
|
+
// Sharing one compiled regex across calls is stateless HERE because scan()
|
|
99
|
+
// matches with String.match, which resets lastIndex even for a global regex.
|
|
100
|
+
// That is the precondition, not an accident: moving this to .test() or
|
|
101
|
+
// .exec() would make a cached `g`-flagged matcher resume from the previous
|
|
102
|
+
// call's offset. A test for it was tried and deleted — with .match it cannot
|
|
103
|
+
// be driven red, so the constraint lives in this comment instead.
|
|
104
|
+
const RE_CACHE = new Map();
|
|
105
|
+
function compilePattern(regex) {
|
|
106
|
+
if (RE_CACHE.has(regex)) return RE_CACHE.get(regex);
|
|
107
|
+
let re;
|
|
108
|
+
try {
|
|
109
|
+
re = new RegExp(posixClassesToJs(regex), 'i');
|
|
110
|
+
} catch {
|
|
111
|
+
re = null; // bad regex — skip (fail-open)
|
|
112
|
+
}
|
|
113
|
+
RE_CACHE.set(regex, re);
|
|
114
|
+
return re;
|
|
115
|
+
}
|
|
116
|
+
|
|
83
117
|
// stripIdentifiers — remove code / identifier / path regions before §10-V
|
|
84
118
|
// matching so a filename, branch, or backtick span quoting a high-fire word is
|
|
85
119
|
// not read as a value claim. `\b` treats '-', '/', '.' as word boundaries, so
|
|
@@ -382,12 +416,8 @@ export function scan(text, { excludeRatio = false, patterns, sanitize = false }
|
|
|
382
416
|
const hits = [];
|
|
383
417
|
for (const p of pats) {
|
|
384
418
|
if (excludeRatio && p.isRatio) continue;
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
re = new RegExp(posixClassesToJs(p.regex), 'i');
|
|
388
|
-
} catch {
|
|
389
|
-
continue; // bad regex — skip (fail-open)
|
|
390
|
-
}
|
|
419
|
+
const re = compilePattern(p.regex);
|
|
420
|
+
if (!re) continue; // bad regex — skip (fail-open)
|
|
391
421
|
const m = scanText.match(re);
|
|
392
422
|
if (m) hits.push({ match: m[0], regex: p.regex, reason: p.reason, isRatio: p.isRatio });
|
|
393
423
|
}
|