claudemd-cli 0.26.1 → 0.27.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/CHANGELOG.md +18 -0
- package/README.md +6 -1
- 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,24 @@ 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.27.0] - 2026-07-10
|
|
12
|
+
|
|
13
|
+
**Minor — post-compaction §11 re-read reminder (new SessionStart behavior) + spec v6.14.2 wording patch.** First implementation tranche of `docs/spec-optimization-plan-2026-07-10.md` (P6 items F1–F4).
|
|
14
|
+
|
|
15
|
+
- **Compaction re-read reminder** (`hooks/session-start-check.sh`): SessionStart events with `source=="compact"` (auto or manual compaction — verified against code.claude.com/docs/en/hooks) now emit a one-line `additionalContext` banner: re-read the active plan + spec state per core §11 before continuing L2+ work. Rationale: §11 post-compaction re-read is a self-enforced rule guarding exactly the state where model attention is least reliable — it depended on the very attention it protects; the banner makes it hook-assisted. Advisory only, never blocks. **Opt-out**: `DISABLE_COMPACT_REREAD_REMINDER=1` (README §2a). Telemetry event: `session-start / compact-reminder / §11`.
|
|
16
|
+
- **Behavior change on compact events**: `source=="compact"` now exits early — bootstrap, upgrade-banner, and summary-banner no longer run on compaction. They are session-START concerns; re-running `install.js` mid-session on a compaction event was never desirable. Startup / resume / clear paths are unchanged (stdin is now parsed whenever `jq` is present, previously only when `CLAUDE_SESSION_ID` was unset — needed for the `source` field; no observable difference on those paths).
|
|
17
|
+
- **Spec v6.14.2** (patch — wording only, no rule change; detail in `spec/CLAUDE-changelog.md`): (1) extended header load-scope "review" → "pre-ship review", aligning with core §2.2 (per-task code review does not load extended); (2) trigger-word lists marked non-exhaustive with `e.g.` (quality-slider / depth-triggers / HACK / EMERGENCY / three-strike / continuation-cancel-switch) — detector-consumed lists (§11 mid-SPINE tell) stay exact; (3) context7 hard references conditionalized to "docs-lookup for API claims (e.g. context7, if available)" + a §12 fallback-table row (WebFetch official docs).
|
|
18
|
+
- **Tests**: `session-start.test.sh` 14 → 17 — compact banner emits exactly ONE JSON object (`jq -s length == 1`, per the v0.23.13 double-emit lesson); `DISABLE_COMPACT_REREAD_REMINDER=1` suppresses; compact does NOT spawn bootstrap even when the manifest is missing. Version-pin cascade updated (`spec-structure.test.js`, `upgrade-lifecycle.test.sh`, `hard-rules.json#spec_version`).
|
|
19
|
+
|
|
20
|
+
## [0.26.2] - 2026-07-07
|
|
21
|
+
|
|
22
|
+
**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.
|
|
23
|
+
|
|
24
|
+
- **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.
|
|
25
|
+
- **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).
|
|
26
|
+
- **§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.
|
|
27
|
+
- **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).
|
|
28
|
+
|
|
11
29
|
## [0.26.1] - 2026-07-06
|
|
12
30
|
|
|
13
31
|
**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/README.md
CHANGED
|
@@ -198,6 +198,11 @@ export DISABLE_SESSION_SUMMARY_BANNER=1 # v0.8.0+ — only the SessionStart b
|
|
|
198
198
|
# the data is captured for /claudemd-audit
|
|
199
199
|
# but no additionalContext line is injected.
|
|
200
200
|
|
|
201
|
+
export DISABLE_COMPACT_REREAD_REMINDER=1 # v0.27.0+ — only the post-compaction §11
|
|
202
|
+
# re-read reminder banner (SessionStart with
|
|
203
|
+
# source=="compact"); compact events still
|
|
204
|
+
# skip bootstrap/upgrade-banner either way.
|
|
205
|
+
|
|
201
206
|
export DISABLE_BATCH_CADENCE_ADVISORY=1 # v0.19.2+ — only the §13.2 batch-review
|
|
202
207
|
# cadence advisory inside session-end-check;
|
|
203
208
|
# mid-SPINE warn-on-unvalidated-mutation
|
|
@@ -333,7 +338,7 @@ claudemd/
|
|
|
333
338
|
├── commands/ # 12 slash-command markdown files
|
|
334
339
|
├── bin/ # standalone CLI entrypoint (claudemd-lint.js → `npx claudemd-cli` on npmjs.org)
|
|
335
340
|
├── scripts/ # 16 Node.js management scripts + scripts/lib/ (single-source registry, lint, etc.)
|
|
336
|
-
├── spec/ # shipped v6.14.
|
|
341
|
+
├── spec/ # shipped v6.14.2 CLAUDE*.md trio + OPERATOR.md + hard-rules.json manifest
|
|
337
342
|
├── tests/ # hook shell tests + Node.js tests + integration + fixtures
|
|
338
343
|
├── docs/ # ADDING-NEW-HOOK.md + RULE-HITS-SCHEMA.md + superpowers/
|
|
339
344
|
└── .github/workflows/ # ci.yml (ubuntu+macOS × node 20) + npm-publish.yml (tag-triggered)
|
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.
|
|
3
|
+
"version": "0.27.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,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;
|