claudemd-cli 0.72.0 → 0.73.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.
@@ -480,7 +480,20 @@ function auditCmd(rawArgs) {
480
480
  }
481
481
  }
482
482
 
483
- const turns = parseTranscript(jsonl);
483
+ // `integrity` counts the rows parseTranscript drops. The pre-flight above
484
+ // only catches an ENTIRELY unparseable file; a transcript that is 30%
485
+ // corrupt passed it and then printed "no §10-V hits across N turn(s)" with N
486
+ // silently short (audit R11-24). Same warning shape as the string-row skip
487
+ // below — the verdict still comes from the rows that parsed, but the reader
488
+ // is told how many did not.
489
+ const integrity = { totalLines: 0, badLines: 0 };
490
+ const turns = parseTranscript(jsonl, integrity);
491
+ if (integrity.badLines > 0) {
492
+ process.stderr.write(
493
+ `audit: warning: skipped ${integrity.badLines} of ${integrity.totalLines} non-empty line(s) ` +
494
+ `that did not parse as JSON — the turn count below covers only the rows that parsed.\n`
495
+ );
496
+ }
484
497
  // QA ISSUE-002 (option c): string-shape assistant rows are outside the
485
498
  // block-array input domain and never reach the scanner. Keep the verdict
486
499
  // based on scanned turns, but say so on stderr — a silent skip here is the
@@ -514,7 +527,9 @@ function auditCmd(rawArgs) {
514
527
  const flaggedCount = annotated.reduce((n, t) => n + (t.hits.length > 0 ? 1 : 0), 0);
515
528
 
516
529
  if (json) {
517
- process.stdout.write(formatJSON({ scope: 'audit', transcript: transcriptPath, turns: annotated }) + '\n');
530
+ process.stdout.write(
531
+ formatJSON({ scope: 'audit', transcript: transcriptPath, turns: annotated, integrity }) + '\n'
532
+ );
518
533
  } else {
519
534
  const out = formatHumanReadable({ scope: 'audit', turns: annotated });
520
535
  if (flaggedCount === 0) process.stdout.write(out + '\n');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudemd-cli",
3
- "version": "0.72.0",
3
+ "version": "0.73.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": {
@@ -24,7 +24,7 @@
24
24
  "scripts": {
25
25
  "test": "bash tests/run-all.sh",
26
26
  "test:scripts": "bash -c 'source tests/lib/env-hygiene.sh && claudemd_reset_test_env && node --test tests/scripts/*.test.js'",
27
- "test:hooks": "for t in tests/hooks/*.test.sh; do bash \"$t\" || exit 1; done",
27
+ "test:hooks": "bash -c 'source tests/lib/env-hygiene.sh && claudemd_reset_test_env && source tests/lib/run-suite.sh && for t in tests/hooks/*.test.sh; do run_suite \"$t\" 300 || exit 1; done'",
28
28
  "lint:argv": "node scripts/lint-argv.js",
29
29
  "version-check": "node scripts/version-cascade-check.js",
30
30
  "test:coverage": "c8 --all --src=bin --src=scripts --include='bin/**/*.js' --include='scripts/**/*.js' --reporter=text-summary --reporter=html --reports-dir=coverage npm run test:scripts",
@@ -383,21 +383,31 @@ export function scan(text, { excludeRatio = false, patterns, sanitize = false }
383
383
  return hits;
384
384
  }
385
385
 
386
- // parseTranscript(jsonlText) → [{turnIndex, line, text}, ...]
386
+ // parseTranscript(jsonlText, integrity) → [{turnIndex, line, text}, ...]
387
387
  // Iterates jsonl, returns one entry per assistant text-content turn. Each
388
388
  // entry concatenates all .message.content[*].text blocks for that turn.
389
- // Corrupt rows (unparseable JSON, missing fields) silently skipped — matches
389
+ // Corrupt rows (unparseable JSON, missing fields) are skipped — matches
390
390
  // transcript-vocab-scan.sh's `try fromjson catch empty` design.
391
- export function parseTranscript(jsonlText) {
391
+ //
392
+ // `integrity` (out param, optional): `{ totalLines, badLines }` written back
393
+ // so the skip is countable rather than invisible. Skipping is still the right
394
+ // behavior — a half-corrupt transcript is worth scanning — but until 2026-09-03
395
+ // (audit R11-24) nothing downstream could tell "0 hits across 40 turns" from
396
+ // "0 hits across the 40 turns that happened to parse". readLogRows in
397
+ // rule-hits-parse.js has carried the same counter since a 33% corruption went
398
+ // unnoticed there; this is the same counter on the transcript side.
399
+ export function parseTranscript(jsonlText, integrity = null) {
392
400
  const lines = jsonlText.split('\n');
393
401
  const turns = [];
394
402
  let turnIndex = 0;
395
403
  for (let i = 0; i < lines.length; i++) {
396
404
  if (!lines[i].trim()) continue;
405
+ if (integrity) integrity.totalLines = (integrity.totalLines || 0) + 1;
397
406
  let row;
398
407
  try {
399
408
  row = JSON.parse(lines[i]);
400
409
  } catch {
410
+ if (integrity) integrity.badLines = (integrity.badLines || 0) + 1;
401
411
  continue;
402
412
  }
403
413
  if (row.type !== 'assistant') continue;