claudemd-cli 0.58.1 → 0.59.1
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 +63 -0
- package/README.md +1 -1
- package/bin/claudemd-lint.js +16 -1
- package/package.json +1 -1
- package/scripts/lib/lint.js +7 -0
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,69 @@ 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.59.1] - 2026-07-26
|
|
12
|
+
|
|
13
|
+
Hotfix for a macOS CI regression shipped in 0.59.0. Tests only — no hook, script, or spec behavior changes.
|
|
14
|
+
|
|
15
|
+
- **fix: the ARCH-2 encoder-parity locale probe now tests BEHAVIOR instead of parsing `locale -a`.** 0.59.0 replaced an inherited locale with an explicit pin, and got it wrong twice in a row: the first form asked whether *either* candidate was listed and then kept the first regardless (pinning a `C.UTF-8` macOS does not ship, caught in pre-tag review); the corrected form matched `locale -a` lines exactly, found nothing on the macOS runner, and **hard-failed** the suite — taking `env-hygiene` case 5 with it, since that case runs this suite under a polluted environment and expects it to pass. What actually matters is whether bash slices by codepoint under the candidate, so that is now measured directly on a known 3-codepoint string.
|
|
16
|
+
- **fix: no usable UTF-8 locale is a loud SKIP, not a failure.** With byte-wise slicing the parity property is genuinely unassertable, and `rule-hits.sh` already documents that degradation as accepted. Same posture as `upgrade-lifecycle`'s unreachable-tag skip: the operator sees it, rather than being blocked by it.
|
|
17
|
+
|
|
18
|
+
The lesson generalizes past this fixture: a portability probe that parses a tool's *output format* inherits that format's platform variance. Probing the behavior you actually depend on does not.
|
|
19
|
+
|
|
20
|
+
## [0.59.0] - 2026-07-26
|
|
21
|
+
|
|
22
|
+
Clears the audit's deferred queue: all 17 items from `tasks/audit-2026-07-26-deferred.md` A+B, in one release. Minor bump: two instruments change what they report and one hook gains a banner on a path where it previously stayed silent. No §8 verdict moves — the differential against the pre-fix hook is still 0 changes across all 330 baseline corpus rows.
|
|
23
|
+
|
|
24
|
+
Several of these are the same defect wearing different clothes: a measurement whose answer moved with an unrelated flag, a check that reported green when it had not run, a constant hand-copied next to a comment naming its source.
|
|
25
|
+
|
|
26
|
+
### Also in this release — the three "claimed enforcement" items
|
|
27
|
+
|
|
28
|
+
Written up separately while 0.58.2 was in flight; that version was never tagged, so it ships here.
|
|
29
|
+
|
|
30
|
+
Closes the three remaining audit items that share a root with what 0.58.0 fixed — an artifact claiming enforcement or source-of-truth status that nothing backed. Everything else from that audit is triaged in `tasks/audit-2026-07-26-deferred.md` for the next §13.2 batch review. No behavior change: hooks are untouched apart from one comment.
|
|
31
|
+
|
|
32
|
+
- **fix: `spec/hard-rules.json` §11-memory-read `enforcement: "hook"` → `"both"`.** `memory-read-check.sh`'s trigger regex covers the ship/release verbs only — `grep -cE "destructive|L3"` over that hook returns 0 — while the rule reads "HARD at ship/release/destructive-path/L3". §13 META directs the agent to calibrate from this field, so it was promising mechanical coverage for two triggers that are Agent-enforced. `§10-specificity` already used `both` plus a coverage note for the same shape.
|
|
33
|
+
- **fix: Iron Law #3 gains a manifest entry.** It is graded by heading convention rather than a `(HARD)` token, so the manifest's coverage scan never required one and the rule was structurally invisible to §13.1/§13.2 accounting. Iron Laws #1 and #2 both had entries. Partition is now 6 hook / 16 self / 2 both / 1 external over 25 rules.
|
|
34
|
+
- **fix: `docs/ARCHITECTURE.md`'s hook-taxonomy table, plus the gate it never had.** The table calls itself source-of-truth for the literal `spec_section` arguments hooks pass, and had drifted twice: `§8-curl-sh` was absent though the curl|sh gate has filed denies under it since v0.51.x, and `session-start-check` was listed `n/a` while emitting `§11-post-compaction`. README's counts had `readme-drift.test.js`; this was the one shipped doc with a source-of-truth claim and nothing behind it. New `tests/scripts/architecture-drift.test.js` extracts sections from `hooks/**/*.sh` and requires each to appear in the table, both directions on the hook list; mutation-verified by deleting `§8-curl-sh` from the table and confirming red.
|
|
35
|
+
- **docs: the §8 comment claiming F24 closed option-with-arg wrapper forms now says which side.** True for the fetch side, which runs through `s8_strip_wrappers`; the sink side is matched by a regex that consumes no options, so `curl … | sudo -u root bash` remains allowed. Pre-existing and recorded as accepted residual, not a regression.
|
|
36
|
+
|
|
37
|
+
### Instruments that measured the wrong thing
|
|
38
|
+
|
|
39
|
+
- **`hard-rules-audit`: review cadence decoupled from the audit window.** `staleReviews` used `--days` as its freshness threshold, so "which rules are overdue for review" moved with a flag about how far back to count hits — `--days=30` returned none, `--days=7` returned all 23. New `REVIEW_CADENCE_DAYS = 90`. An unparseable `last_demote_review` also produced `NaN < cutoff` = false, reading garbage as "reviewed recently"; it now counts as stale.
|
|
40
|
+
- **`memory-maintenance`: a log line with no parseable `ts` no longer counts as in-window.** Its `.md` mentions entered the liveness set unconditionally, so a genuinely stale memory could never surface. `rule-hits-parse.js` already treats a null ts as corruption rather than epoch-0.
|
|
41
|
+
- **`version-cascade-check`: presence floor.** The scan only reported tokens whose minor disagreed, so a file that lost every `vX.Y` mention produced an empty offender list — identical output to "all correct". These files are scanned precisely because they must carry the version.
|
|
42
|
+
- **`lesson-bypass-audit`: `EMIT_CAP` is read from `hooks/memory-prompt-hint.sh`'s `MAX=` line** instead of a hand-copied `5` sitting beside a comment naming that constant. Raising the hook's cap would have silently sliced the extra suggestions off before scoring.
|
|
43
|
+
|
|
44
|
+
### Checks that reported green without running
|
|
45
|
+
|
|
46
|
+
- **`doctor`: a SKIPPED `memory-maintenance:promote` now reports not-ok.** `promoteSkipped != null || length === 0` rendered "could not run" and "0 candidates" the same colour.
|
|
47
|
+
- **`install-drift` is bidirectional.** It iterated source only, so a hook present in the marketplace root and absent from source — exactly what retiring a hook leaves behind, the shape v0.57.0 created — produced no diff.
|
|
48
|
+
- **`excludeTestSessions` keeps a non-string `session_id`.** `(12345).length` is undefined and `undefined > 7` is false, so such a row vanished from every audit view without being counted anywhere, unlike the deliberate sentinel filter which reports `testSessionsFiltered`.
|
|
49
|
+
|
|
50
|
+
### Reachability and contracts
|
|
51
|
+
|
|
52
|
+
- **`session-start-check`: the session-summary banner fires on the fall-through path.** Its only call site was inside the version-MATCH branch, so the session right after every upgrade — and every fresh install — showed nothing. Merged with the bootstrap-failed banner through `jq -s`; verified across all four banner states that stdout is exactly one JSON object, or empty when neither fires.
|
|
53
|
+
- **`claudemd-cli`: `--help` works in any argv position** (`lint --help` exited 2 with "unknown flag").
|
|
54
|
+
- **`version-cascade-check`'s generic catch exits 1, not 2.** 2 is documented as "argv-shape error", which made a malformed `hard-rules.json` indistinguishable from a typo'd flag.
|
|
55
|
+
|
|
56
|
+
### Single-sourcing and hygiene
|
|
57
|
+
|
|
58
|
+
- **`paths.js` gains `projectsRoot()` and `projectDir()`.** The cwd ENCODER was single-sourced by an earlier audit; the directory it feeds was still rebuilt from a `.claude/projects` literal in five call sites.
|
|
59
|
+
- **`status.js` uses `settingsPath()`** instead of a hand-rolled path whose `''` HOME fallback (cwd-relative) diverged from `paths.js`'s `os.homedir()`, silently emptying the kill-switch drift report when HOME was unset.
|
|
60
|
+
- **`doctor`'s `which()` resolves once per binary** (was four `execSync` spawns for two lookups).
|
|
61
|
+
|
|
62
|
+
### Two deferred items closed as "premise did not hold"
|
|
63
|
+
|
|
64
|
+
Pre-tag review measured both claims and neither survived; recorded here rather than shipped as fixes.
|
|
65
|
+
|
|
66
|
+
- **The slashed-path sanitizer does not blank `12/12`.** The item said a lone-`/` match was eating ratios in commit messages. Measured: the first alternative matches `12/12` in full either way, so splitting the alternation changed only whitespace-on-both-sides (`and / or`) — and no shipped ratio pattern matches an N/M shape at all (they are `N% faster` / `Nx faster` plus the 中文 equivalents). Reverted rather than carry a 2x cost on long class-character runs for a fix that was not one.
|
|
67
|
+
- **`audit` deliberately does NOT honor `[allow-banned-vocab]`.** The item read the lint-honors/audit-ignores split as an asymmetry. It is not: `lint` scans text the caller hands it, so the token is explicit intent, while `audit` scans a transcript of assistant turns where the token is incidental — and in this repo, turns discussing the escape hatch are routine, so honoring it would let a turn suppress its own scan by mentioning it. The hook agrees (it reads the token from the Bash command, never from the prose it scans), `README.md` and `status.js` both scope it to "commit message", and `sampling-audit.js` does not honor it either — adding it to `audit` alone would have moved the asymmetry, not removed it. The reasoning is now a comment at the call site.
|
|
68
|
+
|
|
69
|
+
### Test gates
|
|
70
|
+
|
|
71
|
+
- **`rule-hits` ARCH-2 parity pins `LC_CTYPE`.** `rule-hits.sh` documents that its slicing needs a UTF-8 locale and degrades byte-wise under `LC_ALL=C`, but nothing set it — the verdict was inherited from the invoking shell, so a green run did not say which mode ran. Adds a floor asserting the documented C-locale degradation still yields a usable name.
|
|
72
|
+
- **`hard-rules-4` asserts its own premise.** Its null-section arm joins on the rule id, which is sound only while ids and section names share a namespace — previously an unstated coincidence that would let a future rule pass vacuously.
|
|
73
|
+
|
|
11
74
|
## [0.58.1] - 2026-07-26
|
|
12
75
|
|
|
13
76
|
Hotfix for a macOS regression shipped in 0.58.0. Live enforcement was off on macOS: `hooks/lib/hook-common.sh` failed to parse under `/bin/bash` 3.2, and every hook sources it.
|
package/README.md
CHANGED
|
@@ -352,7 +352,7 @@ claudemd/
|
|
|
352
352
|
├── commands/ # 16 slash-command markdown files
|
|
353
353
|
├── bin/ # standalone CLI entrypoint (claudemd-lint.js → `npx claudemd-cli` on npmjs.org)
|
|
354
354
|
├── scripts/ # 18 Node.js scripts + scripts/lib/ (single-source registry, lint, etc.)
|
|
355
|
-
├── spec/ # shipped v6.23.
|
|
355
|
+
├── spec/ # shipped v6.23.1 CLAUDE*.md trio + OPERATOR.md + hard-rules.json manifest
|
|
356
356
|
├── tests/ # hook shell tests + Node.js tests + integration + fixtures
|
|
357
357
|
├── docs/ # ADDING-NEW-HOOK.md + RULE-HITS-SCHEMA.md + superpowers/
|
|
358
358
|
└── .github/workflows/ # ci.yml (ubuntu+macOS × node 20) + npm-publish.yml (tag-triggered)
|
package/bin/claudemd-lint.js
CHANGED
|
@@ -345,6 +345,17 @@ function auditCmd(rawArgs) {
|
|
|
345
345
|
);
|
|
346
346
|
}
|
|
347
347
|
const patterns = readPatterns();
|
|
348
|
+
// NO escape hatch here, deliberately. A 2026-07-26 audit item read the
|
|
349
|
+
// lint-honors/audit-ignores split as an asymmetry to close; it is not one.
|
|
350
|
+
// `lint` scans text the caller hands it, so the token is that caller's explicit
|
|
351
|
+
// intent. `audit` scans a TRANSCRIPT of assistant turns, where the token is
|
|
352
|
+
// incidental text — and in this repo, turns discussing the escape hatch are
|
|
353
|
+
// routine, so honoring it here lets a turn suppress its own scan by mentioning
|
|
354
|
+
// it. The hook agrees: banned-vocab-check.sh:90 reads the token from the Bash
|
|
355
|
+
// command, never from the prose it scans (pinned by banned-vocab.test.sh:243),
|
|
356
|
+
// README.md:241 and status.js:24 both scope it to "commit message", and
|
|
357
|
+
// sampling-audit.js does not honor it at all — adding it here would have moved
|
|
358
|
+
// the asymmetry rather than removed it.
|
|
348
359
|
const annotated = turns.map(t => ({
|
|
349
360
|
...t,
|
|
350
361
|
hits: scan(t.text, { excludeRatio: !includeRatio, patterns, sanitize: true }),
|
|
@@ -363,7 +374,11 @@ function auditCmd(rawArgs) {
|
|
|
363
374
|
|
|
364
375
|
function main() {
|
|
365
376
|
const argv = process.argv.slice(2);
|
|
366
|
-
|
|
377
|
+
// `--help` in ANY position (2026-07-26 audit). Recognizing it only as argv[0]
|
|
378
|
+
// meant `claudemd-cli lint --help` exited 2 with "unknown flag '--help'" — the
|
|
379
|
+
// exact discoverability bug lib/argv.js#printHelpAndExit was written to fix,
|
|
380
|
+
// which this CLI predates.
|
|
381
|
+
if (argv.length === 0 || argv.includes('--help') || argv.includes('-h')) { // argv-lint:allow — help detection runs before subcommand routing; --help takes no value
|
|
367
382
|
process.stdout.write(USAGE + '\n');
|
|
368
383
|
process.exit(argv.length === 0 ? 2 : 0);
|
|
369
384
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claudemd-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.59.1",
|
|
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
|
@@ -120,6 +120,13 @@ export function stripIdentifiers(text) {
|
|
|
120
120
|
// 2. Inline backtick spans.
|
|
121
121
|
.replace(/`[^`]*`/g, ' ')
|
|
122
122
|
// 3. Slashed-path runs (branch names, file paths, URLs) — Path 2's rule.
|
|
123
|
+
// A 2026-07-26 audit item claimed this blanked `12/12` / `2/3` and that
|
|
124
|
+
// splitting the alternation would preserve them. Measured: alternative 1
|
|
125
|
+
// matches `12/12` in full either way, so the split changed only
|
|
126
|
+
// whitespace-on-both-sides (`and / or`) — and no shipped ratio pattern
|
|
127
|
+
// matches an N/M shape at all (they are `N% faster` / `Nx faster` and the
|
|
128
|
+
// 中文 equivalents). The premise did not hold; reverted rather than ship a
|
|
129
|
+
// 2x cost on long class-character runs for a fix that was not one.
|
|
123
130
|
.replace(/[A-Za-z0-9._@~-]*\/[A-Za-z0-9._/@~-]*/g, ' ')
|
|
124
131
|
// 4. Bare dotted-file tokens (foo.js, comprehensive-parser.ts) — CLI
|
|
125
132
|
// extension. The extension must start with a LOWERCASE letter, which
|