claudemd-cli 0.87.0 → 0.89.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 +1 -1
- package/package.json +1 -1
- package/scripts/lib/lint.js +36 -6
package/README.md
CHANGED
|
@@ -76,7 +76,7 @@ Once installed, hooks run silently in the background. Verbose log: `~/.claude/lo
|
|
|
76
76
|
| Trigger | Hook | What happens |
|
|
77
77
|
|---|---|---|
|
|
78
78
|
| `git commit` with banned vocab (e.g. `significantly`, `70% faster`, `should work`) | `banned-vocab-check` | Blocks the commit with a message pointing to the §10-V spec rule. |
|
|
79
|
-
| Bash command with `rm -rf $VAR` (unvalidated expansion), unpinned `npx <pkg>`, or a fetch piped into an interpreter (`curl … \| sh`) | `pre-bash-safety-check` (v0.5.0+) | Blocks at PreToolUse:Bash per §8 SAFETY. Bypass via `[allow-rm-rf-var]` / `[allow-npx-unpinned]` / `[allow-curl-sh]` token in the command, or pin/validate the variable. |
|
|
79
|
+
| Bash command with `rm -rf $VAR` (unvalidated expansion), unpinned `npx <pkg>`, or a fetch piped into an interpreter (`curl … \| sh`) | `pre-bash-safety-check` (v0.5.0+) | Blocks at PreToolUse:Bash per §8 SAFETY. Bypass via `[allow-rm-rf-var]` / `[allow-npx-unpinned]` / `[allow-curl-sh]` token in the command, or pin/validate the variable. The `rm` arm is **wider than the `-rf` shorthand**: it fires on any `-r` / `-R` / `-f` / `-F` short flag or `--recursive` / `--force` long form (so `rm -f "$VAR/one-file"` is blocked too), and on `find "$VAR" -delete` / `find "$VAR" -exec rm …`. For an `rm` the deny message names the flag it matched (`rm -f`, `rm --force`, …) rather than a fixed `rm -rf`; the two `find` forms share one class label, `find … -delete/-exec rm`. |
|
|
80
80
|
| `git push` while the pushed branch's CI is red | `ship-baseline-check` | Blocks the push (2-second `gh run list` timeout; fail-open if `gh` absent or times out). |
|
|
81
81
|
| Bash command matching ship/push/deploy/release with an unread matched `MEMORY.md` entry | `memory-read-check` | Blocks the command with a list of memory files to Read first. |
|
|
82
82
|
| Session end with `~/.claude/tmp/` growth > 20 entries | `residue-audit` | Advisory stderr warning; never blocks. |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claudemd-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.89.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": {
|
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
|
}
|