skalpel 4.0.39 → 4.0.40

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/eval-harness.mjs CHANGED
@@ -25,6 +25,7 @@
25
25
 
26
26
  import fs from "node:fs";
27
27
  import path from "node:path";
28
+ import { fileURLToPath } from "node:url";
28
29
 
29
30
  // ───────────────────────────── tunables (UNCALIBRATED defaults) ─────────────────────────────
30
31
  // These thresholds are NOT tuned against a human gold set unless one is supplied via --gold.
@@ -1117,9 +1118,22 @@ function main() {
1117
1118
  console.log("═".repeat(90));
1118
1119
  }
1119
1120
 
1120
- const isMain =
1121
- process.argv[1] &&
1122
- path.resolve(process.argv[1]) === path.resolve(new URL(import.meta.url).pathname);
1121
+ // Robust main detection (mirrors verify-shadow.mjs / skalpel-setup.mjs): compare REAL paths via
1122
+ // realpathSync, not a hand-built path.resolve(url.pathname). On Windows, `new URL(import.meta.url)
1123
+ // .pathname` yields "/C:/Users/..." (a leading slash BEFORE the drive letter); path.resolve() on that
1124
+ // string produces "\C:\Users\..." — a different string than path.resolve(process.argv[1])'s
1125
+ // "C:\Users\...". isMain then silently evaluates false, so `node eval-harness.mjs <dir>` (the CLI's
1126
+ // entire purpose) does nothing at all on Windows: no usage, no report, no error, exit 0.
1127
+ const isMain = (() => {
1128
+ try {
1129
+ return (
1130
+ Boolean(process.argv[1]) &&
1131
+ fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url))
1132
+ );
1133
+ } catch {
1134
+ return false;
1135
+ }
1136
+ })();
1123
1137
  if (isMain) main();
1124
1138
 
1125
1139
  export { V1_onset, V2_cue, V3_repeat, FIRE_ALWAYS, FIRE_NEVER, calibrate };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skalpel",
3
- "version": "4.0.39",
3
+ "version": "4.0.40",
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": {
@@ -139,10 +139,17 @@ function steerLabel() {
139
139
  // line (fresh + honest — the statusline never invents it, it only renders what the re-run recorded) or
140
140
  // null. Auto-clears when stale (past the TTL) so an old catch can't wallpaper the bar. GATED entirely on
141
141
  // SKALPEL_VERIFY_REVEAL: when off, this is never called and the bar is byte-identical to the baseline.
142
- function revealNote() {
142
+ function revealNote(sessionId) {
143
143
  try {
144
144
  const r = JSON.parse(readFileSync(REVEAL_PATH, "utf8"));
145
145
  if (!r || typeof r.line !== "string" || !r.line.trim()) return null;
146
+ // SESSION SCOPING: verify-reveal.json is a single shared path across every concurrent Claude Code
147
+ // session (multiple tabs/worktrees). Without this check, this pane would show — and misattribute to
148
+ // "your agent" — a catch that actually came from a DIFFERENT session's transcript. Only skip when
149
+ // BOTH sides know their session and they disagree; an unknown session on either side stays permissive.
150
+ // NOT deleted here (unlike the stale-TTL branch below) — it may still be the right reveal for whichever
151
+ // session actually owns it.
152
+ if (r.session && sessionId && r.session !== sessionId) return null;
146
153
  const age = Date.now() - Date.parse(r.ts);
147
154
  if (!Number.isFinite(age) || age > REVEAL_TTL_MS) {
148
155
  try {
@@ -361,7 +368,7 @@ function main() {
361
368
  // SKALPEL_VERIFY_REVEAL — when off (the default) this branch never runs and the bar is byte-identical to
362
369
  // the always-on baseline below.
363
370
  if (revealEnabled(process.env)) {
364
- const reveal = revealNote();
371
+ const reveal = revealNote(payload.session_id);
365
372
  if (reveal) {
366
373
  process.stdout.write(`${BOLD}${reveal}${RESET}`);
367
374
  return;
package/verify-shadow.mjs CHANGED
@@ -877,9 +877,23 @@ function writeReveal(row) {
877
877
  }
878
878
  }
879
879
 
880
- // clearReveal() — retract a prior catch (the agent actually fixed it: a later proof re-run PASSED).
881
- function clearReveal() {
880
+ // clearReveal(session) — retract a prior catch (the agent actually fixed it: a later proof re-run
881
+ // PASSED). SESSION-SCOPED: verify-reveal.json is a single shared path under ~/.skalpel, read/written by
882
+ // every concurrent Claude Code session (multiple tabs/worktrees is a normal prosumer workflow). Without
883
+ // this guard, session B's PASSING proof would silently wipe out session A's still-unresolved GENUINE_FAIL
884
+ // reveal — the user in session A would never see the catch they hadn't gotten to yet, and session B gets
885
+ // credit for "fixing" something it never touched. Only refuse to clear when BOTH sides know their session
886
+ // and they disagree; an unknown session on either side stays permissive (matches prior single-session
887
+ // behavior, and a manual/CLI-driven re-run with no session context still self-heals as before).
888
+ function clearReveal(session) {
882
889
  try {
890
+ let existing = null;
891
+ try {
892
+ existing = JSON.parse(readFileSync(REVEAL_PATH, "utf8"));
893
+ } catch {
894
+ /* no reveal file, or unreadable — nothing to protect; fall through to the (idempotent) remove */
895
+ }
896
+ if (existing && existing.session && session && existing.session !== session) return; // not ours
883
897
  rmSync(REVEAL_PATH, { force: true });
884
898
  } catch {
885
899
  /* stale reveal self-expires via the statusline's TTL anyway */
@@ -946,7 +960,7 @@ function maybeSurfaceReveal(row) {
946
960
  session: row.session || null,
947
961
  });
948
962
  } else if (isPass) {
949
- clearReveal(); // resolved — don't wallpaper a catch the agent already fixed
963
+ clearReveal(row.session || null); // resolved — don't wallpaper a catch the agent already fixed
950
964
  // The green VERDICT stamp is #612's TEST-pass path only: its line ("re-ran `<proof>`, exit 0") is a
951
965
  // real test re-run receipt. A ship SHIP_OK (read-only probe came back positive) has no re-run and no
952
966
  // proof_command, so it must NOT borrow that wording — it only clears any prior catch (as #611 did).