skalpel 4.0.56 → 4.0.58

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/anchor-shadow.mjs CHANGED
@@ -505,11 +505,22 @@ function findPushBranch(text) {
505
505
  return { remote, branch };
506
506
  }
507
507
 
508
- // findTag(text) → a release tag (vX.Y.Z or a `git tag <t>` / `git push origin <t>` mention). Validated.
508
+ // findTag(text) → a release tag, COMMAND-ANCHORED (2026-07-13 false-accusation incident): a git-tag
509
+ // target may ONLY be minted when the tag token appears on a line that is itself a `git tag` / `git push`
510
+ // COMMAND — NEVER scavenged from arbitrary prose/output. A bare `v26.3.1` in quoted `node --version`
511
+ // output once became a "release tag", probed `git ls-remote --tags origin v26.3.1`, and minted a false
512
+ // SHIP_FAIL on a true claim. Implementation: extract ONLY the candidate lines matching
513
+ // /\bgit\s+(?:tag|push)\b/ and run the (unchanged) tag patterns over those lines alone; no such line →
514
+ // no target, ever (→ NO_TARGET, no probe, no accusation). Validation (SHIP_SAFE_REF) unchanged.
509
515
  function findTag(text) {
510
- const near = /\bgit\s+(?:tag|push[^\n;&|]*?)\s+(v?\d+\.\d+\.\d+[0-9A-Za-z.\-+]*)\b/i.exec(text);
516
+ const cmdLines = String(text || "")
517
+ .split(/\r?\n/)
518
+ .filter((l) => /\bgit\s+(?:tag|push)\b/i.test(l));
519
+ if (!cmdLines.length) return null; // no git tag/push command anywhere in context → no tag target
520
+ const cmds = cmdLines.join("\n");
521
+ const near = /\bgit\s+(?:tag|push[^\n;&|]*?)\s+(v?\d+\.\d+\.\d+[0-9A-Za-z.\-+]*)\b/i.exec(cmds);
511
522
  if (near && SHIP_SAFE_REF.test(near[1])) return near[1];
512
- const m = /\b(v\d+\.\d+\.\d+[0-9A-Za-z.\-+]*)\b/.exec(text);
523
+ const m = /\b(v\d+\.\d+\.\d+[0-9A-Za-z.\-+]*)\b/.exec(cmds);
513
524
  return m && SHIP_SAFE_REF.test(m[1]) ? m[1] : null;
514
525
  }
515
526
 
package/eval-harness.mjs CHANGED
@@ -1127,6 +1127,7 @@ const SHIP_FIXTURES = [
1127
1127
  expectReprobe: true,
1128
1128
  },
1129
1129
  // (d) TRUE CATCH: pushed-claim + sha NOT on the real remote tip → red (it never left the machine).
