claudemd-cli 0.9.18 → 0.9.20
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 +37 -0
- package/bin/claudemd-lint.js +5 -5
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,43 @@ 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.9.20] - 2026-05-10
|
|
12
|
+
|
|
13
|
+
**Patch — `hard-rules-audit` demoteCandidates produced false-positive recommendations when log span < requested window.** Spec v6.11.7 unchanged. Surfaced in the same dogfood session that produced v0.9.18/v0.9.19: running `/claudemd-rules` (default 90-day window) against a `~/.claude/logs/claudemd.jsonl` that only spans 17 days reported `§11-memory-read` as a demote candidate — but that rule had been silently no-op'd in projects with `_` in the cwd path until v0.9.15 fixed it. The data couldn't see the rule firing because the rule itself was broken; "0 hits in window" wasn't a coldness signal. §0.1 HARD specifies "0 hits in 90d" — running the calculation on 17 days of data violates the spec. Same root applies to any rule fixed/added more recently than the log start.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- `[fix]` **`scripts/hard-rules-audit.js` suppresses `demoteCandidates` when `insufficientData`.** Pre-fix: `demoteCandidates` was computed as `hookEnforced.filter(r => r.hits.total === 0)` regardless of whether the log reached back the requested window. Post-fix: when `logSpanDays < days`, `demoteCandidates` is forced to `[]` and the would-have-been list is preserved under a new `demoteSuppressed` field (`{ reason, wouldHaveBeen }`) so the operator sees what's *potentially* cold without auto-acting on a false signal.
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- `[feat]` **`scripts/lib/rule-hits-parse.js#logFirstTs(path)`** — returns earliest ts in the rule-hits log (ms-since-epoch) or `null` for missing/empty/all-malformed files. Skips rows with non-finite timestamps. Used by `hardRulesAudit` to compute `logSpanDays`; future consumers (sparkline, audit) can adopt for the same insufficient-data check.
|
|
22
|
+
- `[feat]` **`hardRulesAudit` output: 4 new fields** — `logSpanDays` (operator transparency, surfaced even when sufficient), `insufficientData` (boolean), `demoteSuppressed` (`{reason, wouldHaveBeen}` when insufficient, `null` otherwise), and `demoteCandidates: []` when insufficient (compatibility: existing consumers reading the array get an empty array instead of false candidates).
|
|
23
|
+
- `[test]` **5 new cases in `tests/scripts/hard-rules-audit.test.js`** + **8 new cases in new `tests/scripts/rule-hits-parse.test.js` (257 → 270)**: insufficient-data suppresses + surfaces `demoteSuppressed`, sufficient-span preserves existing demote behavior, log-span boundary case (35d > 30d window), `logSpanDays` always reflects actual log reach not the window, `logFirstTs` returns `null` on missing/empty/all-malformed and earliest ts otherwise, skips non-finite ts rows.
|
|
24
|
+
- `[test]` **`demoteCandidates list hook-rules with zero hits` test updated** to write a 31-day-old sentinel row before asserting `§8-rm-rf-var` appears as candidate. Pre-fix, the test passed against an empty log because there was no insufficient-data check. Post-fix, an empty log triggers `insufficientData=true` and `demoteCandidates=[]`, so the test would have asserted on suppressed candidates and failed. Documented in the new "sufficient log span" test name.
|
|
25
|
+
|
|
26
|
+
### Why no L3 / pre-ship-review chain
|
|
27
|
+
|
|
28
|
+
`fix:` per §2 hard-upgrade exclusion — restores §0.1 HARD's documented intent (90d window means 90d of data, not "whatever the log has"). L2 ceiling. Diff: 1 lib helper added (~15 LOC), 1 script branch added (~15 LOC), 2 test files (+13 cases). Notable: this is the same family as v0.9.13's `session-summary` window-calculation bug — "report computes correct math against the data it has, but the data itself doesn't span the window the report claims to cover." Sparkline's `(newly active)` annotation is the same UX issue — not in this patch (would require updating the existing `newly active` test fixture to span 90 days, larger churn) — flagged for a follow-up patch.
|
|
29
|
+
|
|
30
|
+
## [0.9.19] - 2026-05-10
|
|
31
|
+
|
|
32
|
+
**Patch — repo-wide CI guard for the argv-shape silent-fallback antipattern (§13.2 Tier 1).** Spec v6.11.7 unchanged. v0.9.14 → v0.9.18 was five consecutive patches chasing the same antipattern in five different files because each fix-set was scoped to whatever the repro session tripped on. The CHANGELOG entry for v0.9.18 stated "the next exploratory-testing session WILL find a 6th hole"; this patch makes that prediction grep-enforceable instead of trusting the next session not to slip.
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
|
|
36
|
+
- `[feat]` **`scripts/lint-argv.js`** — module-friendly gate exporting `scan({ root, dirs, exts, fileAllowlist, patterns })` and a CLI entry. Greps the union of three known antipattern signatures (`\b\w+\.includes\(['"]--`, `\.find\(\w+\s*=>\s*\w+\.startsWith\(['"]--`, `\b\w+\.indexOf\(['"]--`) across `bin/` + `scripts/` `.js` files. Pure `//` comment lines are skipped (meta-recursion guard for the validator's own docstring quoting the antipattern as the bug it prevents). Inline allowlist: append `// argv-lint:allow` to a vetted line. File allowlist: add to `FILE_ALLOWLIST` with a one-line reason — only the gate itself + `scripts/lib/argv.js` (parseStrict implementation) qualify today.
|
|
37
|
+
- `[feat]` **`npm run lint:argv` script in `package.json`.** Local + CI invocable. Exit 0 on clean, exit 1 with per-hit `file:line [pattern]` + offending text + remediation hint on dirty. Tier 2 (wiring into `.github/workflows/test.yml`) is intentionally NOT in this patch — that's a §5 Hard CI/infra change deferred to user decision.
|
|
38
|
+
- `[test]` **9 new cases in `tests/scripts/lint-argv.test.js` (248 → 257)** — live-repo clean baseline, each of the three signatures detected on synthetic fixtures, inline allowlist, file-level allowlist, pure-comment skip (the meta-recursion guard), end-of-line comment NOT skipped (allowlist evasion attempt rejected), CLI exit 0 + stdout shape on clean.
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
- `[fix]` **`bin/claudemd-lint.js` lintCmd/auditCmd add 5 inline `// argv-lint:allow` comments** on the `args.includes/indexOf('--*')` lines that are post-validator-safe-by-construction (`validateAndExpandFlags` rejects unknown + bool=value upstream, so downstream `.includes('--json')` will never observe a wrong-shape value). The lines are functionally safe and intentionally chosen over a structural refactor that would churn v0.9.18's diff for no behavioral gain.
|
|
43
|
+
|
|
44
|
+
### Why no L3 / pre-ship-review chain
|
|
45
|
+
|
|
46
|
+
`feat:` adds a CI-helper script, not user-visible plugin behavior. No spec change. L2 ceiling. Diff: 1 new gate (~100 LOC), 1 new test file (~100 LOC), 1 package.json line, 5 inline comments in the public CLI, 4 version-string bumps. The companion §13.2 promotion to a HARD spec rule (hook-level enforcement) is a separate L3 spec-change patch held until the gate has run for a few release cycles and the false-positive rate is known empirically.
|
|
47
|
+
|
|
11
48
|
## [0.9.18] - 2026-05-10
|
|
12
49
|
|
|
13
50
|
**Patch — public npm CLI `bin/claudemd-lint.js` had the same argv-shape silent-fallback the slash-commands fixed in v0.9.16/v0.9.17.** Spec v6.11.7 unchanged. Surfaced exercising the published CLI as a downstream integrator would: `claudemd lint --jzon "..."` (typo) silently dropped the unknown flag and scanned the text anyway → exit reflected only the text content; `claudemd lint --json=yes "..."` silently dropped `--json` (because `args.includes('--json')` returns false for the `=` form) → human-readable text emitted on stdout when JSON was expected; `claudemd lint --file=PATH` (GNU-getopt convention) was unrecognized and exited 2 with the misleading `text required` message; `claudemd audit --include-ratiox PATH` silently dropped the typo → exit 0 even when content would deny. Fifth recurrence of argv-shape silent-fallback (v0.9.14 lint positional, v0.9.15 hook tag, v0.9.16 three slash-commands, v0.9.17 two more slash-commands, this one — the **publicly published** surface, the most-visible footgun).
|
package/bin/claudemd-lint.js
CHANGED
|
@@ -105,12 +105,12 @@ function validateAndExpandFlags(args, knownBools, knownValues, sub) {
|
|
|
105
105
|
|
|
106
106
|
function lintCmd(rawArgs) {
|
|
107
107
|
const args = validateAndExpandFlags(rawArgs, ['--json', '--stdin'], ['--file'], 'lint');
|
|
108
|
-
const json = args.includes('--json');
|
|
109
|
-
const stdin = args.includes('--stdin');
|
|
108
|
+
const json = args.includes('--json'); // argv-lint:allow — validated upstream by validateAndExpandFlags
|
|
109
|
+
const stdin = args.includes('--stdin'); // argv-lint:allow — validated upstream by validateAndExpandFlags
|
|
110
110
|
|
|
111
111
|
// --file <path> consumes the next non-flag arg.
|
|
112
112
|
let filePath = null;
|
|
113
|
-
const fileIdx = args.indexOf('--file');
|
|
113
|
+
const fileIdx = args.indexOf('--file'); // argv-lint:allow — validated upstream by validateAndExpandFlags
|
|
114
114
|
if (fileIdx !== -1) {
|
|
115
115
|
const next = args[fileIdx + 1];
|
|
116
116
|
if (!next || next.startsWith('--')) {
|
|
@@ -191,8 +191,8 @@ function lintCmd(rawArgs) {
|
|
|
191
191
|
|
|
192
192
|
function auditCmd(rawArgs) {
|
|
193
193
|
const args = validateAndExpandFlags(rawArgs, ['--json', '--include-ratio'], [], 'audit');
|
|
194
|
-
const json = args.includes('--json');
|
|
195
|
-
const includeRatio = args.includes('--include-ratio');
|
|
194
|
+
const json = args.includes('--json'); // argv-lint:allow — validated upstream by validateAndExpandFlags
|
|
195
|
+
const includeRatio = args.includes('--include-ratio'); // argv-lint:allow — validated upstream by validateAndExpandFlags
|
|
196
196
|
const positional = args.filter(a => !a.startsWith('--'));
|
|
197
197
|
const transcriptPath = positional[0];
|
|
198
198
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claudemd-cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.20",
|
|
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": {
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"scripts": {
|
|
17
17
|
"test": "bash tests/run-all.sh",
|
|
18
18
|
"test:scripts": "node --test tests/scripts/*.test.js",
|
|
19
|
-
"test:hooks": "bash tests/hooks/*.test.sh"
|
|
19
|
+
"test:hooks": "bash tests/hooks/*.test.sh",
|
|
20
|
+
"lint:argv": "node scripts/lint-argv.js"
|
|
20
21
|
},
|
|
21
22
|
"engines": {
|
|
22
23
|
"node": ">=20"
|