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 +14 -3
- package/eval-harness.mjs +10 -4
- package/package.json +1 -1
- package/skalpel-statusline.mjs +128 -52
- package/verify-shadow.mjs +39 -15
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 (
|
|
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
|
|
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(
|
|
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
package/skalpel-statusline.mjs
CHANGED
|
@@ -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
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
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
|
-
//
|
|
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 {
|
|
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
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
// "steers" →
|
|
45
|
-
// "measured" →
|
|
46
|
-
// "saved" →
|
|
47
|
-
// "both" →
|
|
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
|
-
`${
|
|
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(`${
|
|
505
|
+
process.stdout.write(`${WORDMARK}${EMDASH}${CYAN}${label}${RESET}`);
|
|
435
506
|
return;
|
|
436
507
|
}
|
|
437
508
|
|
|
438
|
-
//
|
|
439
|
-
// never hidden).
|
|
440
|
-
//
|
|
441
|
-
//
|
|
442
|
-
//
|
|
443
|
-
//
|
|
444
|
-
//
|
|
445
|
-
//
|
|
446
|
-
//
|
|
447
|
-
//
|
|
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 =
|
|
450
|
-
|
|
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)
|
|
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)
|
|
544
|
+
if (phrase) clauses.push(`${CYAN}${phrase}${RESET}`);
|
|
470
545
|
}
|
|
471
|
-
|
|
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
|
-
|
|
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
|
|
1293
|
-
//
|
|
1294
|
-
//
|
|
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 (
|
|
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
|
-
//
|
|
1342
|
-
//
|
|
1343
|
-
//
|
|
1344
|
-
|
|
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
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
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 (
|
|
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),
|