claudemd-cli 0.88.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/package.json +1 -1
- package/scripts/lib/lint.js +36 -6
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
|
}
|