1130
+ // Since the all-reds confirm-gate (2026-07-13 incident), even a hard FAIL must confirm 2/2.
1130
1131
  {
1131
1132
  id: "d",
1132
1133
  label: "pushed + sha-not-on-remote → RED",
@@ -1136,8 +1137,9 @@ const SHIP_FIXTURES = [
1136
1137
  exec: gitRemoteExec({ tip: SHA_OLD, ancestor: false }),
1137
1138
  expectOutcome: "SHIP_FAIL",
1138
1139
  expectRed: true,
1140
+ expectReprobe: true,
1139
1141
  },
1140
- // (e) TRUE CATCH: merged-claim + PR is OPEN → red.
1142
+ // (e) TRUE CATCH: merged-claim + PR is OPEN → red (confirmed 2/2).
1141
1143
  {
1142
1144
  id: "e",
1143
1145
  label: "merged + PR OPEN → RED",
@@ -1147,6 +1149,7 @@ const SHIP_FIXTURES = [
1147
1149
  exec: ghPrExec("OPEN", null),
1148
1150
  expectOutcome: "SHIP_FAIL",
1149
1151
  expectRed: true,
1152
+ expectReprobe: true,
1150
1153
  },
1151
1154
  // (f) TRUE CATCH: live-claim + confirmed 404 × 2 → red (only the double-negative fires).
1152
1155
  {
@@ -1217,7 +1220,7 @@ const SHIP_FIXTURES = [
1217
1220
  expectRed: true,
1218
1221
  expectReprobe: true,
1219
1222
  },
1220
- // (l) published + NOT on the registry → red (npm view E404).
1223
+ // (l) published + NOT on the registry → red (npm view E404, confirmed 2/2).
1221
1224
  {
1222
1225
  id: "l",
1223
1226
  label: "published + npm E404 → RED",
@@ -1227,6 +1230,7 @@ const SHIP_FIXTURES = [
1227
1230
  exec: npmExec(false, "9.9.9"),
1228
1231
  expectOutcome: "SHIP_FAIL",
1229
1232
  expectRed: true,
1233
+ expectReprobe: true,
1230
1234
  },
1231
1235
  // (m) published + IS on the registry → NO red.
1232
1236
  {
@@ -1239,7 +1243,7 @@ const SHIP_FIXTURES = [
1239
1243
  expectOutcome: "SHIP_OK",
1240
1244
  expectRed: false,
1241
1245
  },
1242
- // (n) CI green claim + checks FAILING → red.
1246
+ // (n) CI green claim + checks FAILING → red (confirmed 2/2).
1243
1247
  {
1244
1248
  id: "n",
1245
1249
  label: "ci-green + checks failing → RED",
@@ -1249,6 +1253,7 @@ const SHIP_FIXTURES = [
1249
1253
  exec: ghChecksExec([{ bucket: "pass" }, { bucket: "fail" }]),
1250
1254
  expectOutcome: "SHIP_FAIL",
1251
1255
  expectRed: true,
1256
+ expectReprobe: true,
1252
1257
  },
1253
1258
  // (o) CI green claim + checks passing → NO red.
1254
1259
  {
@@ -1261,7 +1266,7 @@ const SHIP_FIXTURES = [
1261
1266
  expectOutcome: "SHIP_OK",
1262
1267
  expectRed: false,
1263
1268
  },
1264
- // (p) released (git tag) + tag NOT on origin → red.
1269
+ // (p) released (git tag) + tag NOT on origin → red (confirmed 2/2).
1265
1270
  {
1266
1271
  id: "p",
1267
1272
  label: "released tag + missing on origin → RED",
@@ -1271,6 +1276,7 @@ const SHIP_FIXTURES = [
1271
1276
  exec: gitTagExec(false, "v2.0.0"),
1272
1277
  expectOutcome: "SHIP_FAIL",
1273
1278
  expectRed: true,
1279
+ expectReprobe: true,
1274
1280
  },
1275
1281
  // (q) released (git tag) + tag present on origin → NO red.
1276
1282
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skalpel",
3
- "version": "4.0.56",
3
+ "version": "4.0.58",
4
4
  "description": "Behavioral graph for AI-assisted coding — learns how you work and steers Claude Code + Codex in real time.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,42 +9,68 @@
9
9
  // that verifiably happened; else
10
10
  // • the current steer's plain-English label (what skalpel is doing now) — a description, not a number;
11
11
  // else
12
- // • the ALWAYS-ON presence line, "skalpel · N steers[ · <time clause>]".
13
- // N = steers ACTUALLY applied (fired + not suppressed), counted from the append-only
14
- // ~/.skalpel/steers.ndjsona fresh install reads "0 steers", present, never blank. The optional
15
- // TIME clause is chosen by BASELINE_TIME_MODE (Ryan flips one constant):
16
- // · <Xm> caught the CUMULATIVE LIFETIME sum of every real MEASURED red→green delta (each caught
17
- // false "done" and the moment that SAME proof, that SAME session, was later logged PASS — real
18
- // timestamps; reuses ledger.mjs's buildFixDeltas logic). ALWAYS-ON once real catches exist (it is
19
- // skalpel's terminal presence never gated/hidden). A measured total, NEVER an estimate; a catch
20
- // with no later PASS adds 0. Cumulative framing (not present-tense) keeps it honest for old data.
21
- // · ~Xm saved Raahil's #609 estimate: (applied steers) × (the user's OWN median loop cost from
22
- // their causal graph). A `~`-prefixed ESTIMATE grounded in their own data, shown only when
23
- // saved_min > 0. Never a server wall-clock number; never "skalpel saved you X" as a fact.
12
+ // • the ALWAYS-ON AMBIENT line: the `🔬 skalpel` wordmark, a dim em-dash, then a COMPOSED body of only
13
+ // the clauses that have real value (see THE INSTRUMENT LINE below). Nothing yet reads
14
+ // "🔬 skalpel — watching your agent's claims" present, quiet, never "0 steers".
15
+ //
16
+ // THE INSTRUMENT LINE (redesign, 2026-07-14 founder: "the bottom of my terminal is ugly, zero
17
+ // aesthetic, zero gratifying feeling" about `skalpel · 0 steers`). Typography + hierarchy, not
18
+ // decoration. The ambient body composes, in order, ONLY the clauses whose value exists:
19
+ // clean → "N claims verified[ · Xm caught][ · N steers]"
20
+ // caught → "N checked · M false[ · Xm caught][ · N steers]"
21
+ // nothing "watching your agent's claims" (dim quiet confidence, NOT a dead zero)
22
+ // · <Xm> caught the CUMULATIVE LIFETIME sum of every real MEASURED red→green delta (each caught
23
+ // false "done" and the moment that SAME proof, that SAME session, was later logged PASS — real
24
+ // timestamps; reuses ledger.mjs's buildFixDeltas logic). A measured total, NEVER an estimate; a
25
+ // catch with no later PASS adds 0. Cumulative framing (not present-tense) keeps it honest.
26
+ // · N steers — steers ACTUALLY applied (fired + not suppressed), counted from the append-only
27
+ // ~/.skalpel/steers.ndjson.
28
+ // IMMUTABLE ALWAYS-ON COUNTER (owner product decision — unchanged in substance): the counter surfaces
29
+ // (`N steers` when > 0, the measured `Xm caught` whenever it has a value) remain ALWAYS-ON on the
30
+ // ambient line — never gated, never hidden. This redesign only removes DEAD ZEROS: a zero-value clause
31
+ // is omitted, and the always-on presence is carried by the wordmark + the watching line instead of a
32
+ // literal "0 steers". "0 steers" never renders again.
33
+ // COLOR DISCIPLINE (Ive restraint): wordmark bold; live numbers cyan; chrome words dim. The skalpel red
34
+ // is ONE accent used ONCE — only on the `M false` clause when lies > 0. Semantic color only.
24
35
  // Every value here is a real count or a real measured timestamp delta (or a clearly `~`-marked estimate).
25
- // NOTE: this always-on presence line is a DELIBERATE new behavior origin/main's clean-idle bar emitted
26
- // ZERO bytes; this emits "skalpel · 0 steers" (Ryan: always-on presence, non-negotiable). Byte-identical
27
- // to origin/main is preserved ONLY for the untouched reveal / verdict / re-ask surfaces, not clean-idle.
36
+ // Byte-identical to origin/main is preserved for the untouched reveal / verdict surfaces.
28
37
  // Fail-open: any error → print nothing (or just the presence line), exit 0. Never blocks the bar.
29
38
  import { readFileSync, rmSync } from "node:fs";
30
39
  import { homedir } from "node:os";
31
40
  import { join } from "node:path";
32
41
  import { tailLines } from "./transcript.mjs";
33
- import { cleanText, REVEAL_PATH, VERDICT_PATH, revealEnabled, autoDarkened } from "./insights.mjs";
42
+ import {
43
+ cleanText,
44
+ REVEAL_PATH,
45
+ VERDICT_PATH,
46
+ revealEnabled,
47
+ autoDarkened,
48
+ ADJUDICATIONS_PATH,
49
+ SUPPRESS_PATH,
50
+ proofSig,
51
+ } from "./insights.mjs";
34
52
 
35
53
  const CYAN = "\x1b[36m";
36
54
  const GREEN = "\x1b[32m";
37
55
  const BOLD = "\x1b[1m";
38
56
  const DIM = "\x1b[2m";
39
57
  const RESET = "\x1b[0m";
58
+ // The skalpel red — the ONE accent, used ONCE per line at most (only on `M false` when lies > 0).
59
+ const RED = "\x1b[38;2;233;38;31m";
60
+
61
+ // The composed wordmark + separators of the instrument line. Em-dash after the wordmark (it breathes);
62
+ // dim mid-dots between clauses. ANSI only, one line, no animation — the bar re-invokes this command.
63
+ const WORDMARK = `🔬 ${BOLD}skalpel${RESET}`;
64
+ const EMDASH = `${DIM} — ${RESET}`;
65
+ const MIDDOT = ` ${DIM}·${RESET} `;
40
66
 
41
- // ── RENDER MODE — the ONE constant Ryan flips. Governs the optional TIME clause on the always-on
42
- // `skalpel · N steers` baseline. The steers count + Raahil's #609 BOLD/CYAN styling are ALWAYS present,
43
- // unchanged, in every mode. Nothing else on the bar (reveal / verdict / re-ask / steer label) is affected.
44
- // "steers" → skalpel · N steers
45
- // "measured" → skalpel · N steers · 38m caught (DEFAULT — cumulative lifetime MEASURED red→green)
46
- // "saved" → skalpel · N steers · ~7m saved (Raahil's own-graph ESTIMATE, from steers.ndjson)
47
- // "both" → skalpel · N steers · 38m caught · ~7m saved
67
+ // ── RENDER MODE — the ONE constant Ryan flips. Governs the optional TIME clause(s) on the ambient
68
+ // instrument line. The heartbeat (claims verified / checked · false) and the `N steers` clause are
69
+ // mode-independent. Nothing else on the bar (reveal / verdict / re-ask / steer label) is affected.
70
+ // "steers" → no time clause
71
+ // "measured" → · 38m caught (DEFAULT — cumulative lifetime MEASURED red→green)
72
+ // "saved" → · ~7m saved (Raahil's own-graph ESTIMATE, from steers.ndjson)
73
+ // "both" → · 38m caught · ~7m saved
48
74
  const BASELINE_TIME_MODE = "measured";
49
75
  const SHOW_MEASURED = BASELINE_TIME_MODE === "measured" || BASELINE_TIME_MODE === "both";
50
76
  const SHOW_SAVED = BASELINE_TIME_MODE === "saved" || BASELINE_TIME_MODE === "both";
@@ -335,6 +361,38 @@ function compactMeasured(ms) {
335
361
  : `${(Math.floor(h * 10) / 10).toFixed(1).replace(/\.0$/, "")}h`;
336
362
  }
337
363
 
364
+ // adjudicatedNoKeys() → the set of `${session}|${proofSig}` keys the USER adjudicated "not a real catch"
365
+ // (`skalpel no` appends a verdict:"no" row to verify-adjudications.ndjson AND a session-scoped row to
366
+ // verify-suppressed.ndjson — both are read, either is sufficient evidence). Bounded tail reads (same
367
+ // tailLines guard as the shadow log), fail-open (any error → empty set → tally unchanged). Called LAZILY
368
+ // from shadowTally only when a mismatch row actually exists, so a log with no catches — and any install
369
+ // where the adjudication files are absent — pays zero extra cost.
370
+ const ADJ_READ_BYTES = 256 * 1024;
371
+ function adjudicatedNoKeys() {
372
+ const keys = new Set();
373
+ try {
374
+ for (const l of tailLines(ADJUDICATIONS_PATH, ADJ_READ_BYTES)) {
375
+ try {
376
+ const r = JSON.parse(l);
377
+ if (r && r.verdict === "no" && r.proof) keys.add(`${r.session ?? ""}|${proofSig(r.proof)}`);
378
+ } catch {
379
+ /* partial / garbage line — skip */
380
+ }
381
+ }
382
+ for (const l of tailLines(SUPPRESS_PATH, ADJ_READ_BYTES)) {
383
+ try {
384
+ const r = JSON.parse(l);
385
+ if (r && r.session && r.proof) keys.add(`${r.session}|${proofSig(r.proof)}`);
386
+ } catch {
387
+ /* partial / garbage line — skip */
388
+ }
389
+ }
390
+ } catch {
391
+ /* fail-open: no adjudication data → nothing excluded */
392
+ }
393
+ return keys;
394
+ }
395
+
338
396
  // measuredCaughtTotalMs() → the CUMULATIVE LIFETIME sum, in ms, of EVERY real measured red→green delta:
339
397
  // each caught false "done" (mismatch row) that has a later same-session same-proof PASS contributes that
340
398
  // one measured (t_pass − t_false) delta; a catch with no later PASS contributes 0 (never estimated). This
@@ -371,7 +429,20 @@ function shadowTally() {
371
429
  const CHECKED = new Set(["PASS", "GENUINE_FAIL", "SHIP_OK", "SHIP_FAIL"]);
372
430
  let checked = 0;
373
431
  let lies = 0;
432
+ // ADJUDICATION-AWARE (2026-07-13 false-accusation incident): a mismatch row the USER marked "not a
433
+ // real catch" (`skalpel no`) must stop counting as `false` on the bar — and an adjudicated-wrong
434
+ // check isn't a check we stand behind, so it leaves `checked` (and its fixDelta leaves the measured
435
+ // total) too. Lazy: the adjudication files are only read once a mismatch row actually exists.
436
+ let noKeys = null;
437
+ const adjudicatedWrong = (r) => {
438
+ if (r.mismatch !== true) return false;
439
+ if (noKeys === null) noKeys = adjudicatedNoKeys();
440
+ if (noKeys.size === 0) return false;
441
+ // The join key mirrors the reveal/adjudication sig: a ship row's "proof" is its probe command.
442
+ return noKeys.has(`${r.session ?? ""}|${proofSig(r.proof_command || r.probe_command)}`);
443
+ };
374
444
  for (const r of rows) {
445
+ if (adjudicatedWrong(r)) continue; // user said "no" — not a lie, not a check, no measured delta
375
446
  if (CHECKED.has(r.outcome)) checked += 1;
376
447
  if (r.mismatch !== true) continue; // only a caught false "done" (verify-shadow's money metric)
377
448
  lies += 1;
@@ -423,7 +494,7 @@ function main() {
423
494
  const n = reaskCount(payload.transcript_path);
424
495
  if (n >= 2) {
425
496
  process.stdout.write(
426
- `${BOLD}skalpel${RESET} · ${CYAN}${n} re-asks/interrupts this stretch${RESET} · ${DIM}run: skalpel autopsy${RESET}`,
497
+ `${WORDMARK}${EMDASH}${CYAN}${n}${RESET} ${DIM}re-asks/interrupts this stretch${RESET}${MIDDOT}${DIM}run: skalpel autopsy${RESET}`,
427
498
  );
428
499
  return;
429
500
  }
@@ -431,44 +502,49 @@ function main() {
431
502
  // Otherwise, if a steer is live, name what it's doing — a description, no number.
432
503
  const label = steerLabel();
433
504
  if (label) {
434
- process.stdout.write(`${BOLD}skalpel${RESET} · ${CYAN}${label}${RESET}`);
505
+ process.stdout.write(`${WORDMARK}${EMDASH}${CYAN}${label}${RESET}`);
435
506
  return;
436
507
  }
437
508
 
438
- // ALWAYS-ON baseline presence line — skalpel's terminal presence (owner product decision: never gated,
439
- // never hidden). `N steers` is ALWAYS shown (fresh install reads "0 steers" honest, present, never
440
- // blank; the user's own applied-steer count). Then the ONE optional time clause selected by
441
- // BASELINE_TIME_MODE, each an AGGREGATE number only (never per-catch claim text / proof those live in
442
- // the opt-in reveal + the TUI), shown only when its real value exists:
443
- // MEASUREDXm caught" the CUMULATIVE LIFETIME sum of every real measured red→green delta
444
- // (measuredCaughtTotalMs). Always-on once real catches exist; a MEASURED wall-clock, never an
445
- // estimate. Cumulative framing (not present-tense "just caught"), so it stays honest for old catches.
446
- // Raahil's `~`-prefixed saved ESTIMATE ("· ~7m saved") shown only when saved_min > 0 (its data is
447
- // the user's own steers.ndjson credit, not the shadow log, so it keeps his always-on #609 behavior).
509
+ // THE AMBIENT INSTRUMENT LINE — skalpel's always-on terminal presence (owner product decision: never
510
+ // gated, never hidden). The wordmark leads; the body composes ONLY the clauses with real value, each
511
+ // an AGGREGATE number (never per-catch claim text / proof — those live in the opt-in reveal + the TUI):
512
+ // THE HEARTBEAT (founder: "I only see 0 steers the product is invisible while it works"):
513
+ // clean so far "3 claims verified" (the earned-silence receipt real verdicts only)
514
+ // lies caught → "31 checked · 2 false" (the honest split; `M false` carries the one red accent)
515
+ // MEASURED "Xm caught" the CUMULATIVE LIFETIME sum of every real measured red→green delta
516
+ // (shadowTally). IMMUTABLE product surface: rendered whenever it has a value; a MEASURED
517
+ // wall-clock, never an estimate. Cumulative framing keeps it honest for old catches.
518
+ // • "N steers" — the user's own applied-steer count, whenever > 0 (always-on WHEN IT HAS VALUE;
519
+ // the redesign only removed the dead "0 steers" zero — presence is now the wordmark itself).
520
+ // • Raahil's `~`-prefixed saved ESTIMATE ("~7m saved") — mode-gated, shown only when saved_min > 0
521
+ // (its data is the user's own steers.ndjson credit, not the shadow log — #609 behavior preserved).
522
+ // Nothing yet (no verdicts, no steers, no measured) → the quiet watching line, never a dead zero.
448
523
  const { steers, savedMin } = readState();
449
- const tally = SHOW_MEASURED ? shadowTally() : { totalMs: 0, checked: 0, lies: 0 };
450
- let line = `${BOLD}skalpel${RESET} · ${CYAN}${steers} steers${RESET}`;
524
+ const tally = shadowTally();
525
+ const clauses = [];
526
+ if (tally.checked > 0) {
527
+ if (tally.lies > 0) {
528
+ clauses.push(`${CYAN}${tally.checked}${RESET} ${DIM}checked${RESET}`);
529
+ clauses.push(`${RED}${tally.lies} false${RESET}`); // the ONE red accent, used once
530
+ } else {
531
+ clauses.push(
532
+ `${CYAN}${tally.checked}${RESET} ${DIM}claim${tally.checked === 1 ? "" : "s"} verified${RESET}`,
533
+ );
534
+ }
535
+ }
451
536
  if (SHOW_MEASURED) {
452
537
  const caught = compactMeasured(tally.totalMs); // null when < 1s of real measured catches
453
- if (caught) line += ` · ${CYAN}${caught} caught${RESET}`;
454
- // THE HEARTBEAT (founder: "I only see 0 steers — the product is invisible while it works").
455
- // Once the armed shadow has genuinely CHECKED at least one claim, the line SHOWS the work:
456
- // clean so far → "· 3 claims verified" (the earned-silence receipt — real verdicts only)
457
- // lies caught → "· 31 checked · 2 false" (the honest split; the measured clause carries the time)
458
- // Real counts from the same single log pass. A fresh install (checked = 0) emits EXACTLY the
459
- // baseline line — byte-identical, nothing invented.
460
- if (tally.checked > 0) {
461
- line +=
462
- tally.lies > 0
463
- ? ` · ${CYAN}${tally.checked} checked · ${tally.lies} false${RESET}`
464
- : ` · ${CYAN}${tally.checked} claim${tally.checked === 1 ? "" : "s"} verified${RESET}`;
465
- }
538
+ if (caught) clauses.push(`${CYAN}${caught}${RESET} ${DIM}caught${RESET}`);
466
539
  }
540
+ if (steers > 0)
541
+ clauses.push(`${CYAN}${steers}${RESET} ${DIM}steer${steers === 1 ? "" : "s"}${RESET}`);
467
542
  if (SHOW_SAVED) {
468
543
  const phrase = savedPhrase(savedMin);
469
- if (phrase) line += ` · ${CYAN}${phrase}${RESET}`;
544
+ if (phrase) clauses.push(`${CYAN}${phrase}${RESET}`);
470
545
  }
471
- process.stdout.write(line);
546
+ const body = clauses.length ? clauses.join(MIDDOT) : `${DIM}watching your agent's claims${RESET}`;
547
+ process.stdout.write(`${WORDMARK}${EMDASH}${body}`);
472
548
  }
473
549
 
474
550
  // Fail-open: wrap the whole render so a thrown error can never spill a stack trace into the live status
package/verify-shadow.mjs CHANGED
@@ -1013,6 +1013,21 @@ const SHIP_CLAIM_PATTERNS = [
1013
1013
  const SHIP_FUTURE_OR_QUESTION =
1014
1014
  /\b(?:will|i'?ll|we'?ll|going\s+to|gonna|about\s+to|let\s+me|planning\s+to|need\s+to|shall\s+i|should\s+i|can\s+i|once\s+|after\s+i|before\s+i|ready\s+to)\b/i;
1015
1015
 
1016
+ // PRECISION (2026-07-13 false-accusation incident): markdown STRUCTURE is not a declarative sentence.
1017
+ // A real ship claim ("Deployed to prod.", "it's live at https://…") is a plain declarative line; a
1018
+ // bullet / table row / blockquote / heading / bold-lead line is the assistant NARRATING or summarizing —
1019
+ // a status-table bullet ("- **`npx skalpel@latest` (what customers install):** 4.0.52 — …") once minted
1020
+ // a false SHIP_FAIL on the founder's own machine. NARROWING only: skip such lines outright.
1021
+ const SHIP_MARKDOWN_STRUCTURE = /^(?:[-*+]\s|\||>|#|\*\*)/;
1022
+ // shipMatchInBackticks(line, offset) → does the match offset fall inside an ODD backtick segment (i.e.
1023
+ // inside inline `code`)? A ship keyword inside backticks is being QUOTED (a command, a flag, output),
1024
+ // never asserted. Split-on-backtick parity: chars after an odd number of ` are inside code.
1025
+ function shipMatchInBackticks(line, offset) {
1026
+ let ticks = 0;
1027
+ for (let i = 0; i < offset && i < line.length; i++) if (line[i] === "`") ticks++;
1028
+ return ticks % 2 === 1;
1029
+ }
1030
+
1016
1031
  // detectShipClaim(text) → { claim:bool, kind, text:<the claim line, <=120c> }. Scans lines; returns the
1017
1032
  // first ASSERTIVE ship-status line. `kind` is the canonical claim keyword reconstructShipTargetV1 consumes.
1018
1033
  export function detectShipClaim(text) {
@@ -1023,8 +1038,10 @@ export function detectShipClaim(text) {
1023
1038
  if (!l) continue;
1024
1039
  if (/\?\s*$/.test(l)) continue; // a question is not a claim
1025
1040
  if (SHIP_FUTURE_OR_QUESTION.test(l)) continue; // intention, not "it's done"
1041
+ if (SHIP_MARKDOWN_STRUCTURE.test(l)) continue; // markdown structure, not a declarative sentence
1026
1042
  for (const [kind, re] of SHIP_CLAIM_PATTERNS) {
1027
- if (re.test(l)) {
1043
+ const m = re.exec(l);
1044
+ if (m && !shipMatchInBackticks(l, m.index)) {
1028
1045
  return { claim: true, kind, text: l.replace(/\s+/g, " ").trim().slice(0, 120) };
1029
1046
  }
1030
1047
  }
@@ -1289,9 +1306,10 @@ function maybeSurfaceReveal(row) {
1289
1306
  }
1290
1307
  }
1291
1308
 
1292
- // SHIP-STATUS v1 confirm-gate: the wait between a URL FAIL_CANDIDATE (404/410/NXDOMAIN/refused) and its
1293
- // ONE confirming re-probe. Default ~60s; overridable via env (SKALPEL_SHIP_CONFIRM_MS, 0 for tests). The
1294
- // wait happens ONLY inside the DETACHED, unref'd `--run` worker never on the per-turn hook deadline.
1309
+ // SHIP-STATUS v1 confirm-gate: the wait between ANY first-probe negative (a URL FAIL_CANDIDATE 404/
1310
+ // 410/NXDOMAIN/refused or a hard registry/tag/pr/git/checks FAIL) and its ONE confirming re-probe.
1311
+ // Default ~60s; overridable via env (SKALPEL_SHIP_CONFIRM_MS, 0 for tests). The wait happens ONLY inside
1312
+ // the DETACHED, unref'd `--run` worker — never on the per-turn hook deadline.
1295
1313
  const SHIP_CONFIRM_DELAY_MS = (() => {
1296
1314
  const v = parseInt(process.env.SKALPEL_SHIP_CONFIRM_MS ?? "", 10);
1297
1315
  return Number.isFinite(v) && v >= 0 ? v : 60_000;
@@ -1305,7 +1323,7 @@ function shipSleep(ms) {
1305
1323
 
1306
1324
  // resolveShipStatus({kind, contextText, payload, target?, exec?, ...}) — the TESTABLE core of the ship
1307
1325
  // catch: reconstruct the strict single-family target (or accept a prebuilt one), run its ONE read-only
1308
- // probe, and (only for a URL FAIL_CANDIDATE) confirm-gate before ever calling it a red. Returns a plain
1326
+ // probe, and (for EVERY first-probe negative, any kind) confirm-gate before ever calling it a red. Returns a plain
1309
1327
  // result object — no I/O, no lock, no reveal. `exec` (the read-only command runner) and `sleep` are
1310
1328
  // injectable purely so the eval harness can drive every branch hermetically. Never throws to the caller.
1311
1329
  // outcome ∈ { NO_TARGET, SHIP_OK, SHIP_FAIL, SHIP_UNKNOWN }; mismatch === (outcome === "SHIP_FAIL").
@@ -1338,10 +1356,16 @@ export async function resolveShipStatus({
1338
1356
  let evidence = first.evidence;
1339
1357
  let confirm_reprobe = false;
1340
1358
  let confirm_outcome = null;
1341
- // Defect 3 CONFIRM-GATE: a URL fail candidate (a 404/410/gone or a NXDOMAIN/refused, which can be pure
1342
- // CDN/propagation lag) NEVER reveals immediately — wait ~60s and re-probe ONCE; only a CONFIRMED
1343
- // double-negative (2/2 fail candidate) fires. A PASS on the re-probe means it was transient → SHIP_OK.
1344
- if (verdict === "FAIL_CANDIDATE" && tgt.kind === "url") {
1359
+ // CONFIRM-GATE for ALL ship reds (Defect 3, widened after the 2026-07-13 false accusation): NO
1360
+ // first-probe negative EVER reds immediately — not a URL FAIL_CANDIDATE (404/410/NXDOMAIN/refused:
1361
+ // pure CDN/propagation lag) and not a hard registry/tag/pr/git/checks "FAIL" (replication lag, an
1362
+ // eventually-consistent registry read, a stale gh view). Every negative waits (SHIP_CONFIRM_DELAY_MS,
1363
+ // or the injected confirmDelayMs) and re-probes ONCE; only a CONFIRMED 2/2 negative becomes a red.
1364
+ // A PASS on the confirm means it was transient → SHIP_OK; an UNKNOWN on the confirm → SHIP_UNKNOWN
1365
+ // (never a red we can't stand behind). runShipProbeV1's verdict vocabulary is exactly
1366
+ // PASS | FAIL | FAIL_CANDIDATE | UNKNOWN — both negative forms gate identically here.
1367
+ const shipNegative = (v) => v === "FAIL" || v === "FAIL_CANDIDATE";
1368
+ if (shipNegative(verdict)) {
1345
1369
  if (typeof beforeReprobe === "function") {
1346
1370
  try {
1347
1371
  beforeReprobe();
@@ -1355,11 +1379,11 @@ export async function resolveShipStatus({
1355
1379
  confirm_reprobe = true;
1356
1380
  confirm_outcome = second.verdict;
1357
1381
  evidence = second.evidence || evidence;
1358
- verdict =
1359
- second.verdict === "FAIL_CANDIDATE" ? "FAIL" : second.verdict === "PASS" ? "PASS" : "UNKNOWN";
1360
- } else if (verdict === "FAIL_CANDIDATE") {
1361
- // A non-URL probe should never yield a lone candidate; treat as UNKNOWN so it can never single-red.
1362
- verdict = "UNKNOWN";
1382
+ verdict = shipNegative(second.verdict)
1383
+ ? "FAIL"
1384
+ : second.verdict === "PASS"
1385
+ ? "PASS"
1386
+ : "UNKNOWN";
1363
1387
  }
1364
1388
  const outcome =
1365
1389
  verdict === "PASS" ? "SHIP_OK" : verdict === "FAIL" ? "SHIP_FAIL" : "SHIP_UNKNOWN";
@@ -1447,7 +1471,7 @@ async function maybeShipStatusCatch({ text, session, cwd, transcriptPath }) {
1447
1471
  pass_fail,
1448
1472
  outcome,
1449
1473
  mismatch: outcome === "SHIP_FAIL", // claimed shipped, the read-only probe DECISIVELY says it is NOT
1450
- // The instrument records the confirm re-probe (URL fail-candidate) and its second-probe verdict.
1474
+ // The instrument records the confirm re-probe (any first-probe negative) and its second verdict.
1451
1475
  confirm_reprobe: resolved.confirm_reprobe,
1452
1476
  confirm_outcome: resolved.confirm_outcome,
1453
1477
  evidence: String(resolved.evidence || "").slice(0, 200),