claudemd-cli 0.26.1 → 0.26.2
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/CHANGELOG.md +9 -0
- package/bin/claudemd-lint.js +2 -2
- package/package.json +1 -1
- package/scripts/lib/lint.js +41 -2
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,15 @@ All notable changes to the `claudemd` plugin. This changelog tracks plugin artif
|
|
|
8
8
|
- **Canonical spec version source**: `spec/CLAUDE.md` top-line title (`# AI-CODING-SPEC vX.Y.Z — Core`) + `spec/CLAUDE-changelog.md` top `##` entry.
|
|
9
9
|
- **Plugin semver vs spec semver** are independent: plugin patch (0.2.0 → 0.2.1) may ship when spec is unchanged (this release); plugin minor (0.1.9 → 0.2.0) ships when spec minor updates (v0.2.0 shipped spec v6.10.0).
|
|
10
10
|
|
|
11
|
+
## [0.26.2] - 2026-07-07
|
|
12
|
+
|
|
13
|
+
**Patch — dogfood QA hardening: three false-positive / data-loss fixes surfaced by an end-to-end usage pass.** All `fix:` (restore intended behavior); spec unchanged (stays v6.14.1). No new user-facing features.
|
|
14
|
+
|
|
15
|
+
- **statusLine multi-supersede data loss** (`scripts/lib/statusline.js`): `/claudemd-statusline adopt --supersede=A` then `--supersede=B` on a composite host overwrote the single `{superseded:<prov>}` restore record, so `remove()` restored only B — A was permanently lost from the host registry. The record is now a `{superseded:[…]}` list: `adopt()` appends (dedup by id), `remove()` restores ALL entries in reverse order (front-insert) so they regain their original relative order. Legacy singular records written by ≤0.26.1 are still restored (`readSupersededList()` normalizes both shapes), so an upgrade taken mid-supersede loses nothing.
|
|
16
|
+
- **banned-vocab `-am` false positive** (`hooks/banned-vocab-check.sh`): the commit-message extractor matched `-m`/`--message` but not combined short-flag blocks, so `git commit -am "…"` (one of the most common forms) fell through to the whole-command fallback — a banned word in a *chained* segment (`git commit -am "clean fix" && npm run comprehensive-x`) falsely denied a clean-message commit that the identical `-m` form passed. The extract + strip regexes now use `-[[:alpha:]]*m` (matches `-m`/`-am`/`-vam`; a strict superset — bare `-m` still matches with zero leading alphas).
|
|
17
|
+
- **§10-V identifier/path FP in the standalone CLI + transcript scan** (`scripts/lib/lint.js`, `bin/claudemd-lint.js`, `hooks/transcript-vocab-scan.sh`): the v0.23.19 Path 2 sanitizer was never ported to two siblings. `claudemd-cli lint` (used in git pre-commit hooks / CI) flagged commit messages naming a file/branch/backtick-identifier that embeds a high-fire word (`refactor comprehensive-parser.js` → exit 1 → **blocked commit**); `transcript-vocab-scan` inflated §10-V advisory telemetry the same way. Added `stripIdentifiers()` (new `lib/lint.js` export) + a `scan({sanitize})` opt-in wired into the CLI `lint`/`audit` paths, and the mirror awk+sed in `transcript-vocab-scan.sh`. Rule set: fenced blocks → inline backtick spans → slashed-path runs → bare `name.ext` files (lowercase-extension only, so decimals/versions like `3.5x`/`v6.14` survive and a baseline-less ratio claim is not swallowed — no false negative). Bare hyphenated identifiers with no extension/slash (`robust-retry`) remain a residual FP, the same `\b`-boundary limitation Path 2 has.
|
|
18
|
+
- **Tests (+8 → 611 Node tests; +5 shell hook tests)**: `statusline-adopt.test.js` multi-supersede + legacy-singular restore; `lint.test.js` `stripIdentifiers` + `scan({sanitize})` FP-fix + FN-guards; `banned-vocab.test.sh` 41 → 44 (`-am`/`-vam` isolation); `transcript-vocab-scan.test.sh` 12 → 14 (identifier-only clean / bare-prose-beside-id still fires).
|
|
19
|
+
|
|
11
20
|
## [0.26.1] - 2026-07-06
|
|
12
21
|
|
|
13
22
|
**Patch — post-release review hardening of v0.26.0 statusLine coexistence.** Three fixes from an independent post-ship review (`superpowers:requesting-code-review`); no user-visible behavior change on the normal path. Spec unchanged (stays v6.14.1).
|
package/bin/claudemd-lint.js
CHANGED
|
@@ -252,7 +252,7 @@ function lintCmd(rawArgs) {
|
|
|
252
252
|
const HAS_BASELINE = /baseline/i;
|
|
253
253
|
const baselineExempt = HAS_NUMERIC_ARROW.test(text) || HAS_BASELINE.test(text);
|
|
254
254
|
|
|
255
|
-
const hits = scan(text, { excludeRatio: baselineExempt });
|
|
255
|
+
const hits = scan(text, { excludeRatio: baselineExempt, sanitize: true });
|
|
256
256
|
if (json) {
|
|
257
257
|
process.stdout.write(formatJSON({ scope: 'lint', text, hits }) + '\n');
|
|
258
258
|
} else {
|
|
@@ -332,7 +332,7 @@ function auditCmd(rawArgs) {
|
|
|
332
332
|
const patterns = readPatterns();
|
|
333
333
|
const annotated = turns.map(t => ({
|
|
334
334
|
...t,
|
|
335
|
-
hits: scan(t.text, { excludeRatio: !includeRatio, patterns }),
|
|
335
|
+
hits: scan(t.text, { excludeRatio: !includeRatio, patterns, sanitize: true }),
|
|
336
336
|
}));
|
|
337
337
|
const flaggedCount = annotated.reduce((n, t) => n + (t.hits.length > 0 ? 1 : 0), 0);
|
|
338
338
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claudemd-cli",
|
|
3
|
-
"version": "0.26.
|
|
3
|
+
"version": "0.26.2",
|
|
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,8 +80,47 @@ function posixClassesToJs(regex) {
|
|
|
80
80
|
return out;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
// stripIdentifiers — remove code / identifier / path regions before §10-V
|
|
84
|
+
// matching so a filename, branch, or backtick span quoting a high-fire word is
|
|
85
|
+
// not read as a value claim. `\b` treats '-', '/', '.' as word boundaries, so
|
|
86
|
+
// `\bcomprehensive\b` fires INSIDE `comprehensive-parser.js` or a branch name
|
|
87
|
+
// `docs/comprehensive-audit`. Mirrors hooks/banned-vocab-check.sh's v0.23.19
|
|
88
|
+
// Path 2 sanitizer (fenced blocks → inline backtick spans → slashed-path runs)
|
|
89
|
+
// and adds a bare dotted-file token strip, because the CLI's primary input —
|
|
90
|
+
// commit messages — commonly names bare files (`refactor comprehensive-parser.js`)
|
|
91
|
+
// without backticks or a leading path. Token classes are ASCII-only so 中文
|
|
92
|
+
// prose and bare-word claims (the real violations) stay intact and still match.
|
|
93
|
+
export function stripIdentifiers(text) {
|
|
94
|
+
if (!text) return text;
|
|
95
|
+
// 1. Fenced code blocks: line-based fence toggle, mirroring the bash awk
|
|
96
|
+
// `/^[[:space:]]*```/{f=!f; next} !f` — drop the ``` marker lines AND the
|
|
97
|
+
// body between them. An unterminated fence drops to EOF (in_fence stays on).
|
|
98
|
+
const kept = [];
|
|
99
|
+
let inFence = false;
|
|
100
|
+
for (const line of text.split('\n')) {
|
|
101
|
+
if (/^\s*```/.test(line)) { inFence = !inFence; continue; }
|
|
102
|
+
if (!inFence) kept.push(line);
|
|
103
|
+
}
|
|
104
|
+
return kept.join('\n')
|
|
105
|
+
// 2. Inline backtick spans.
|
|
106
|
+
.replace(/`[^`]*`/g, ' ')
|
|
107
|
+
// 3. Slashed-path runs (branch names, file paths, URLs) — Path 2's rule.
|
|
108
|
+
.replace(/[A-Za-z0-9._@~-]*\/[A-Za-z0-9._/@~-]*/g, ' ')
|
|
109
|
+
// 4. Bare dotted-file tokens (foo.js, comprehensive-parser.ts) — CLI
|
|
110
|
+
// extension. The extension must start with a LOWERCASE letter, which
|
|
111
|
+
// (a) excludes decimals / versions ("3.5x", "v6.14") whose ".5x"/".14"
|
|
112
|
+
// could otherwise swallow a baseline-less ratio claim → false negative,
|
|
113
|
+
// and (b) excludes sentence-boundary typos ("comprehensive.Next", capital
|
|
114
|
+
// after the dot) so a real claim isn't stripped. Only true `name.ext`
|
|
115
|
+
// identifiers with a lowercase extension are removed.
|
|
116
|
+
.replace(/[A-Za-z0-9_-]+\.[a-z][a-z0-9]*/g, ' ');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function scan(text, { excludeRatio = false, patterns, sanitize = false } = {}) {
|
|
84
120
|
if (!text) return [];
|
|
121
|
+
// Sanitize identifier/path regions when asked (CLI lint/audit opt in). Match
|
|
122
|
+
// against the stripped text; the caller keeps the original for display.
|
|
123
|
+
const scanText = sanitize ? stripIdentifiers(text) : text;
|
|
85
124
|
const pats = patterns || readPatterns();
|
|
86
125
|
const hits = [];
|
|
87
126
|
for (const p of pats) {
|
|
@@ -92,7 +131,7 @@ export function scan(text, { excludeRatio = false, patterns } = {}) {
|
|
|
92
131
|
} catch {
|
|
93
132
|
continue; // bad regex — skip (fail-open)
|
|
94
133
|
}
|
|
95
|
-
const m =
|
|
134
|
+
const m = scanText.match(re);
|
|
96
135
|
if (m) hits.push({ match: m[0], regex: p.regex, reason: p.reason, isRatio: p.isRatio });
|
|
97
136
|
}
|
|
98
137
|
return hits;
|