@guilz-dev/belay 0.9.2 → 0.9.3

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.
Files changed (55) hide show
  1. package/README.md +1 -1
  2. package/dist/adapters/codex/runtime-entry.d.ts +3 -0
  3. package/dist/adapters/codex/runtime-entry.js +27 -4
  4. package/dist/adapters/cursor/cwd-resolution.d.ts +10 -0
  5. package/dist/adapters/cursor/cwd-resolution.js +58 -0
  6. package/dist/adapters/cursor/hooks.d.ts +1 -0
  7. package/dist/adapters/cursor/hooks.js +21 -0
  8. package/dist/adapters/cursor/runtime-entry.d.ts +1 -0
  9. package/dist/adapters/cursor/runtime-entry.js +107 -6
  10. package/dist/adapters/shared/gate-runtime.js +26 -3
  11. package/dist/adapters/shared/repo-root.js +20 -1
  12. package/dist/bundle/claude-runtime.mjs +855 -338
  13. package/dist/bundle/codex-runtime.mjs +877 -342
  14. package/dist/bundle/cursor-runtime.mjs +3886 -3141
  15. package/dist/cli.js +33 -3
  16. package/dist/commands/doctor.js +24 -1
  17. package/dist/commands/health-snapshot.d.ts +3 -0
  18. package/dist/commands/health-snapshot.js +56 -0
  19. package/dist/commands/report.js +14 -0
  20. package/dist/commands/status.js +15 -0
  21. package/dist/commands/where.d.ts +4 -0
  22. package/dist/commands/where.js +52 -0
  23. package/dist/core/approval-repo-lookup.d.ts +16 -0
  24. package/dist/core/approval-repo-lookup.js +48 -0
  25. package/dist/core/audit-io.d.ts +1 -1
  26. package/dist/core/audit-io.js +1 -1
  27. package/dist/core/audit-query.d.ts +1 -0
  28. package/dist/core/audit-query.js +7 -0
  29. package/dist/core/audit-serialize.d.ts +3 -0
  30. package/dist/core/audit-serialize.js +39 -3
  31. package/dist/core/audit-summary.d.ts +9 -0
  32. package/dist/core/audit-summary.js +58 -1
  33. package/dist/core/audit-types.d.ts +4 -0
  34. package/dist/core/effect-ir/shell-lower.js +93 -40
  35. package/dist/core/replay-scrub.d.ts +1 -0
  36. package/dist/core/replay-scrub.js +22 -3
  37. package/dist/core/shell-tokenizer.d.ts +28 -0
  38. package/dist/core/shell-tokenizer.js +111 -29
  39. package/dist/core/verdict/docker-compose-run.d.ts +18 -0
  40. package/dist/core/verdict/docker-compose-run.js +136 -0
  41. package/dist/core/verdict/launcher-resolve.js +28 -28
  42. package/dist/core/verdict/parser.d.ts +3 -0
  43. package/dist/core/verdict/parser.js +23 -40
  44. package/dist/core/verdict/recursive-invocation.d.ts +20 -0
  45. package/dist/core/verdict/recursive-invocation.js +224 -0
  46. package/dist/defaults.js +7 -0
  47. package/dist/installer/scope-config.d.ts +2 -2
  48. package/dist/installer.d.ts +10 -1
  49. package/dist/installer.js +43 -2
  50. package/dist/types.d.ts +34 -1
  51. package/dist/version.d.ts +1 -1
  52. package/dist/version.js +1 -1
  53. package/package.json +5 -2
  54. package/skills/belay/SKILL.md +5 -0
  55. package/skills/belay/belay-report.md +4 -1
@@ -355,60 +355,135 @@ function isRedirectOperator(token) {
355
355
  function isFdDuplication(token) {
356
356
  return FD_DUPLICATION_PATTERN.test(token);
357
357
  }
358
- function tokenizeShell(input) {
358
+ function lexShell(input) {
359
359
  const tokens = [];
360
- let buffer = "";
360
+ let value = "";
361
+ let wordStart = null;
362
+ let parts = [];
361
363
  let quote = null;
362
- let escaping = false;
363
- const flush = () => {
364
- if (buffer.length > 0) {
365
- tokens.push(buffer);
366
- buffer = "";
364
+ let quoteStart = -1;
365
+ let quoteHadContent = false;
366
+ let complete = true;
367
+ const startWord = (index) => {
368
+ wordStart ??= index;
369
+ };
370
+ const append = (decoded, start, end, mode, hasExpansion) => {
371
+ startWord(start);
372
+ value += decoded;
373
+ const previous = parts.at(-1);
374
+ if (previous && previous.quote === mode && previous.hasExpansion === hasExpansion && previous.end === start) {
375
+ previous.value += decoded;
376
+ previous.raw += input.slice(start, end);
377
+ previous.end = end;
378
+ return;
367
379
  }
380
+ parts.push({
381
+ value: decoded,
382
+ raw: input.slice(start, end),
383
+ start,
384
+ end,
385
+ quote: mode,
386
+ hasExpansion
387
+ });
388
+ };
389
+ const flushWord = (end) => {
390
+ if (wordStart === null) return;
391
+ tokens.push({
392
+ kind: "word",
393
+ value,
394
+ raw: input.slice(wordStart, end),
395
+ start: wordStart,
396
+ end,
397
+ parts
398
+ });
399
+ value = "";
400
+ wordStart = null;
401
+ parts = [];
402
+ };
403
+ const pushOperator = (token, start, end) => {
404
+ tokens.push({ kind: "operator", value: token, raw: input.slice(start, end), start, end });
368
405
  };
369
406
  for (let index = 0; index < input.length; index += 1) {
370
- const char = input[index];
371
- if (escaping) {
372
- buffer += char;
373
- escaping = false;
374
- continue;
375
- }
376
- if (char === "\\") {
377
- escaping = true;
407
+ const char = input[index] ?? "";
408
+ if (quote === "single") {
409
+ if (char === "'") {
410
+ if (!quoteHadContent) append("", quoteStart, index + 1, "single", false);
411
+ quote = null;
412
+ } else {
413
+ append(char, index, index + 1, "single", false);
414
+ quoteHadContent = true;
415
+ }
378
416
  continue;
379
417
  }
380
- if (quote) {
381
- if (char === quote) {
418
+ if (quote === "double") {
419
+ if (char === '"') {
420
+ if (!quoteHadContent) append("", quoteStart, index + 1, "double", false);
382
421
  quote = null;
383
- } else {
384
- buffer += char;
422
+ continue;
385
423
  }
424
+ if (char === "\\") {
425
+ const next = input[index + 1];
426
+ if (next === void 0) {
427
+ append("\\", index, index + 1, "double", false);
428
+ complete = false;
429
+ continue;
430
+ }
431
+ if (next === "$" || next === "`" || next === '"' || next === "\\" || next === "\n") {
432
+ append(next === "\n" ? "" : next, index, index + 2, "double", false);
433
+ quoteHadContent = true;
434
+ index += 1;
435
+ continue;
436
+ }
437
+ append("\\", index, index + 1, "double", false);
438
+ quoteHadContent = true;
439
+ continue;
440
+ }
441
+ append(char, index, index + 1, "double", char === "$" || char === "`");
442
+ quoteHadContent = true;
386
443
  continue;
387
444
  }
388
- if (char === '"' || char === "'") {
389
- quote = char;
445
+ if (char === "'" || char === '"') {
446
+ startWord(index);
447
+ quote = char === "'" ? "single" : "double";
448
+ quoteStart = index;
449
+ quoteHadContent = false;
450
+ continue;
451
+ }
452
+ if (char === "\\") {
453
+ const next = input[index + 1];
454
+ if (next === void 0) {
455
+ append("\\", index, index + 1, "unquoted", false);
456
+ complete = false;
457
+ continue;
458
+ }
459
+ append(next, index, index + 2, "unquoted", false);
460
+ index += 1;
390
461
  continue;
391
462
  }
392
463
  const operator = readShellOperator(input, index);
393
464
  if (operator) {
394
- flush();
395
- tokens.push(operator.token);
465
+ flushWord(index);
466
+ pushOperator(operator.token, index, index + operator.length);
396
467
  index += operator.length - 1;
397
468
  continue;
398
469
  }
399
470
  if (char === "\n" || char === "\r") {
400
- flush();
401
- tokens.push(";");
471
+ flushWord(index);
472
+ pushOperator(";", index, index + 1);
402
473
  continue;
403
474
  }
404
475
  if (/\s/.test(char)) {
405
- flush();
476
+ flushWord(index);
406
477
  continue;
407
478
  }
408
- buffer += char;
479
+ append(char, index, index + 1, "unquoted", char === "$" || char === "`");
409
480
  }
410
- flush();
411
- return tokens;
481
+ if (quote !== null) complete = false;
482
+ flushWord(input.length);
483
+ return { tokens, complete };
484
+ }
485
+ function tokenizeShell(input) {
486
+ return lexShell(input).tokens.map((token) => token.value);
412
487
  }
413
488
  function commandKey(tokens) {
414
489
  const filtered = tokens.filter((token) => token !== "sudo");
@@ -1400,6 +1475,22 @@ import { createHash as createHash2 } from "node:crypto";
1400
1475
  function approvalCorrelationId(approvalId) {
1401
1476
  return createHash2("sha256").update(approvalId).digest("hex").slice(0, 16);
1402
1477
  }
1478
+ function canonicalToolUseIdForCorrelation(toolUseId) {
1479
+ const trimmed = toolUseId.trim();
1480
+ if (trimmed.startsWith("tool_")) {
1481
+ const remainder = trimmed.slice("tool_".length);
1482
+ if (TOOL_USE_UUID_PATTERN.test(remainder)) {
1483
+ return remainder.toLowerCase();
1484
+ }
1485
+ }
1486
+ if (TOOL_USE_UUID_PATTERN.test(trimmed)) {
1487
+ return trimmed.toLowerCase();
1488
+ }
1489
+ return trimmed;
1490
+ }
1491
+ function toolInvocationCorrelationId(toolUseId) {
1492
+ return createHash2("sha256").update(canonicalToolUseIdForCorrelation(toolUseId)).digest("hex").slice(0, 16);
1493
+ }
1403
1494
  function isValidApprovalCorrelationId(value) {
1404
1495
  return /^[a-f0-9]{16}$/.test(value);
1405
1496
  }
@@ -1422,7 +1513,16 @@ function isValidPreservedHashField(field, value) {
1422
1513
  return isValidAuditFingerprint(value);
1423
1514
  }
1424
1515
  function scrubAuditContainer(value, options) {
1425
- return scrubValue(value, {
1516
+ const withoutRawToolIds = (input) => {
1517
+ if (Array.isArray(input)) return input.map(withoutRawToolIds);
1518
+ if (input && typeof input === "object") {
1519
+ return Object.fromEntries(
1520
+ Object.entries(input).filter(([key]) => key !== "tool_use_id").map(([key, child]) => [key, withoutRawToolIds(child)])
1521
+ );
1522
+ }
1523
+ return input;
1524
+ };
1525
+ return scrubValue(withoutRawToolIds(value), {
1426
1526
  ...options,
1427
1527
  maskHighEntropyStrings: true
1428
1528
  });
@@ -1441,7 +1541,7 @@ function serializeAuditField(key, value, options) {
1441
1541
  if (key === "timestamp" && typeof value === "string" && isValidAuditTimestamp(value)) {
1442
1542
  return value;
1443
1543
  }
1444
- if (key === "approvalCorrelationId" && typeof value === "string" && isValidApprovalCorrelationId(value)) {
1544
+ if ((key === "approvalCorrelationId" || key === "toolInvocationCorrelationId") && typeof value === "string" && isValidApprovalCorrelationId(value)) {
1445
1545
  return value;
1446
1546
  }
1447
1547
  if ((key === "runtimeVersion" || key === "runtimeBuildStamp" || key === "boundaryProfile") && typeof value === "string" && value.length > 0) {
@@ -1493,7 +1593,7 @@ function serializeAuditRecordV3(record, options) {
1493
1593
  serialized.approvalCorrelationId = record.approvalCorrelationId;
1494
1594
  }
1495
1595
  for (const [key, value] of Object.entries(record)) {
1496
- if (key === "timestamp" || key === "ts" || key === "approvalId" || key === "schemaVersion") {
1596
+ if (key === "timestamp" || key === "ts" || key === "approvalId" || key === "tool_use_id" || key === "schemaVersion") {
1497
1597
  continue;
1498
1598
  }
1499
1599
  const next = serializeAuditField(key, value, options);
@@ -1503,7 +1603,7 @@ function serializeAuditRecordV3(record, options) {
1503
1603
  }
1504
1604
  return serialized;
1505
1605
  }
1506
- var AUDIT_SCHEMA_VERSION, ISO8601_PATTERN, HEX64_PATTERN, SCRUB_PLACEHOLDERS, PRESERVED_HASH_FIELDS, PRESERVED_LITERAL_FIELDS, SCRUBBED_CONTAINER_FIELDS;
1606
+ var AUDIT_SCHEMA_VERSION, ISO8601_PATTERN, HEX64_PATTERN, SCRUB_PLACEHOLDERS, PRESERVED_HASH_FIELDS, PRESERVED_LITERAL_FIELDS, SCRUBBED_CONTAINER_FIELDS, TOOL_USE_UUID_PATTERN;
1507
1607
  var init_audit_serialize = __esm({
1508
1608
  "src/core/audit-serialize.ts"() {
1509
1609
  "use strict";
@@ -1525,6 +1625,7 @@ var init_audit_serialize = __esm({
1525
1625
  PRESERVED_LITERAL_FIELDS = /* @__PURE__ */ new Set([
1526
1626
  "timestamp",
1527
1627
  "approvalCorrelationId",
1628
+ "toolInvocationCorrelationId",
1528
1629
  "runtimeVersion",
1529
1630
  "runtimeBuildStamp",
1530
1631
  "boundaryProfile",
@@ -1545,6 +1646,7 @@ var init_audit_serialize = __esm({
1545
1646
  "predictedAssessment",
1546
1647
  "observedAssessment"
1547
1648
  ]);
1649
+ TOOL_USE_UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
1548
1650
  }
1549
1651
  });
1550
1652
 
@@ -4130,7 +4232,7 @@ init_approval_replay();
4130
4232
  import { randomUUID as randomUUID6 } from "node:crypto";
4131
4233
  import { existsSync as existsSync16 } from "node:fs";
4132
4234
  import { mkdir as mkdir14, readFile as readFile15, writeFile as writeFile11 } from "node:fs/promises";
4133
- import path52 from "node:path";
4235
+ import path54 from "node:path";
4134
4236
 
4135
4237
  // src/core/approval-service.ts
4136
4238
  init_config_io();
@@ -10872,6 +10974,24 @@ init_fingerprint2();
10872
10974
 
10873
10975
  // src/core/replay-scrub.ts
10874
10976
  init_scrub();
10977
+ function redactToolInvocationId(value, rawToolUseId) {
10978
+ if (typeof value === "string") {
10979
+ return rawToolUseId ? value.replaceAll(rawToolUseId, "<tool-use-id>") : value;
10980
+ }
10981
+ if (Array.isArray(value)) {
10982
+ return value.map((item) => redactToolInvocationId(item, rawToolUseId));
10983
+ }
10984
+ if (value && typeof value === "object") {
10985
+ const result = {};
10986
+ for (const [key, child] of Object.entries(value)) {
10987
+ if (key !== "tool_use_id") {
10988
+ result[key] = redactToolInvocationId(child, rawToolUseId);
10989
+ }
10990
+ }
10991
+ return result;
10992
+ }
10993
+ return value;
10994
+ }
10875
10995
  function subagentFingerprintSource(payload, scrubOptions) {
10876
10996
  const toolInput = payload.tool_input;
10877
10997
  if (toolInput && typeof toolInput === "object") {
@@ -10904,16 +11024,20 @@ function fingerprintReplayPayload(kind, payload, scrubOptions) {
10904
11024
  if (!payload) {
10905
11025
  return void 0;
10906
11026
  }
11027
+ const replayPayload = redactToolInvocationId(
11028
+ payload,
11029
+ typeof payload.tool_use_id === "string" ? payload.tool_use_id : void 0
11030
+ );
10907
11031
  if (kind === "tool") {
10908
- const toolInput = payload.tool_input;
11032
+ const toolInput = replayPayload.tool_input;
10909
11033
  if (toolInput && typeof toolInput === "object") {
10910
11034
  return scrubValue(toolInput, scrubOptions);
10911
11035
  }
10912
11036
  }
10913
11037
  if (kind === "subagent") {
10914
- return subagentFingerprintSource(payload, scrubOptions);
11038
+ return subagentFingerprintSource(replayPayload, scrubOptions);
10915
11039
  }
10916
- return scrubValue(payload, scrubOptions);
11040
+ return scrubValue(replayPayload, scrubOptions);
10917
11041
  }
10918
11042
 
10919
11043
  // src/core/classify-subagent.ts
@@ -11005,7 +11129,7 @@ function classifySubagent(payload, repoRoot, options = {}, config) {
11005
11129
  }
11006
11130
 
11007
11131
  // src/core/classify-tool.ts
11008
- import path40 from "node:path";
11132
+ import path42 from "node:path";
11009
11133
  init_fingerprint2();
11010
11134
  init_path_utils();
11011
11135
  init_scrub();
@@ -11128,10 +11252,346 @@ init_git_resource_identity();
11128
11252
  init_path_utils();
11129
11253
  init_shell_tokenizer();
11130
11254
  import { lstatSync as lstatSync2, realpathSync as realpathSync4 } from "node:fs";
11131
- import path39 from "node:path";
11255
+ import path41 from "node:path";
11132
11256
 
11133
- // src/core/verdict/egress-classify.ts
11257
+ // src/core/verdict/docker-compose-run.ts
11258
+ import path36 from "node:path";
11259
+
11260
+ // src/core/verdict/recursive-invocation.ts
11134
11261
  import path35 from "node:path";
11262
+ var SHELL_INTERPRETERS = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "dash", "fish"]);
11263
+ var PYTHON_INTERPRETERS = /* @__PURE__ */ new Set(["python", "python3"]);
11264
+ var SHELL_SHORT_OPTIONS = /* @__PURE__ */ new Set(["c", "l", "e", "x", "u"]);
11265
+ var SHELL_NON_SCRIPT_SHORT_OPTIONS = /* @__PURE__ */ new Set(["n"]);
11266
+ var SHELL_TERMINAL_OPTIONS = /* @__PURE__ */ new Map([
11267
+ ["bash", /* @__PURE__ */ new Set(["--help", "--version"])],
11268
+ ["zsh", /* @__PURE__ */ new Set(["--version"])],
11269
+ ["fish", /* @__PURE__ */ new Set(["-h", "--help", "-v", "--version"])]
11270
+ ]);
11271
+ var SHELL_VALUE_OPTIONS = /* @__PURE__ */ new Set(["-O", "+O", "--init-file", "--rcfile"]);
11272
+ var NODE_TERMINAL_OPTIONS = /* @__PURE__ */ new Set(["-h", "--help", "--help-all", "-v", "--version"]);
11273
+ var NODE_FILE_OPTIONS = /* @__PURE__ */ new Set(["-c", "--check"]);
11274
+ var PYTHON_PROFILE = {
11275
+ scriptOptions: /* @__PURE__ */ new Set(["-c"]),
11276
+ terminalOptions: /* @__PURE__ */ new Set(["-h", "--help", "-V", "-VV", "--version"]),
11277
+ terminalValueOptions: /* @__PURE__ */ new Set(["-m"]),
11278
+ flagOptions: /* @__PURE__ */ new Set([
11279
+ "-b",
11280
+ "-bb",
11281
+ "-B",
11282
+ "-d",
11283
+ "-E",
11284
+ "-I",
11285
+ "-O",
11286
+ "-OO",
11287
+ "-P",
11288
+ "-q",
11289
+ "-s",
11290
+ "-S",
11291
+ "-u",
11292
+ "-v",
11293
+ "-x"
11294
+ ]),
11295
+ valueOptions: /* @__PURE__ */ new Set(["-W", "-X"]),
11296
+ attachedValuePrefixes: ["-W", "-X"]
11297
+ };
11298
+ var RUBY_PROFILE = {
11299
+ scriptOptions: /* @__PURE__ */ new Set(["-e"]),
11300
+ terminalOptions: /* @__PURE__ */ new Set(["-h", "--help", "-v", "--version", "--copyright"]),
11301
+ terminalValueOptions: /* @__PURE__ */ new Set([]),
11302
+ flagOptions: /* @__PURE__ */ new Set(["-d", "--debug", "-w"]),
11303
+ valueOptions: /* @__PURE__ */ new Set(["-I"]),
11304
+ attachedValuePrefixes: ["-I"]
11305
+ };
11306
+ var PERL_PROFILE = {
11307
+ scriptOptions: /* @__PURE__ */ new Set(["-e"]),
11308
+ terminalOptions: /* @__PURE__ */ new Set(["-h", "--help", "-v", "--version"]),
11309
+ terminalValueOptions: /* @__PURE__ */ new Set([]),
11310
+ flagOptions: /* @__PURE__ */ new Set([]),
11311
+ valueOptions: /* @__PURE__ */ new Set(["-I"]),
11312
+ attachedValuePrefixes: ["-I"]
11313
+ };
11314
+ var OSASCRIPT_PROFILE = {
11315
+ scriptOptions: /* @__PURE__ */ new Set(["-e"]),
11316
+ terminalOptions: /* @__PURE__ */ new Set(["-h", "--help"]),
11317
+ terminalValueOptions: /* @__PURE__ */ new Set([]),
11318
+ flagOptions: /* @__PURE__ */ new Set([]),
11319
+ valueOptions: /* @__PURE__ */ new Set(["-l"]),
11320
+ attachedValuePrefixes: []
11321
+ };
11322
+ function normalizeInterpreter(value) {
11323
+ return path35.basename(value);
11324
+ }
11325
+ function scriptResult(interpreter, token) {
11326
+ if (!token) {
11327
+ return { kind: "indeterminate", interpreter, signal: "shell.interpreter_argv_incomplete" };
11328
+ }
11329
+ if (token.parts.some((part) => part.hasExpansion)) {
11330
+ return { kind: "dynamic", interpreter, signal: "shell.script_expanded" };
11331
+ }
11332
+ return { kind: "static", interpreter, script: token.value };
11333
+ }
11334
+ function decodeShell(words, interpreter) {
11335
+ for (let index = 1; index < words.length; index += 1) {
11336
+ const option = words[index]?.value ?? "";
11337
+ if (option === "--") return { kind: "none" };
11338
+ if (SHELL_TERMINAL_OPTIONS.get(interpreter)?.has(option)) return { kind: "none" };
11339
+ if (interpreter === "bash" && SHELL_VALUE_OPTIONS.has(option)) {
11340
+ const operand = words[index + 1]?.value;
11341
+ if (!operand || operand.startsWith("-")) {
11342
+ return { kind: "indeterminate", interpreter, signal: "shell.interpreter_option_unknown" };
11343
+ }
11344
+ index += 1;
11345
+ continue;
11346
+ }
11347
+ if (!option.startsWith("-") || option === "-") return { kind: "none" };
11348
+ const flags = [...option.slice(1)];
11349
+ if (flags.length === 0) {
11350
+ return { kind: "indeterminate", interpreter, signal: "shell.interpreter_option_unknown" };
11351
+ }
11352
+ if (flags.every((flag) => SHELL_SHORT_OPTIONS.has(flag))) {
11353
+ if (!flags.includes("c")) continue;
11354
+ return scriptResult(interpreter, words[index + 1]);
11355
+ }
11356
+ if (flags.every(
11357
+ (flag) => SHELL_SHORT_OPTIONS.has(flag) || SHELL_NON_SCRIPT_SHORT_OPTIONS.has(flag)
11358
+ ) && flags.some((flag) => SHELL_NON_SCRIPT_SHORT_OPTIONS.has(flag))) {
11359
+ return { kind: "none" };
11360
+ }
11361
+ return { kind: "indeterminate", interpreter, signal: "shell.interpreter_option_unknown" };
11362
+ }
11363
+ return { kind: "none" };
11364
+ }
11365
+ function decodeSeparated(words, interpreter, profile) {
11366
+ for (let index = 1; index < words.length; index += 1) {
11367
+ const option = words[index]?.value ?? "";
11368
+ if (option === "--" || !option.startsWith("-") || option === "-") return { kind: "none" };
11369
+ if (profile.scriptOptions.has(option)) {
11370
+ return scriptResult(interpreter, words[index + 1]);
11371
+ }
11372
+ if (profile.terminalOptions.has(option)) return { kind: "none" };
11373
+ if (profile.terminalValueOptions.has(option)) {
11374
+ const operand = words[index + 1]?.value;
11375
+ if (!operand || operand.startsWith("-")) {
11376
+ return { kind: "indeterminate", interpreter, signal: "shell.interpreter_option_unknown" };
11377
+ }
11378
+ return { kind: "none" };
11379
+ }
11380
+ if (profile.flagOptions.has(option)) {
11381
+ continue;
11382
+ }
11383
+ if (profile.valueOptions.has(option)) {
11384
+ const operand = words[index + 1]?.value;
11385
+ if (!operand || operand.startsWith("-")) {
11386
+ return { kind: "indeterminate", interpreter, signal: "shell.interpreter_option_unknown" };
11387
+ }
11388
+ index += 1;
11389
+ continue;
11390
+ }
11391
+ if (profile.attachedValuePrefixes.some(
11392
+ (prefix) => option.startsWith(prefix) && option.length > prefix.length
11393
+ )) {
11394
+ continue;
11395
+ }
11396
+ return { kind: "indeterminate", interpreter, signal: "shell.interpreter_option_unknown" };
11397
+ }
11398
+ return { kind: "none" };
11399
+ }
11400
+ function decodeNode(words, interpreter) {
11401
+ const option = words[1]?.value ?? "";
11402
+ if (option === "--" || !option.startsWith("-") || option === "-") return { kind: "none" };
11403
+ if (option === "-e" || option === "--eval") {
11404
+ return scriptResult(interpreter, words[2]);
11405
+ }
11406
+ if (option.startsWith("--eval=")) {
11407
+ const script = option.slice("--eval=".length);
11408
+ if (words[1]?.parts.some((part) => part.hasExpansion)) {
11409
+ return { kind: "dynamic", interpreter, signal: "shell.script_expanded" };
11410
+ }
11411
+ return { kind: "static", interpreter, script };
11412
+ }
11413
+ if (NODE_TERMINAL_OPTIONS.has(option)) return { kind: "none" };
11414
+ if (NODE_FILE_OPTIONS.has(option)) return { kind: "none" };
11415
+ return { kind: "indeterminate", interpreter, signal: "shell.interpreter_option_unknown" };
11416
+ }
11417
+ function decodeEval(words) {
11418
+ const arguments_ = words.slice(1);
11419
+ if (arguments_.length === 0) return { kind: "none" };
11420
+ if (arguments_.some((word) => word.parts.some((part) => part.hasExpansion))) {
11421
+ return { kind: "dynamic", interpreter: "eval", signal: "shell.script_expanded" };
11422
+ }
11423
+ return {
11424
+ kind: "static",
11425
+ interpreter: "eval",
11426
+ script: arguments_.map((word) => word.value).join(" ")
11427
+ };
11428
+ }
11429
+ function decodeRecursiveInvocation(tokens) {
11430
+ if (tokens.some((token) => token.kind === "operator")) return { kind: "none" };
11431
+ const words = tokens.filter((token) => token.kind === "word");
11432
+ const interpreter = normalizeInterpreter(words[0]?.value ?? "");
11433
+ if (!interpreter) return { kind: "none" };
11434
+ if (interpreter === "eval") return decodeEval(words);
11435
+ if (SHELL_INTERPRETERS.has(interpreter)) return decodeShell(words, interpreter);
11436
+ if (PYTHON_INTERPRETERS.has(interpreter)) {
11437
+ return decodeSeparated(words, interpreter, PYTHON_PROFILE);
11438
+ }
11439
+ if (interpreter === "node") return decodeNode(words, interpreter);
11440
+ if (interpreter === "ruby") return decodeSeparated(words, interpreter, RUBY_PROFILE);
11441
+ if (interpreter === "perl") return decodeSeparated(words, interpreter, PERL_PROFILE);
11442
+ if (interpreter === "osascript") return decodeSeparated(words, interpreter, OSASCRIPT_PROFILE);
11443
+ return { kind: "none" };
11444
+ }
11445
+ function shellTokensFromValues(values, options = {}) {
11446
+ let offset = 0;
11447
+ return values.map((value) => {
11448
+ const start = offset;
11449
+ const end = start + value.length;
11450
+ offset = end + 1;
11451
+ return {
11452
+ kind: "word",
11453
+ value,
11454
+ raw: value,
11455
+ start,
11456
+ end,
11457
+ parts: [
11458
+ {
11459
+ value,
11460
+ raw: value,
11461
+ start,
11462
+ end,
11463
+ quote: "unquoted",
11464
+ hasExpansion: options.detectExpansion !== false && (value.includes("$") || value.includes("`"))
11465
+ }
11466
+ ]
11467
+ };
11468
+ });
11469
+ }
11470
+
11471
+ // src/core/verdict/docker-compose-run.ts
11472
+ var COMPOSE_GLOBAL_OPTIONS = /* @__PURE__ */ new Map([
11473
+ ["--all-resources", 0],
11474
+ ["--ansi", 1],
11475
+ ["--compatibility", 0],
11476
+ ["--dry-run", 0],
11477
+ ["--env-file", 1],
11478
+ ["-f", 1],
11479
+ ["--file", 1],
11480
+ ["--parallel", 1],
11481
+ ["--profile", 1],
11482
+ ["--progress", 1],
11483
+ ["--project-directory", 1],
11484
+ ["-p", 1],
11485
+ ["--project-name", 1]
11486
+ ]);
11487
+ var COMPOSE_RUN_OPTIONS = /* @__PURE__ */ new Map([
11488
+ ["--build", 0],
11489
+ ["--cap-add", 1],
11490
+ ["--cap-drop", 1],
11491
+ ["-d", 0],
11492
+ ["--detach", 0],
11493
+ ["--entrypoint", 1],
11494
+ ["-e", 1],
11495
+ ["--env", 1],
11496
+ ["--env-from-file", 1],
11497
+ ["-i", 0],
11498
+ ["--interactive", 0],
11499
+ ["-l", 1],
11500
+ ["--label", 1],
11501
+ ["--name", 1],
11502
+ ["--no-deps", 0],
11503
+ ["-T", 0],
11504
+ ["--no-tty", 0],
11505
+ ["-p", 1],
11506
+ ["--publish", 1],
11507
+ ["--pull", 1],
11508
+ ["-q", 0],
11509
+ ["--quiet", 0],
11510
+ ["--quiet-build", 0],
11511
+ ["--quiet-pull", 0],
11512
+ ["--remove-orphans", 0],
11513
+ ["--rm", 0],
11514
+ ["-P", 0],
11515
+ ["--service-ports", 0],
11516
+ ["--use-aliases", 0],
11517
+ ["-u", 1],
11518
+ ["--user", 1],
11519
+ ["-v", 1],
11520
+ ["--volume", 1],
11521
+ ["-w", 1],
11522
+ ["--workdir", 1]
11523
+ ]);
11524
+ function parseOptions(words, start, options) {
11525
+ let index = start;
11526
+ while (index < words.length) {
11527
+ const value = words[index]?.value ?? "";
11528
+ if (value === "--") return { kind: "ok", index: index + 1 };
11529
+ if (!value.startsWith("-") || value === "-") return { kind: "ok", index };
11530
+ const equalsIndex = value.indexOf("=");
11531
+ const name = equalsIndex === -1 ? value : value.slice(0, equalsIndex);
11532
+ const arity = options.get(name);
11533
+ if (arity === void 0) return { kind: "indeterminate" };
11534
+ if (equalsIndex !== -1) {
11535
+ if (!value.startsWith("--") || arity !== 1 || equalsIndex === value.length - 1) {
11536
+ return { kind: "indeterminate" };
11537
+ }
11538
+ index += 1;
11539
+ continue;
11540
+ }
11541
+ if (arity === 1) {
11542
+ if (!words[index + 1]) return { kind: "indeterminate" };
11543
+ index += 2;
11544
+ continue;
11545
+ }
11546
+ index += 1;
11547
+ }
11548
+ return { kind: "ok", index };
11549
+ }
11550
+ function decodeDockerComposeRun(tokens) {
11551
+ if (tokens.some((token) => token.kind === "operator")) return { kind: "none" };
11552
+ const words = tokens.filter((token) => token.kind === "word");
11553
+ const head = path36.basename(words[0]?.value ?? "");
11554
+ let index;
11555
+ if (head === "docker-compose") {
11556
+ index = 1;
11557
+ } else if (head === "docker" && words[1]?.value === "compose") {
11558
+ index = 2;
11559
+ } else {
11560
+ return { kind: "none" };
11561
+ }
11562
+ const globalOptions = parseOptions(words, index, COMPOSE_GLOBAL_OPTIONS);
11563
+ if (globalOptions.kind === "indeterminate") {
11564
+ return { kind: "indeterminate", signal: "shell.compose_argv_indeterminate" };
11565
+ }
11566
+ index = globalOptions.index;
11567
+ if (words[index]?.value !== "run") return { kind: "none" };
11568
+ const runOptions = parseOptions(words, index + 1, COMPOSE_RUN_OPTIONS);
11569
+ if (runOptions.kind === "indeterminate") {
11570
+ return { kind: "indeterminate", signal: "shell.compose_argv_indeterminate" };
11571
+ }
11572
+ index = runOptions.index;
11573
+ const service = words[index]?.value;
11574
+ if (!service) return { kind: "indeterminate", signal: "shell.compose_argv_indeterminate" };
11575
+ const command = words.slice(index + 1);
11576
+ if (command.length === 0) return { kind: "none" };
11577
+ const recursive = decodeRecursiveInvocation(command);
11578
+ if (recursive.kind === "static") {
11579
+ return {
11580
+ kind: "recursive",
11581
+ service,
11582
+ interpreter: recursive.interpreter,
11583
+ script: recursive.script
11584
+ };
11585
+ }
11586
+ if (recursive.kind === "dynamic") return { kind: "dynamic", service, signal: recursive.signal };
11587
+ if (recursive.kind === "indeterminate") {
11588
+ return { kind: "indeterminate", signal: "shell.compose_argv_indeterminate" };
11589
+ }
11590
+ return { kind: "none" };
11591
+ }
11592
+
11593
+ // src/core/verdict/egress-classify.ts
11594
+ import path37 from "node:path";
11135
11595
  var CURL_EFFECT_NEUTRAL_FLAGS = /* @__PURE__ */ new Set([
11136
11596
  "-f",
11137
11597
  "-L",
@@ -11166,7 +11626,7 @@ var GH_READ_COMMANDS = /* @__PURE__ */ new Set([
11166
11626
  "workflow view"
11167
11627
  ]);
11168
11628
  function decodeEgressEffects(params) {
11169
- const head = path35.basename(params.tokens[0] ?? "");
11629
+ const head = path37.basename(params.tokens[0] ?? "");
11170
11630
  if (head !== "curl" && head !== "wget" && head !== "gh") {
11171
11631
  return null;
11172
11632
  }
@@ -11174,7 +11634,7 @@ function decodeEgressEffects(params) {
11174
11634
  const provenance = { segment: params.segment };
11175
11635
  const requirements = [];
11176
11636
  for (const file of decoded.files) {
11177
- const resolved = path35.resolve(params.cwd, expandHome2(file));
11637
+ const resolved = path37.resolve(params.cwd, expandHome2(file));
11178
11638
  requirements.push(
11179
11639
  requirement("fs.read", "fs.read", { kind: "path", path: resolved }, params.segment, [
11180
11640
  "egress.explicit_file_read"
@@ -11196,7 +11656,7 @@ function decodeEgressEffects(params) {
11196
11656
  if (file === "-") {
11197
11657
  continue;
11198
11658
  }
11199
- const resolved = path35.resolve(params.cwd, expandHome2(file));
11659
+ const resolved = path37.resolve(params.cwd, expandHome2(file));
11200
11660
  if (resolved === "/dev/null") {
11201
11661
  continue;
11202
11662
  }
@@ -11674,13 +12134,13 @@ function decodeSingleCurlWgetGrammar(head, tokens) {
11674
12134
  if (head === "wget" && !explicitOutput) {
11675
12135
  outputFiles.push(
11676
12136
  ...endpointOutputNames.map(
11677
- (name) => outputDirectory ? path35.join(outputDirectory, name) : name
12137
+ (name) => outputDirectory ? path37.join(outputDirectory, name) : name
11678
12138
  )
11679
12139
  );
11680
12140
  } else if (head === "curl" && remoteNameOutput) {
11681
12141
  outputFiles.push(
11682
12142
  ...endpointOutputNames.map(
11683
- (name) => outputDirectory ? path35.join(outputDirectory, name) : name
12143
+ (name) => outputDirectory ? path37.join(outputDirectory, name) : name
11684
12144
  )
11685
12145
  );
11686
12146
  }
@@ -11694,7 +12154,7 @@ function decodeSingleCurlWgetGrammar(head, tokens) {
11694
12154
  outputFiles: [
11695
12155
  ...new Set(
11696
12156
  outputFiles.map(
11697
- (file) => outputDirectory && directoryEligibleOutputs.has(file) && !path35.isAbsolute(file) ? path35.join(outputDirectory, file) : file
12157
+ (file) => outputDirectory && directoryEligibleOutputs.has(file) && !path37.isAbsolute(file) ? path37.join(outputDirectory, file) : file
11698
12158
  )
11699
12159
  )
11700
12160
  ],
@@ -11709,7 +12169,7 @@ function remoteOutputName(spec) {
11709
12169
  } catch {
11710
12170
  pathname = spec.split(/[?#]/, 1)[0] ?? "";
11711
12171
  }
11712
- const name = path35.posix.basename(pathname);
12172
+ const name = path37.posix.basename(pathname);
11713
12173
  return name && name !== "/" ? name : "index.html";
11714
12174
  }
11715
12175
  function decodeGhGrammar(tokens) {
@@ -11874,7 +12334,7 @@ function expandHome2(value) {
11874
12334
  return process.env.HOME ?? value;
11875
12335
  }
11876
12336
  if (value.startsWith("~/")) {
11877
- return path35.join(process.env.HOME ?? "~", value.slice(2));
12337
+ return path37.join(process.env.HOME ?? "~", value.slice(2));
11878
12338
  }
11879
12339
  return value;
11880
12340
  }
@@ -11893,7 +12353,7 @@ function requirement(tag, action, resource, segment, signals) {
11893
12353
  }
11894
12354
 
11895
12355
  // src/core/verdict/git-classifier.ts
11896
- import path36 from "node:path";
12356
+ import path38 from "node:path";
11897
12357
  init_shell_tokenizer();
11898
12358
  var GIT_BRANCH_MUTATION_FLAGS = /* @__PURE__ */ new Set([
11899
12359
  "--copy",
@@ -11989,7 +12449,7 @@ var FILE_OPERAND_SUBCOMMANDS = /* @__PURE__ */ new Set([
11989
12449
  var COMPOUND_SUBCOMMAND_HEADS = /* @__PURE__ */ new Set(["worktree", "stash", "tag"]);
11990
12450
  var REF_ONLY_WITHOUT_TERMINATOR = /* @__PURE__ */ new Set(["checkout", "show", "log"]);
11991
12451
  function isGitExecutable(token) {
11992
- return path36.basename(token) === "git";
12452
+ return path38.basename(token) === "git";
11993
12453
  }
11994
12454
  function takesValue(flag) {
11995
12455
  return flag === "-C" || flag === "-c" || flag === "--git-dir" || flag === "--work-tree" || flag === "--exec-path" || flag === "--paginate" || flag === "--config-env" || flag.startsWith("-C") || flag.startsWith("-c") || flag.startsWith("--git-dir=") || flag.startsWith("--work-tree=");
@@ -12017,7 +12477,7 @@ function peelGlobalOptions(tokens, baseCwd) {
12017
12477
  if (token === "-C" || token === "--work-tree" || token === "--git-dir" || token === "-c") {
12018
12478
  const value = tokens[index + 1];
12019
12479
  if (token === "-C" && value) {
12020
- effectiveCwd = path36.resolve(baseCwd, value);
12480
+ effectiveCwd = path38.resolve(baseCwd, value);
12021
12481
  } else if (token === "--work-tree" && value) {
12022
12482
  workTree = value;
12023
12483
  } else if (token === "--git-dir" && value) {
@@ -12027,7 +12487,7 @@ function peelGlobalOptions(tokens, baseCwd) {
12027
12487
  continue;
12028
12488
  }
12029
12489
  if (token.startsWith("-C") && token.length > 2) {
12030
- effectiveCwd = path36.resolve(baseCwd, token.slice(2));
12490
+ effectiveCwd = path38.resolve(baseCwd, token.slice(2));
12031
12491
  index += 1;
12032
12492
  continue;
12033
12493
  }
@@ -12170,7 +12630,7 @@ function looksLikeDiffPathOperand(token) {
12170
12630
  if (!looksLikeFileOperand(token)) {
12171
12631
  return false;
12172
12632
  }
12173
- if (token.startsWith(".") || path36.isAbsolute(token)) {
12633
+ if (token.startsWith(".") || path38.isAbsolute(token)) {
12174
12634
  return true;
12175
12635
  }
12176
12636
  return token.includes(".");
@@ -12178,12 +12638,12 @@ function looksLikeDiffPathOperand(token) {
12178
12638
  function resolveGitWorkTree(baseCwd, effectiveCwd, workTree, gitDir) {
12179
12639
  const resolveBase = effectiveCwd ?? baseCwd;
12180
12640
  if (workTree) {
12181
- return path36.resolve(resolveBase, workTree);
12641
+ return path38.resolve(resolveBase, workTree);
12182
12642
  }
12183
12643
  if (gitDir) {
12184
- const resolvedGitDir = path36.resolve(resolveBase, gitDir);
12185
- if (path36.basename(resolvedGitDir) === ".git") {
12186
- return path36.dirname(resolvedGitDir);
12644
+ const resolvedGitDir = path38.resolve(resolveBase, gitDir);
12645
+ if (path38.basename(resolvedGitDir) === ".git") {
12646
+ return path38.dirname(resolvedGitDir);
12187
12647
  }
12188
12648
  }
12189
12649
  return void 0;
@@ -12264,7 +12724,7 @@ function classifyGitCommand(tokens, baseCwd) {
12264
12724
  const { subcommand, args, effectiveCwd, gitDir, workTree } = normalized;
12265
12725
  const normalizedKey = `git ${subcommand}`;
12266
12726
  const gitWorkTree = resolveGitWorkTree(baseCwd, effectiveCwd, workTree, gitDir);
12267
- const effectiveGitDir = gitDir ? path36.resolve(effectiveCwd ?? baseCwd, gitDir) : void 0;
12727
+ const effectiveGitDir = gitDir ? path38.resolve(effectiveCwd ?? baseCwd, gitDir) : void 0;
12268
12728
  const scopeTargets = [effectiveCwd, gitWorkTree, effectiveGitDir].filter(
12269
12729
  (target, index, targets) => Boolean(target) && targets.indexOf(target) === index
12270
12730
  );
@@ -12416,9 +12876,9 @@ function decodeGitEffects(params) {
12416
12876
  ...subcommand === "push" ? ["tier0_external"] : []
12417
12877
  ];
12418
12878
  const effectiveCwd = normalized.effectiveCwd ?? params.cwd;
12419
- const workTreeRoot = normalized.workTree ? path36.resolve(normalized.effectiveCwd ?? params.cwd, normalized.workTree) : normalized.effectiveCwd ?? params.repoRoot;
12420
- const gitRefRoot = normalized.gitDir ? path36.resolve(effectiveCwd, normalized.gitDir) : workTreeRoot;
12421
- const gitControlRoot = normalized.gitDir ? gitRefRoot : path36.join(gitRefRoot, ".git");
12879
+ const workTreeRoot = normalized.workTree ? path38.resolve(normalized.effectiveCwd ?? params.cwd, normalized.workTree) : normalized.effectiveCwd ?? params.repoRoot;
12880
+ const gitRefRoot = normalized.gitDir ? path38.resolve(effectiveCwd, normalized.gitDir) : workTreeRoot;
12881
+ const gitControlRoot = normalized.gitDir ? gitRefRoot : path38.join(gitRefRoot, ".git");
12422
12882
  const requirements = [];
12423
12883
  if (subcommand === "fetch" || subcommand === "pull") {
12424
12884
  const positionals = gitRemotePositionals(args);
@@ -12577,7 +13037,7 @@ function decodeGitEffects(params) {
12577
13037
  gitRequirement(
12578
13038
  "control_plane.write",
12579
13039
  "control_plane.write",
12580
- { kind: "path", path: path36.join(gitControlRoot, "logs") },
13040
+ { kind: "path", path: path38.join(gitControlRoot, "logs") },
12581
13041
  params.segment,
12582
13042
  [...signals, "git_history_destructive", "git.reflog.mutate"]
12583
13043
  )
@@ -12635,7 +13095,7 @@ function decodeGitEffects(params) {
12635
13095
  gitRequirement(
12636
13096
  "fs.read",
12637
13097
  "fs.read",
12638
- { kind: "path", path: path36.resolve(workTreeRoot, operand) },
13098
+ { kind: "path", path: path38.resolve(workTreeRoot, operand) },
12639
13099
  params.segment,
12640
13100
  [...signals, "git.path.read"]
12641
13101
  )
@@ -12670,7 +13130,7 @@ function decodeGitEffects(params) {
12670
13130
  gitRequirement(
12671
13131
  "fs.write",
12672
13132
  "fs.write",
12673
- { kind: "path", path: path36.resolve(workTreeRoot, operand) },
13133
+ { kind: "path", path: path38.resolve(workTreeRoot, operand) },
12674
13134
  params.segment,
12675
13135
  [...signals, "git.path.write"]
12676
13136
  )
@@ -12855,7 +13315,7 @@ function gitRequirement(tag, action, resource, segment, signals) {
12855
13315
 
12856
13316
  // src/core/verdict/launcher-resolve.ts
12857
13317
  import { existsSync as existsSync11, readFileSync as readFileSync5 } from "node:fs";
12858
- import path37 from "node:path";
13318
+ import path39 from "node:path";
12859
13319
 
12860
13320
  // src/core/verdict/makefile-expand.ts
12861
13321
  var MAX_EXPAND_DEPTH = 16;
@@ -12874,22 +13334,6 @@ function parseMakefileVariables(content) {
12874
13334
  }
12875
13335
  return variables;
12876
13336
  }
12877
- function parsePhonyTargets(content) {
12878
- const phony = /* @__PURE__ */ new Set();
12879
- for (const line of content.split("\n")) {
12880
- const trimmed = line.trim();
12881
- const match = /^\.PHONY:\s*(.+)$/.exec(trimmed);
12882
- if (!match) {
12883
- continue;
12884
- }
12885
- for (const token of (match[1] ?? "").split(/\s+/)) {
12886
- if (token) {
12887
- phony.add(token);
12888
- }
12889
- }
12890
- }
12891
- return phony;
12892
- }
12893
13337
  function normalizeMakeRecipeLine(line) {
12894
13338
  let normalized = line.trim();
12895
13339
  while (normalized.startsWith("@") || normalized.startsWith("-") || normalized.startsWith("+")) {
@@ -13044,7 +13488,7 @@ var PNPM_BUILTIN_COMMANDS = /* @__PURE__ */ new Set([
13044
13488
  "why"
13045
13489
  ]);
13046
13490
  function readPackageJson(dir) {
13047
- const packagePath = path37.join(dir, "package.json");
13491
+ const packagePath = path39.join(dir, "package.json");
13048
13492
  if (!existsSync11(packagePath)) {
13049
13493
  return null;
13050
13494
  }
@@ -13055,17 +13499,17 @@ function readPackageJson(dir) {
13055
13499
  }
13056
13500
  }
13057
13501
  function findPackageJson(startDir, stopDir) {
13058
- let current = path37.resolve(startDir);
13059
- const stop = path37.resolve(stopDir);
13502
+ let current = path39.resolve(startDir);
13503
+ const stop = path39.resolve(stopDir);
13060
13504
  while (true) {
13061
- const packagePath = path37.join(current, "package.json");
13505
+ const packagePath = path39.join(current, "package.json");
13062
13506
  if (existsSync11(packagePath)) {
13063
13507
  return packagePath;
13064
13508
  }
13065
- if (current === stop || current === path37.dirname(current)) {
13509
+ if (current === stop || current === path39.dirname(current)) {
13066
13510
  return existsSync11(packagePath) ? packagePath : null;
13067
13511
  }
13068
- const parent = path37.dirname(current);
13512
+ const parent = path39.dirname(current);
13069
13513
  if (!parent.startsWith(stop) && parent !== current) {
13070
13514
  }
13071
13515
  if (parent === current) {
@@ -13122,7 +13566,7 @@ function resolveNpmRecipe(cwd, repoRoot, scriptName, extraArgs) {
13122
13566
  }
13123
13567
  return { recipes: [], opaque: true, reason: "package_json_missing" };
13124
13568
  }
13125
- const pkg = readPackageJson(path37.dirname(packagePath));
13569
+ const pkg = readPackageJson(path39.dirname(packagePath));
13126
13570
  const scripts = pkg?.scripts;
13127
13571
  if (!scripts || typeof scripts !== "object") {
13128
13572
  return { recipes: [], opaque: true, reason: "package_scripts_missing" };
@@ -13213,27 +13657,26 @@ function parseMakefileRecipeContent(content) {
13213
13657
  function resolveMakeRecipe(cwd, repoRoot, target, cliVars = {}) {
13214
13658
  const candidates = ["Makefile", "makefile", "GNUmakefile"];
13215
13659
  let makefilePath = null;
13216
- let searchDir = path37.resolve(cwd);
13217
- const stop = path37.resolve(repoRoot);
13660
+ let searchDir = path39.resolve(cwd);
13661
+ const stop = path39.resolve(repoRoot);
13218
13662
  while (true) {
13219
13663
  for (const name of candidates) {
13220
- const candidate = path37.join(searchDir, name);
13664
+ const candidate = path39.join(searchDir, name);
13221
13665
  if (existsSync11(candidate)) {
13222
13666
  makefilePath = candidate;
13223
13667
  break;
13224
13668
  }
13225
13669
  }
13226
- if (makefilePath || searchDir === stop || searchDir === path37.dirname(searchDir)) {
13670
+ if (makefilePath || searchDir === stop || searchDir === path39.dirname(searchDir)) {
13227
13671
  break;
13228
13672
  }
13229
- searchDir = path37.dirname(searchDir);
13673
+ searchDir = path39.dirname(searchDir);
13230
13674
  }
13231
13675
  if (!makefilePath) {
13232
13676
  return { recipes: [], opaque: true, reason: "unknown_local_effect" };
13233
13677
  }
13234
13678
  const makefileContent = readFileSync5(makefilePath, "utf8");
13235
13679
  const makefileVars = parseMakefileVariables(makefileContent);
13236
- const phonyTargets = parsePhonyTargets(makefileContent);
13237
13680
  const targets = parseMakefileRecipeContent(makefileContent);
13238
13681
  if (!targets.has(target)) {
13239
13682
  return { recipes: [], opaque: true, reason: "make_target_undefined" };
@@ -13241,36 +13684,34 @@ function resolveMakeRecipe(cwd, repoRoot, target, cliVars = {}) {
13241
13684
  const recipeLines = [];
13242
13685
  const visiting = /* @__PURE__ */ new Set();
13243
13686
  const visited = /* @__PURE__ */ new Set();
13244
- let opaquePrerequisites = false;
13687
+ let hasDynamicPrerequisite = false;
13688
+ let hasUndefinedPrerequisite = false;
13689
+ let hasDependencyCycle = false;
13245
13690
  const collect = (name) => {
13246
13691
  if (visited.has(name)) {
13247
- return true;
13692
+ return;
13248
13693
  }
13249
13694
  if (visiting.has(name)) {
13250
- return false;
13695
+ hasDependencyCycle = true;
13696
+ return;
13251
13697
  }
13252
13698
  const entry = targets.get(name);
13253
13699
  if (!entry) {
13254
- return true;
13700
+ if (!existsSync11(path39.resolve(path39.dirname(makefilePath), name))) {
13701
+ hasUndefinedPrerequisite = true;
13702
+ }
13703
+ return;
13255
13704
  }
13256
13705
  visiting.add(name);
13257
- opaquePrerequisites ||= entry.opaquePrerequisites;
13706
+ hasDynamicPrerequisite ||= entry.opaquePrerequisites;
13258
13707
  for (const prerequisite of entry.prerequisites) {
13259
- if (targets.has(prerequisite) && !collect(prerequisite)) {
13260
- return false;
13261
- }
13262
- }
13263
- const skipPhonyPrerequisiteRecipes = name !== target && (phonyTargets.has(name) || name.startsWith("_")) && (targets.get(target)?.recipes.length ?? 0) > 0;
13264
- if (!skipPhonyPrerequisiteRecipes) {
13265
- recipeLines.push(...entry.recipes);
13708
+ collect(prerequisite);
13266
13709
  }
13710
+ recipeLines.push(...entry.recipes);
13267
13711
  visiting.delete(name);
13268
13712
  visited.add(name);
13269
- return true;
13270
13713
  };
13271
- if (!collect(target)) {
13272
- return { recipes: recipeLines, opaque: true, reason: "make_dependency_cycle" };
13273
- }
13714
+ collect(target);
13274
13715
  const expandedRecipes = [];
13275
13716
  for (const line of recipeLines) {
13276
13717
  const normalized = normalizeMakeRecipeLine(line);
@@ -13285,18 +13726,24 @@ function resolveMakeRecipe(cwd, repoRoot, target, cliVars = {}) {
13285
13726
  return { recipes: expandedRecipes, opaque: true, reason: "make_recipe_dynamic" };
13286
13727
  }
13287
13728
  }
13288
- if (opaquePrerequisites) {
13729
+ if (hasDependencyCycle) {
13730
+ return { recipes: expandedRecipes, opaque: true, reason: "make_dependency_cycle" };
13731
+ }
13732
+ if (hasDynamicPrerequisite) {
13289
13733
  return { recipes: expandedRecipes, opaque: true, reason: "make_prerequisite_dynamic" };
13290
13734
  }
13735
+ if (hasUndefinedPrerequisite) {
13736
+ return { recipes: expandedRecipes, opaque: true, reason: "make_prerequisite_undefined" };
13737
+ }
13291
13738
  return { recipes: expandedRecipes, opaque: false, reason: "make_recipe_resolved" };
13292
13739
  }
13293
13740
  function resolveLauncherRecipe(params) {
13294
- if (params.depth >= MAX_RESOLVE_DEPTH) {
13295
- return { recipes: [], opaque: true, reason: "launcher_depth_exceeded" };
13296
- }
13297
13741
  const tokens = params.tokens;
13298
13742
  const scriptName = npmScriptName(tokens);
13299
13743
  if (scriptName) {
13744
+ if (params.depth >= MAX_RESOLVE_DEPTH) {
13745
+ return { recipes: [], opaque: true, reason: "launcher_depth_exceeded" };
13746
+ }
13300
13747
  const resolution = resolveNpmRecipe(
13301
13748
  params.cwd,
13302
13749
  params.repoRoot,
@@ -13313,6 +13760,9 @@ function resolveLauncherRecipe(params) {
13313
13760
  return resolution;
13314
13761
  }
13315
13762
  if (tokens[0] === "make") {
13763
+ if (params.depth >= MAX_RESOLVE_DEPTH) {
13764
+ return { recipes: [], opaque: true, reason: "launcher_depth_exceeded" };
13765
+ }
13316
13766
  if (tokens.includes("-n") || tokens.includes("--dry-run")) {
13317
13767
  return null;
13318
13768
  }
@@ -13346,7 +13796,7 @@ function resolveLauncherRecipe(params) {
13346
13796
  }
13347
13797
 
13348
13798
  // src/core/verdict/parser.ts
13349
- import path38 from "node:path";
13799
+ import path40 from "node:path";
13350
13800
 
13351
13801
  // src/core/shell-substitution.ts
13352
13802
  function findStructuralCommandSubstitutions(command) {
@@ -13571,9 +14021,8 @@ function hasUnbalancedDollarParen(command) {
13571
14021
  // src/core/verdict/parser.ts
13572
14022
  var ENV_PREFIX_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*=(?:'[^']*'|"[^"]*"|\S+)$/;
13573
14023
  var MAX_WRAPPER_PEEL_DEPTH = 32;
13574
- var SHELL_INTERPRETERS = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "dash", "fish"]);
14024
+ var SHELL_INTERPRETERS2 = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "dash", "fish"]);
13575
14025
  var CODE_INTERPRETERS = /* @__PURE__ */ new Set(["python", "python3", "node", "ruby", "perl", "osascript"]);
13576
- var SCRIPT_FLAGS = /* @__PURE__ */ new Set(["-c", "-lc", "-e", "--eval"]);
13577
14026
  var INTERPRETER_SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([
13578
14027
  ".js",
13579
14028
  ".mjs",
@@ -13585,7 +14034,7 @@ var INTERPRETER_SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([
13585
14034
  ".sh"
13586
14035
  ]);
13587
14036
  function normalizeHead(token) {
13588
- const base = path38.basename(token);
14037
+ const base = path40.basename(token);
13589
14038
  if (base && base !== "." && base !== "..") {
13590
14039
  return base;
13591
14040
  }
@@ -13597,10 +14046,6 @@ function peelTransparentWrappers(tokens) {
13597
14046
  let encounteredXargs = false;
13598
14047
  let peelDepth = 0;
13599
14048
  while (current.length > 0) {
13600
- if (peelDepth >= MAX_WRAPPER_PEEL_DEPTH) {
13601
- return { tokens: current, xargsStdinOpaque: false, encounteredXargs, opaque: true };
13602
- }
13603
- peelDepth += 1;
13604
14049
  while (current.length > 0 && ENV_PREFIX_PATTERN.test(current[0] ?? "")) {
13605
14050
  current.shift();
13606
14051
  }
@@ -13610,6 +14055,10 @@ function peelTransparentWrappers(tokens) {
13610
14055
  const head = normalizeHead(current[0] ?? "");
13611
14056
  if (head === "xargs") {
13612
14057
  encounteredXargs = true;
14058
+ if (peelDepth >= MAX_WRAPPER_PEEL_DEPTH) {
14059
+ return { tokens: current, xargsStdinOpaque: false, encounteredXargs, opaque: true };
14060
+ }
14061
+ peelDepth += 1;
13613
14062
  const wrapper2 = peelXargsWrapper(current);
13614
14063
  if (wrapper2.kind === "opaque") {
13615
14064
  xargsStdinOpaque = current.length === 1;
@@ -13627,6 +14076,10 @@ function peelTransparentWrappers(tokens) {
13627
14076
  if (!wrapper) {
13628
14077
  break;
13629
14078
  }
14079
+ if (peelDepth >= MAX_WRAPPER_PEEL_DEPTH) {
14080
+ return { tokens: current, xargsStdinOpaque: false, encounteredXargs, opaque: true };
14081
+ }
14082
+ peelDepth += 1;
13630
14083
  if (wrapper.kind === "opaque") {
13631
14084
  return { tokens: current, xargsStdinOpaque: false, encounteredXargs, opaque: true };
13632
14085
  }
@@ -13854,50 +14307,16 @@ function extractRecursiveScript(tokens) {
13854
14307
  const body = filtered.slice(1).join(" ").trim();
13855
14308
  return body || null;
13856
14309
  }
13857
- if (SHELL_INTERPRETERS.has(head) || CODE_INTERPRETERS.has(head)) {
13858
- const flagIndex = filtered.findIndex((token) => SCRIPT_FLAGS.has(token));
13859
- if (flagIndex !== -1) {
13860
- const body = filtered[flagIndex + 1] ?? "";
13861
- return body || null;
13862
- }
13863
- }
13864
- return null;
13865
- }
13866
- function extractDockerComposeRunScript(tokens) {
13867
- const head = normalizeHead(tokens[0] ?? "");
13868
- const usesCompose = head === "docker-compose" || head === "docker" && (tokens[1] ?? "") === "compose";
13869
- if (!usesCompose) {
13870
- return null;
13871
- }
13872
- if (!tokens.includes("run")) {
13873
- return null;
13874
- }
13875
- const runIndex = tokens.indexOf("run");
13876
- const tail = tokens.slice(runIndex + 1);
13877
- for (let index = 0; index < tail.length; index += 1) {
13878
- const shellHead = normalizeHead(tail[index] ?? "");
13879
- if (!SHELL_INTERPRETERS.has(shellHead)) {
13880
- continue;
13881
- }
13882
- const flag = tail[index + 1] ?? "";
13883
- if (flag !== "-lc" && flag !== "-c") {
13884
- continue;
13885
- }
13886
- const body = tail[index + 2] ?? "";
13887
- return body || null;
13888
- }
13889
- return null;
14310
+ const invocation = decodeRecursiveInvocation(
14311
+ shellTokensFromValues(filtered, { detectExpansion: false })
14312
+ );
14313
+ return invocation.kind === "static" ? invocation.script || null : null;
13890
14314
  }
13891
- function isDynamicRecursiveEvaluation(tokens) {
13892
- const { tokens: filtered, opaque } = peelTransparentWrappers(tokens);
13893
- if (opaque) {
13894
- return false;
13895
- }
13896
- const head = normalizeHead(filtered[0] ?? "");
13897
- if (head === "eval") {
13898
- return true;
13899
- }
13900
- return (SHELL_INTERPRETERS.has(head) || CODE_INTERPRETERS.has(head)) && filtered.some((token) => SCRIPT_FLAGS.has(token));
14315
+ function decodeRecursiveInvocationTokens(tokens) {
14316
+ const values = tokens.map((token) => token.value);
14317
+ const { tokens: filtered, opaque } = peelTransparentWrappers(values);
14318
+ if (opaque) return { kind: "none" };
14319
+ return decodeRecursiveInvocation(tokens.slice(values.length - filtered.length));
13901
14320
  }
13902
14321
  function isCommandInspection(tokens) {
13903
14322
  return normalizeHead(tokens[0] ?? "") === "command" && peelCommandWrapper(tokens).kind === "preserve";
@@ -13911,11 +14330,10 @@ function isBareInterpreter(tokens) {
13911
14330
  return false;
13912
14331
  }
13913
14332
  const head = normalizeHead(peeled[0] ?? "");
13914
- if (!SHELL_INTERPRETERS.has(head) && !CODE_INTERPRETERS.has(head)) {
14333
+ if (!SHELL_INTERPRETERS2.has(head) && !CODE_INTERPRETERS.has(head)) {
13915
14334
  return false;
13916
14335
  }
13917
- const hasScriptFlag = peeled.some((token) => SCRIPT_FLAGS.has(token));
13918
- if (hasScriptFlag) {
14336
+ if (decodeRecursiveInvocation(shellTokensFromValues(peeled)).kind !== "none") {
13919
14337
  return false;
13920
14338
  }
13921
14339
  const args = peeled.slice(1);
@@ -13926,7 +14344,7 @@ function isBareInterpreter(tokens) {
13926
14344
  return false;
13927
14345
  }
13928
14346
  const scriptArg = args.find((token) => !token.startsWith("-"));
13929
- if (scriptArg && INTERPRETER_SCRIPT_EXTENSIONS.has(path38.extname(scriptArg))) {
14347
+ if (scriptArg && INTERPRETER_SCRIPT_EXTENSIONS.has(path40.extname(scriptArg))) {
13930
14348
  return false;
13931
14349
  }
13932
14350
  if (scriptArg) {
@@ -14218,11 +14636,11 @@ function lowerTopLevelSegments(command, context) {
14218
14636
  }
14219
14637
  function startsLocalPostgresService(command) {
14220
14638
  const tokens = tokenizeShell(command);
14221
- return path39.basename(tokens[0] ?? "") === "docker" && tokens[1] === "compose" && ["up", "start", "restart"].includes(tokens[2] ?? "") && tokens.includes("postgres");
14639
+ return path41.basename(tokens[0] ?? "") === "docker" && tokens[1] === "compose" && ["up", "start", "restart"].includes(tokens[2] ?? "") && tokens.includes("postgres");
14222
14640
  }
14223
14641
  function resolveCdTransition(command, currentCwd) {
14224
14642
  const tokens = tokenizeShell(command);
14225
- if (path39.basename(tokens[0] ?? "") !== "cd") {
14643
+ if (path41.basename(tokens[0] ?? "") !== "cd") {
14226
14644
  return null;
14227
14645
  }
14228
14646
  const target = tokens[1] ?? "~";
@@ -14243,7 +14661,8 @@ function joinNestedOpacity(outer, nested) {
14243
14661
  }
14244
14662
  function lowerSegment(command, context) {
14245
14663
  const commandRedacted = redactCommand(command);
14246
- const rawTokens = tokenizeShell(command);
14664
+ const lexed = lexShell(command);
14665
+ const rawTokens = lexed.tokens.map((token) => token.value);
14247
14666
  const environment = extractEnvironment(rawTokens, context.env);
14248
14667
  const env = environment.env;
14249
14668
  const parsed = parseSegment(command);
@@ -14251,10 +14670,23 @@ function lowerSegment(command, context) {
14251
14670
  const tokens = stripRedirects(
14252
14671
  parsedTokens.length === 0 && rawTokens.length > 0 && rawTokens.every((token) => ENV_PREFIX_PATTERN2.test(token)) ? rawTokens : parsedTokens
14253
14672
  ).map((token) => expandKnownVariables(token, env));
14254
- const head = path39.basename(tokens[0] ?? parsed.head);
14673
+ const decoderTokens = alignStructuredTokens(
14674
+ stripStructuredRedirects(lexed.tokens),
14675
+ stripRedirects(parsedTokens)
14676
+ );
14677
+ const head = path41.basename(tokens[0] ?? parsed.head);
14255
14678
  let opacity = segmentOpacity(command);
14256
14679
  const signals = /* @__PURE__ */ new Set();
14257
14680
  const requirements = [];
14681
+ if (!lexed.complete) {
14682
+ requirements.push(
14683
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
14684
+ "shell.grammar_incomplete"
14685
+ ])
14686
+ );
14687
+ signals.add("shell.grammar_incomplete");
14688
+ opacity = joinEffectOpacity(opacity, "unparseable");
14689
+ }
14258
14690
  addRedirectEffects(requirements, rawTokens, env, context, commandRedacted);
14259
14691
  addSubstitutionEffects(requirements, command, context, commandRedacted, signals);
14260
14692
  if (environment.malformed) {
@@ -14274,7 +14706,7 @@ function lowerSegment(command, context) {
14274
14706
  signals.add("shell.xargs_stdin_dynamic");
14275
14707
  opacity = joinEffectOpacity(opacity, "opaque");
14276
14708
  }
14277
- if (context.depth >= MAX_LOWER_DEPTH) {
14709
+ if (context.depth > MAX_LOWER_DEPTH) {
14278
14710
  requirements.push(
14279
14711
  requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
14280
14712
  "shell.lower_depth_exceeded"
@@ -14321,62 +14753,96 @@ function lowerSegment(command, context) {
14321
14753
  }
14322
14754
  return shellSegment(commandRedacted, head, requirements, opacity, signals);
14323
14755
  }
14324
- const recursiveScript = extractRecursiveScript(tokens);
14325
- if (recursiveScript && opacity !== "opaque" && opacity !== "unparseable") {
14326
- const dynamicEvaluation = isDynamicRecursiveEvaluation(tokens);
14756
+ const recursive = decodeRecursiveInvocationTokens(decoderTokens);
14757
+ if (recursive.kind === "static" && opacity !== "opaque" && opacity !== "unparseable") {
14327
14758
  requirements.push(
14328
- processRequirement(head || "sh", "spawn", commandRedacted, [
14759
+ processRequirement(recursive.interpreter, "spawn", commandRedacted, [
14329
14760
  "shell.recursive_wrapper",
14330
- ...dynamicEvaluation ? ["dynamic_shell_evaluation"] : []
14761
+ "dynamic_shell_evaluation"
14331
14762
  ])
14332
14763
  );
14333
- const nested = lowerTopLevelSegments(recursiveScript, {
14334
- ...context,
14335
- command: recursiveScript,
14336
- env,
14337
- depth: context.depth + 1
14338
- });
14339
- for (const nestedSegment of nested) {
14340
- requirements.push(
14341
- ...nestedSegment.requirements.map(
14342
- (entry) => withInnerProvenance(entry, recursiveScript, head, commandRedacted)
14343
- )
14344
- );
14345
- for (const signal of nestedSegment.signals) {
14346
- signals.add(signal);
14764
+ if (recursive.script !== "") {
14765
+ const nested = lowerTopLevelSegments(recursive.script, {
14766
+ ...context,
14767
+ command: recursive.script,
14768
+ env,
14769
+ depth: context.depth + 1
14770
+ });
14771
+ for (const nestedSegment of nested) {
14772
+ requirements.push(
14773
+ ...nestedSegment.requirements.map(
14774
+ (entry) => withInnerProvenance(entry, recursive.script, head, commandRedacted)
14775
+ )
14776
+ );
14777
+ for (const signal of nestedSegment.signals) signals.add(signal);
14347
14778
  }
14348
14779
  }
14349
14780
  signals.add("shell.recursive_wrapper");
14350
- if (dynamicEvaluation) {
14351
- signals.add("dynamic_shell_evaluation");
14352
- }
14781
+ signals.add("dynamic_shell_evaluation");
14353
14782
  return shellSegment(commandRedacted, head, requirements, "recursive", signals);
14354
14783
  }
14355
- const dockerComposeScript = extractDockerComposeRunScript(tokens);
14356
- if (dockerComposeScript && opacity !== "opaque" && opacity !== "unparseable") {
14784
+ if (recursive.kind === "dynamic" || recursive.kind === "indeterminate") {
14785
+ const recursiveSignals = [
14786
+ recursive.signal,
14787
+ ...recursive.kind === "dynamic" ? ["dynamic_shell_evaluation"] : []
14788
+ ];
14789
+ requirements.push(
14790
+ processRequirement(recursive.interpreter, "spawn", commandRedacted, recursiveSignals),
14791
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
14792
+ ...recursiveSignals
14793
+ ])
14794
+ );
14795
+ for (const signal of recursiveSignals) signals.add(signal);
14796
+ return shellSegment(
14797
+ commandRedacted,
14798
+ head,
14799
+ requirements,
14800
+ joinEffectOpacity(opacity, "opaque"),
14801
+ signals
14802
+ );
14803
+ }
14804
+ const compose = decodeDockerComposeRun(decoderTokens);
14805
+ if (compose.kind === "recursive" && opacity !== "opaque" && opacity !== "unparseable") {
14357
14806
  requirements.push(
14358
14807
  processRequirement(head, "spawn", commandRedacted, ["process.docker_compose_run"])
14359
14808
  );
14360
- const nested = lowerTopLevelSegments(dockerComposeScript, {
14361
- ...context,
14362
- command: dockerComposeScript,
14363
- env,
14364
- depth: context.depth + 1
14365
- });
14366
- for (const nestedSegment of nested) {
14367
- requirements.push(
14368
- ...nestedSegment.requirements.map(
14369
- (entry) => withInnerProvenance(entry, dockerComposeScript, head, commandRedacted)
14370
- )
14371
- );
14372
- for (const signal of nestedSegment.signals) {
14373
- signals.add(signal);
14809
+ if (compose.script !== "") {
14810
+ const nested = lowerTopLevelSegments(compose.script, {
14811
+ ...context,
14812
+ command: compose.script,
14813
+ env,
14814
+ depth: context.depth + 1
14815
+ });
14816
+ for (const nestedSegment of nested) {
14817
+ requirements.push(
14818
+ ...nestedSegment.requirements.map(
14819
+ (entry) => withInnerProvenance(entry, compose.script, head, commandRedacted)
14820
+ )
14821
+ );
14822
+ for (const signal of nestedSegment.signals) signals.add(signal);
14823
+ opacity = joinNestedOpacity(opacity, nestedSegment);
14374
14824
  }
14375
- opacity = joinNestedOpacity(opacity, nestedSegment);
14376
14825
  }
14377
14826
  signals.add("process.docker_compose_run");
14378
14827
  return shellSegment(commandRedacted, head, requirements, "recursive", signals);
14379
14828
  }
14829
+ if (compose.kind === "dynamic" || compose.kind === "indeterminate") {
14830
+ requirements.push(
14831
+ processRequirement(head, "spawn", commandRedacted, ["process.docker_compose_run"]),
14832
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
14833
+ compose.signal
14834
+ ])
14835
+ );
14836
+ signals.add("process.docker_compose_run");
14837
+ signals.add(compose.signal);
14838
+ return shellSegment(
14839
+ commandRedacted,
14840
+ head,
14841
+ requirements,
14842
+ joinEffectOpacity(opacity, "opaque"),
14843
+ signals
14844
+ );
14845
+ }
14380
14846
  const launcher = resolveLauncherRecipe({
14381
14847
  tokens,
14382
14848
  cwd: context.cwd,
@@ -14588,7 +15054,7 @@ function isMetadataOnlyArgv(argv) {
14588
15054
  return argv.length > 0 && argv.every((token) => METADATA_ONLY_FLAGS.has(token));
14589
15055
  }
14590
15056
  function executableBaseName(head) {
14591
- return path39.basename(head);
15057
+ return path41.basename(head);
14592
15058
  }
14593
15059
  var RAILS_READ_ONLY_SUBCOMMANDS = /* @__PURE__ */ new Set(["routes", "middleware", "stats", "about", "version"]);
14594
15060
  function railsReadOnlySubcommand(args) {
@@ -14599,7 +15065,7 @@ function railsReadOnlySubcommand(args) {
14599
15065
  return RAILS_READ_ONLY_SUBCOMMANDS.has(subcommand);
14600
15066
  }
14601
15067
  function isRubyTestScript(scriptPath) {
14602
- const base = path39.basename(scriptPath);
15068
+ const base = path41.basename(scriptPath);
14603
15069
  return base.endsWith("_test.rb") || base.endsWith("_spec.rb");
14604
15070
  }
14605
15071
  function parseRubyTestInvocation(args) {
@@ -14784,7 +15250,7 @@ function decodeShellControlBuiltin(head, args) {
14784
15250
  }
14785
15251
  return null;
14786
15252
  }
14787
- function decodeDockerComposeRun(head, args, segment) {
15253
+ function decodeDockerComposeRun2(head, args, segment) {
14788
15254
  let composeArgs = null;
14789
15255
  let command = head;
14790
15256
  if (head === "docker-compose") {
@@ -14878,7 +15344,7 @@ function decodeProcessOrFilesystem(params) {
14878
15344
  requirement2(
14879
15345
  "fs.read",
14880
15346
  "fs.read",
14881
- { kind: "path", path: path39.resolve(cwd, syntax) },
15347
+ { kind: "path", path: path41.resolve(cwd, syntax) },
14882
15348
  segment,
14883
15349
  ["shell.syntax_source_read"]
14884
15350
  )
@@ -14902,7 +15368,7 @@ function decodeProcessOrFilesystem(params) {
14902
15368
  return decoded;
14903
15369
  }
14904
15370
  }
14905
- const dockerCompose = decodeDockerComposeRun(head, args, segment);
15371
+ const dockerCompose = decodeDockerComposeRun2(head, args, segment);
14906
15372
  if (dockerCompose) {
14907
15373
  return dockerCompose;
14908
15374
  }
@@ -14928,7 +15394,7 @@ function decodeProcessOrFilesystem(params) {
14928
15394
  return [processRequirement(head, "inspect", segment, ["process.inspect.base64_stdin"])];
14929
15395
  }
14930
15396
  if (head === "node") {
14931
- return decodeNode(args, cwd, segment);
15397
+ return decodeNode2(args, cwd, segment);
14932
15398
  }
14933
15399
  if (head === "vite" || head === "vite-node") {
14934
15400
  return [processRequirement(head, "spawn", segment, ["process.local_dev_spawn"])];
@@ -15018,7 +15484,7 @@ function decodeBelay(args, repoRoot, segment) {
15018
15484
  requirement2(
15019
15485
  "control_plane.write",
15020
15486
  "control_plane.write",
15021
- { kind: "path", path: path39.join(repoRoot, ".belay-control-plane") },
15487
+ { kind: "path", path: path41.join(repoRoot, ".belay-control-plane") },
15022
15488
  segment,
15023
15489
  ["belay.config_non_judge_mutation"]
15024
15490
  )
@@ -15180,11 +15646,11 @@ function decodeRm(args, cwd, repoRoot, segment) {
15180
15646
  function canonicalRmOperand(targetPath, finalOperandIsSymlink) {
15181
15647
  try {
15182
15648
  if (finalOperandIsSymlink) {
15183
- return path39.join(realpathSync4.native(path39.dirname(targetPath)), path39.basename(targetPath));
15649
+ return path41.join(realpathSync4.native(path41.dirname(targetPath)), path41.basename(targetPath));
15184
15650
  }
15185
15651
  return realpathSync4.native(targetPath);
15186
15652
  } catch {
15187
- return path39.resolve(targetPath);
15653
+ return path41.resolve(targetPath);
15188
15654
  }
15189
15655
  }
15190
15656
  function isSymbolicLink(targetPath) {
@@ -15195,8 +15661,8 @@ function isSymbolicLink(targetPath) {
15195
15661
  }
15196
15662
  }
15197
15663
  function pathContains(ancestor, candidate) {
15198
- const relative = path39.relative(path39.resolve(ancestor), path39.resolve(candidate));
15199
- return relative === "" || !relative.startsWith("..") && !path39.isAbsolute(relative);
15664
+ const relative = path41.relative(path41.resolve(ancestor), path41.resolve(candidate));
15665
+ return relative === "" || !relative.startsWith("..") && !path41.isAbsolute(relative);
15200
15666
  }
15201
15667
  function decodeGo(args, segment) {
15202
15668
  if (["test", "list", "vet"].includes(args[0] ?? "")) {
@@ -15342,7 +15808,7 @@ function decodeSed(args, cwd, segment) {
15342
15808
  }
15343
15809
  return lowered;
15344
15810
  }
15345
- function decodeNode(args, cwd, segment) {
15811
+ function decodeNode2(args, cwd, segment) {
15346
15812
  if (args.length > 0 && args.every((arg) => ["--help", "--version", "-h", "-v"].includes(arg))) {
15347
15813
  return [processRequirement("node", "inspect", segment, ["process.inspect.node_metadata"])];
15348
15814
  }
@@ -15608,15 +16074,36 @@ function stripRedirects(tokens) {
15608
16074
  stripped.push(token);
15609
16075
  continue;
15610
16076
  }
15611
- if (token.includes(">") || token.includes("<")) {
15612
- const inline = token.replace(/^\d*(?:>>?|<<?|<>|>\|)/, "");
15613
- if (!inline) {
15614
- index += 1;
15615
- }
16077
+ index += 1;
16078
+ }
16079
+ return stripped;
16080
+ }
16081
+ function stripStructuredRedirects(tokens) {
16082
+ const stripped = [];
16083
+ for (let index = 0; index < tokens.length; index += 1) {
16084
+ const token = tokens[index];
16085
+ if (!token) continue;
16086
+ if (isFdDuplication(token.value)) {
16087
+ continue;
16088
+ }
16089
+ if (!isRedirectOperator(token.value)) {
16090
+ stripped.push(token);
16091
+ continue;
15616
16092
  }
16093
+ index += 1;
15617
16094
  }
15618
16095
  return stripped;
15619
16096
  }
16097
+ function alignStructuredTokens(tokens, values) {
16098
+ if (values.length === 0) return [];
16099
+ for (let start = tokens.length - values.length; start >= 0; start -= 1) {
16100
+ const candidate = tokens.slice(start);
16101
+ if (candidate.length === values.length && candidate.every((token, index) => token.value === values[index])) {
16102
+ return candidate;
16103
+ }
16104
+ }
16105
+ return [];
16106
+ }
15620
16107
  function shellSegment(commandRedacted, segmentHead, requirements, opacity, signals) {
15621
16108
  const normalizedRequirements = requirements.flatMap((entry) => {
15622
16109
  const dynamicSignal = dynamicResourceSignal(entry.resource);
@@ -16016,9 +16503,9 @@ function resolvePathOperand(operand, cwd) {
16016
16503
  return process.env.HOME ?? operand;
16017
16504
  }
16018
16505
  if (operand.startsWith("~/")) {
16019
- return path39.join(process.env.HOME ?? "~", operand.slice(2));
16506
+ return path41.join(process.env.HOME ?? "~", operand.slice(2));
16020
16507
  }
16021
- return path39.resolve(cwd, operand);
16508
+ return path41.resolve(cwd, operand);
16022
16509
  }
16023
16510
  function isShellHead(head) {
16024
16511
  return head === "bash" || head === "sh" || head === "zsh" || head === "dash" || head === "fish";
@@ -16542,7 +17029,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
16542
17029
  };
16543
17030
  }
16544
17031
  const signals = [];
16545
- const resolvedPath = path40.isAbsolute(filePath) ? filePath : path40.resolve(cwd, filePath);
17032
+ const resolvedPath = path42.isAbsolute(filePath) ? filePath : path42.resolve(cwd, filePath);
16546
17033
  const hitsProtectedRoot = protectedRoots.some((root) => pathWithinRoot(root, resolvedPath));
16547
17034
  if (hitsProtectedRoot) {
16548
17035
  signals.push("control_plane_path");
@@ -17144,7 +17631,7 @@ function hashDecisionConfig(config) {
17144
17631
  init_fingerprint2();
17145
17632
 
17146
17633
  // src/version.ts
17147
- var PACKAGE_VERSION = "0.9.2";
17634
+ var PACKAGE_VERSION = "0.9.3";
17148
17635
 
17149
17636
  // src/runtime-provenance.ts
17150
17637
  function resolveRuntimeArtifactHash(artifactHash) {
@@ -17219,7 +17706,7 @@ init_path_utils();
17219
17706
  import { randomUUID as randomUUID5 } from "node:crypto";
17220
17707
  import { existsSync as existsSync15 } from "node:fs";
17221
17708
  import { mkdir as mkdir11, readdir as readdir3, readFile as readFile12, rename as rename3, rm as rm6 } from "node:fs/promises";
17222
- import path45 from "node:path";
17709
+ import path47 from "node:path";
17223
17710
 
17224
17711
  // src/core/recovery/artifact-store.ts
17225
17712
  init_fingerprint2();
@@ -17227,7 +17714,7 @@ init_path_utils();
17227
17714
  import { randomUUID as randomUUID4 } from "node:crypto";
17228
17715
  import { existsSync as existsSync13 } from "node:fs";
17229
17716
  import { lstat as lstat7, mkdir as mkdir10, open as open5, readdir as readdir2, readFile as readFile9, rename as rename2, rm as rm5, writeFile as writeFile7 } from "node:fs/promises";
17230
- import path42 from "node:path";
17717
+ import path44 from "node:path";
17231
17718
 
17232
17719
  // src/core/recovery/snapshot-node.ts
17233
17720
  init_fingerprint2();
@@ -17247,25 +17734,25 @@ import {
17247
17734
  symlink as symlink3,
17248
17735
  writeFile as writeFile6
17249
17736
  } from "node:fs/promises";
17250
- import path41 from "node:path";
17737
+ import path43 from "node:path";
17251
17738
  var RECOVERY_UNSUPPORTED_FILE_KIND = "recovery_unsupported_file_kind";
17252
17739
  function validRecoveryRelativePath(relativePath) {
17253
- if (!relativePath || relativePath.includes("\0") || path41.isAbsolute(relativePath)) return false;
17254
- const normalized = path41.normalize(relativePath);
17255
- return normalized !== "." && normalized !== ".." && !normalized.startsWith(`..${path41.sep}`);
17740
+ if (!relativePath || relativePath.includes("\0") || path43.isAbsolute(relativePath)) return false;
17741
+ const normalized = path43.normalize(relativePath);
17742
+ return normalized !== "." && normalized !== ".." && !normalized.startsWith(`..${path43.sep}`);
17256
17743
  }
17257
17744
  async function assertRecoverySafeTarget(resourceRoot, relativePath) {
17258
17745
  if (!validRecoveryRelativePath(relativePath)) throw new Error("recovery_path_escape");
17259
17746
  const root = canonicalPath(resourceRoot);
17260
- const target = path41.resolve(root, relativePath);
17261
- const relative = path41.relative(root, target);
17262
- if (relative === ".." || relative.startsWith(`..${path41.sep}`) || path41.isAbsolute(relative)) {
17747
+ const target = path43.resolve(root, relativePath);
17748
+ const relative = path43.relative(root, target);
17749
+ if (relative === ".." || relative.startsWith(`..${path43.sep}`) || path43.isAbsolute(relative)) {
17263
17750
  throw new Error("recovery_path_escape");
17264
17751
  }
17265
17752
  let current = root;
17266
- const parentParts = path41.relative(root, path41.dirname(target)).split(path41.sep).filter(Boolean);
17753
+ const parentParts = path43.relative(root, path43.dirname(target)).split(path43.sep).filter(Boolean);
17267
17754
  for (const part of parentParts) {
17268
- current = path41.join(current, part);
17755
+ current = path43.join(current, part);
17269
17756
  if (!existsSync12(current)) break;
17270
17757
  const info = await lstat6(current);
17271
17758
  if (info.isSymbolicLink()) throw new Error("recovery_symlink_escape");
@@ -17321,7 +17808,7 @@ async function captureRecoverySnapshot(filePath, options) {
17321
17808
  let blob;
17322
17809
  if (options?.blobDir) {
17323
17810
  await mkdir9(options.blobDir, { recursive: true, mode: 448 });
17324
- const blobPath = path41.join(options.blobDir, hash);
17811
+ const blobPath = path43.join(options.blobDir, hash);
17325
17812
  if (!existsSync12(blobPath)) {
17326
17813
  await writeFile6(blobPath, content, { mode: 384 });
17327
17814
  await fsyncPath(blobPath);
@@ -17376,7 +17863,7 @@ async function validateRecoverySnapshot(params) {
17376
17863
  if (record.blob !== `blobs/${record.hash}`) throw new Error(params.corruptReason);
17377
17864
  let content;
17378
17865
  try {
17379
- content = await readFile8(path41.join(params.artifactDir, record.blob));
17866
+ content = await readFile8(path43.join(params.artifactDir, record.blob));
17380
17867
  } catch {
17381
17868
  throw new Error(params.corruptReason);
17382
17869
  }
@@ -17404,13 +17891,13 @@ var RECOVERY_STATES = /* @__PURE__ */ new Set([
17404
17891
  ]);
17405
17892
  var STAGING_STALE_MS = 5 * 6e4;
17406
17893
  function checkpointsRoot(stateDir) {
17407
- return path42.join(stateDir, "recovery", "checkpoints");
17894
+ return path44.join(stateDir, "recovery", "checkpoints");
17408
17895
  }
17409
17896
  function checkpointDir(stateDir, checkpointId) {
17410
17897
  if (!/^cp_[a-f0-9]{24}$/.test(checkpointId)) {
17411
17898
  throw new Error("invalid_recovery_checkpoint_id");
17412
17899
  }
17413
- return path42.join(checkpointsRoot(stateDir), checkpointId);
17900
+ return path44.join(checkpointsRoot(stateDir), checkpointId);
17414
17901
  }
17415
17902
  async function fsyncPath2(filePath) {
17416
17903
  const handle = await open5(filePath, "r");
@@ -17421,13 +17908,13 @@ async function fsyncPath2(filePath) {
17421
17908
  }
17422
17909
  }
17423
17910
  async function atomicWriteJson(filePath, value) {
17424
- await mkdir10(path42.dirname(filePath), { recursive: true, mode: 448 });
17911
+ await mkdir10(path44.dirname(filePath), { recursive: true, mode: 448 });
17425
17912
  const temporary = `${filePath}.tmp-${randomUUID4()}`;
17426
17913
  await writeFile7(temporary, `${JSON.stringify(value, null, 2)}
17427
17914
  `, { mode: 384 });
17428
17915
  await fsyncPath2(temporary);
17429
17916
  await rename2(temporary, filePath);
17430
- await fsyncPath2(path42.dirname(filePath));
17917
+ await fsyncPath2(path44.dirname(filePath));
17431
17918
  }
17432
17919
  async function writeRecoveryState(artifactDir, state, manifestHash, detail) {
17433
17920
  const value = {
@@ -17437,13 +17924,13 @@ async function writeRecoveryState(artifactDir, state, manifestHash, detail) {
17437
17924
  manifestHash,
17438
17925
  ...detail ? { detail } : {}
17439
17926
  };
17440
- await atomicWriteJson(path42.join(artifactDir, "state.json"), value);
17927
+ await atomicWriteJson(path44.join(artifactDir, "state.json"), value);
17441
17928
  }
17442
17929
  async function directorySize(root) {
17443
17930
  if (!existsSync13(root)) return 0;
17444
17931
  let total = 0;
17445
17932
  for (const entry of await readdir2(root, { withFileTypes: true })) {
17446
- const entryPath = path42.join(root, entry.name);
17933
+ const entryPath = path44.join(root, entry.name);
17447
17934
  if (entry.isDirectory()) total += await directorySize(entryPath);
17448
17935
  else total += (await lstat7(entryPath)).size;
17449
17936
  }
@@ -17488,9 +17975,9 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
17488
17975
  let rawManifest;
17489
17976
  let state;
17490
17977
  try {
17491
- rawManifest = JSON.parse(await readFile9(path42.join(artifactDir, "manifest.json"), "utf8"));
17978
+ rawManifest = JSON.parse(await readFile9(path44.join(artifactDir, "manifest.json"), "utf8"));
17492
17979
  state = JSON.parse(
17493
- await readFile9(path42.join(artifactDir, "state.json"), "utf8")
17980
+ await readFile9(path44.join(artifactDir, "state.json"), "utf8")
17494
17981
  );
17495
17982
  } catch {
17496
17983
  throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
@@ -17506,10 +17993,10 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
17506
17993
  }
17507
17994
  const entryPaths = /* @__PURE__ */ new Set();
17508
17995
  for (const entry of manifest.entries) {
17509
- if (!entry || typeof entry !== "object" || Array.isArray(entry) || Object.keys(entry).length !== 3 || !Object.keys(entry).every((key) => ["path", "before", "after"].includes(key)) || typeof entry.path !== "string" || !("before" in entry) || !("after" in entry) || !validRecoveryRelativePath(entry.path) || entryPaths.has(path42.normalize(entry.path))) {
17996
+ if (!entry || typeof entry !== "object" || Array.isArray(entry) || Object.keys(entry).length !== 3 || !Object.keys(entry).every((key) => ["path", "before", "after"].includes(key)) || typeof entry.path !== "string" || !("before" in entry) || !("after" in entry) || !validRecoveryRelativePath(entry.path) || entryPaths.has(path44.normalize(entry.path))) {
17510
17997
  throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
17511
17998
  }
17512
- entryPaths.add(path42.normalize(entry.path));
17999
+ entryPaths.add(path44.normalize(entry.path));
17513
18000
  for (const [side, snapshot] of [
17514
18001
  ["before", entry.before],
17515
18002
  ["after", entry.after]
@@ -17523,7 +18010,7 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
17523
18010
  });
17524
18011
  }
17525
18012
  }
17526
- const receiptPath = path42.join(artifactDir, "receipt.json");
18013
+ const receiptPath = path44.join(artifactDir, "receipt.json");
17527
18014
  let receipt;
17528
18015
  if (["applied", "restoring", "restored", "conflict"].includes(state.state) || existsSync13(receiptPath)) {
17529
18016
  receipt = await readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash);
@@ -17533,7 +18020,7 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
17533
18020
  async function readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash) {
17534
18021
  let rawReceipt;
17535
18022
  try {
17536
- rawReceipt = JSON.parse(await readFile9(path42.join(artifactDir, "receipt.json"), "utf8"));
18023
+ rawReceipt = JSON.parse(await readFile9(path44.join(artifactDir, "receipt.json"), "utf8"));
17537
18024
  } catch {
17538
18025
  throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
17539
18026
  }
@@ -17556,7 +18043,7 @@ async function readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHas
17556
18043
  return receipt;
17557
18044
  }
17558
18045
  async function ensureRecoveryReceipt(artifactDir, manifest, manifestHash) {
17559
- const receiptPath = path42.join(artifactDir, "receipt.json");
18046
+ const receiptPath = path44.join(artifactDir, "receipt.json");
17560
18047
  if (existsSync13(receiptPath)) {
17561
18048
  return readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash);
17562
18049
  }
@@ -17581,7 +18068,7 @@ async function artifactRepoRoot(stateDir, checkpointId) {
17581
18068
  const artifactDir = checkpointDir(stateDir, checkpointId);
17582
18069
  try {
17583
18070
  const manifest = JSON.parse(
17584
- await readFile9(path42.join(artifactDir, "manifest.json"), "utf8")
18071
+ await readFile9(path44.join(artifactDir, "manifest.json"), "utf8")
17585
18072
  );
17586
18073
  if (typeof manifest.repoRoot === "string" && manifest.repoRoot) {
17587
18074
  return canonicalPath(manifest.repoRoot);
@@ -17589,7 +18076,7 @@ async function artifactRepoRoot(stateDir, checkpointId) {
17589
18076
  } catch {
17590
18077
  }
17591
18078
  try {
17592
- const owner = JSON.parse(await readFile9(path42.join(artifactDir, "owner.json"), "utf8"));
18079
+ const owner = JSON.parse(await readFile9(path44.join(artifactDir, "owner.json"), "utf8"));
17593
18080
  return typeof owner.repoRoot === "string" && owner.repoRoot ? canonicalPath(owner.repoRoot) : null;
17594
18081
  } catch {
17595
18082
  return null;
@@ -17609,10 +18096,10 @@ async function cleanupOrphanedStaging(stateDir) {
17609
18096
  const now = Date.now();
17610
18097
  for (const entry of await readdir2(root, { withFileTypes: true })) {
17611
18098
  if (!entry.isDirectory() || !/^\.tmp-cp_[a-f0-9]{24}$/.test(entry.name)) continue;
17612
- const stagingPath = path42.join(root, entry.name);
18099
+ const stagingPath = path44.join(root, entry.name);
17613
18100
  let stale = false;
17614
18101
  try {
17615
- const owner = JSON.parse(await readFile9(path42.join(stagingPath, "owner.json"), "utf8"));
18102
+ const owner = JSON.parse(await readFile9(path44.join(stagingPath, "owner.json"), "utf8"));
17616
18103
  const pid = typeof owner.pid === "number" ? owner.pid : Number.NaN;
17617
18104
  const createdAt = typeof owner.createdAt === "string" ? Date.parse(owner.createdAt) : NaN;
17618
18105
  let alive = false;
@@ -17654,7 +18141,7 @@ async function markRecoveryCheckpointApplied(stateDir, checkpoint) {
17654
18141
  init_fingerprint2();
17655
18142
  import { existsSync as existsSync14 } from "node:fs";
17656
18143
  import { readFile as readFile10 } from "node:fs/promises";
17657
- import path43 from "node:path";
18144
+ import path45 from "node:path";
17658
18145
  async function matchRecoverySide(resourceRoot, entries, side) {
17659
18146
  for (const entry of entries) {
17660
18147
  const target = await assertRecoverySafeTarget(resourceRoot, entry.path);
@@ -17669,7 +18156,7 @@ async function reconcileRecoveryCheckpoint(stateDir, checkpointId) {
17669
18156
  } catch {
17670
18157
  const artifactDir = checkpointDir(stateDir, checkpointId);
17671
18158
  if (existsSync14(artifactDir)) {
17672
- const manifestPath = path43.join(artifactDir, "manifest.json");
18159
+ const manifestPath = path45.join(artifactDir, "manifest.json");
17673
18160
  const hash = existsSync14(manifestPath) ? hashValue(await readFile10(manifestPath, "utf8")) : "unavailable";
17674
18161
  await writeRecoveryState(artifactDir, "corrupt", hash, RECOVERY_CHECKPOINT_CORRUPT);
17675
18162
  }
@@ -17706,7 +18193,7 @@ async function reconcileRecoveryCheckpoint(stateDir, checkpointId) {
17706
18193
  // src/core/recovery/resource-identity.ts
17707
18194
  init_fingerprint2();
17708
18195
  import { lstat as lstat8, readFile as readFile11, realpath as realpath3 } from "node:fs/promises";
17709
- import path44 from "node:path";
18196
+ import path46 from "node:path";
17710
18197
  async function currentRecoveryResourceIdentity(resourceRoot, resourceKind) {
17711
18198
  const resolvedRoot = await realpath3(resourceRoot);
17712
18199
  if (resourceKind === "directory") {
@@ -17714,13 +18201,13 @@ async function currentRecoveryResourceIdentity(resourceRoot, resourceKind) {
17714
18201
  if (!rootInfo.isDirectory()) throw new Error("recovery_repo_identity_unavailable");
17715
18202
  return hashValue(`${resolvedRoot}\0${rootInfo.dev}:${rootInfo.ino}:${rootInfo.birthtimeMs}`);
17716
18203
  }
17717
- const dotGit = path44.join(resolvedRoot, ".git");
18204
+ const dotGit = path46.join(resolvedRoot, ".git");
17718
18205
  const gitInfo = await lstat8(dotGit);
17719
18206
  let gitMetadataPath = dotGit;
17720
18207
  if (gitInfo.isFile()) {
17721
18208
  const marker = (await readFile11(dotGit, "utf8")).trim();
17722
18209
  if (!marker.startsWith("gitdir:")) throw new Error("recovery_repo_identity_unavailable");
17723
- gitMetadataPath = path44.resolve(resolvedRoot, marker.slice("gitdir:".length).trim());
18210
+ gitMetadataPath = path46.resolve(resolvedRoot, marker.slice("gitdir:".length).trim());
17724
18211
  } else if (!gitInfo.isDirectory()) {
17725
18212
  throw new Error("recovery_repo_identity_unavailable");
17726
18213
  }
@@ -17793,16 +18280,16 @@ async function prepareRecoveryCheckpoint(params) {
17793
18280
  throw new Error(RECOVERY_CHECKPOINT_QUOTA);
17794
18281
  }
17795
18282
  const checkpointId = `cp_${randomUUID5().replaceAll("-", "").slice(0, 24)}`;
17796
- const temporary = path45.join(checkpointsRoot(params.stateDir), `.tmp-${checkpointId}`);
18283
+ const temporary = path47.join(checkpointsRoot(params.stateDir), `.tmp-${checkpointId}`);
17797
18284
  const finalDir = checkpointDir(params.stateDir, checkpointId);
17798
18285
  await mkdir11(temporary, { recursive: true, mode: 448 });
17799
- await atomicWriteJson(path45.join(temporary, "owner.json"), {
18286
+ await atomicWriteJson(path47.join(temporary, "owner.json"), {
17800
18287
  version: 1,
17801
18288
  pid: process.pid,
17802
18289
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
17803
18290
  repoRoot: canonicalPath(params.repoRoot)
17804
18291
  });
17805
- await mkdir11(path45.join(temporary, "blobs"), { recursive: true, mode: 448 });
18292
+ await mkdir11(path47.join(temporary, "blobs"), { recursive: true, mode: 448 });
17806
18293
  try {
17807
18294
  const entries = [];
17808
18295
  const protectedRoots = (params.protectedRoots ?? []).map(canonicalPath);
@@ -17811,8 +18298,8 @@ async function prepareRecoveryCheckpoint(params) {
17811
18298
  )) {
17812
18299
  const target = await assertRecoverySafeTarget(params.repoRoot, change.relativePath);
17813
18300
  if (protectedRoots.some((root) => {
17814
- const relative = path45.relative(root, target);
17815
- return relative === "" || relative !== ".." && !relative.startsWith(`..${path45.sep}`) && !path45.isAbsolute(relative);
18301
+ const relative = path47.relative(root, target);
18302
+ return relative === "" || relative !== ".." && !relative.startsWith(`..${path47.sep}`) && !path47.isAbsolute(relative);
17816
18303
  })) {
17817
18304
  throw new Error("recovery_protected_path");
17818
18305
  }
@@ -17824,7 +18311,7 @@ async function prepareRecoveryCheckpoint(params) {
17824
18311
  entries.push({
17825
18312
  path: change.relativePath,
17826
18313
  before: await captureRecoverySnapshot(baseline, {
17827
- blobDir: path45.join(temporary, "blobs")
18314
+ blobDir: path47.join(temporary, "blobs")
17828
18315
  }),
17829
18316
  after: withoutRecoveryBlob(await captureRecoverySnapshot(source))
17830
18317
  });
@@ -17861,7 +18348,7 @@ async function prepareRecoveryCheckpoint(params) {
17861
18348
  entries
17862
18349
  };
17863
18350
  const manifestHash = hashValue(canonicalStringify(manifest));
17864
- await atomicWriteJson(path45.join(temporary, "manifest.json"), manifest);
18351
+ await atomicWriteJson(path47.join(temporary, "manifest.json"), manifest);
17865
18352
  await writeRecoveryState(temporary, "prepared", manifestHash);
17866
18353
  await fsyncPath2(temporary);
17867
18354
  const projectedBytes = await recoveryCheckpointStorageBytes(params.stateDir, params.repoRoot);
@@ -17904,7 +18391,7 @@ async function listRecoveryCheckpoints(stateDir, repoRoot) {
17904
18391
  } catch {
17905
18392
  try {
17906
18393
  const raw = JSON.parse(
17907
- await readFile12(path45.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
18394
+ await readFile12(path47.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
17908
18395
  );
17909
18396
  rootFromArtifact = typeof raw.repoRoot === "string" && raw.repoRoot ? raw.repoRoot : void 0;
17910
18397
  } catch {
@@ -17938,7 +18425,7 @@ async function listRecoveryCheckpoints(stateDir, repoRoot) {
17938
18425
  } catch {
17939
18426
  try {
17940
18427
  const manifest = JSON.parse(
17941
- await readFile12(path45.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
18428
+ await readFile12(path47.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
17942
18429
  );
17943
18430
  if (manifest.checkpointId !== id || ![1, 2].includes(manifest.version)) continue;
17944
18431
  if (repoRoot && canonicalPath(manifest.repoRoot) !== canonicalPath(repoRoot)) continue;
@@ -17968,7 +18455,7 @@ async function recoveryCheckpointStorageBytes(stateDir, repoRoot) {
17968
18455
  let total = 0;
17969
18456
  for (const entry of await readdir3(root, { withFileTypes: true })) {
17970
18457
  if (!entry.isDirectory()) continue;
17971
- const entryPath = path45.join(root, entry.name);
18458
+ const entryPath = path47.join(root, entry.name);
17972
18459
  if (/^cp_[a-f0-9]{24}$/.test(entry.name)) {
17973
18460
  if (await artifactRepoRoot(stateDir, entry.name) === expected) {
17974
18461
  total += await directorySize(entryPath);
@@ -17977,7 +18464,7 @@ async function recoveryCheckpointStorageBytes(stateDir, repoRoot) {
17977
18464
  }
17978
18465
  if (/^\.tmp-cp_[a-f0-9]{24}$/.test(entry.name)) {
17979
18466
  try {
17980
- const owner = JSON.parse(await readFile12(path45.join(entryPath, "owner.json"), "utf8"));
18467
+ const owner = JSON.parse(await readFile12(path47.join(entryPath, "owner.json"), "utf8"));
17981
18468
  if (typeof owner.repoRoot === "string" && canonicalPath(owner.repoRoot) === expected) {
17982
18469
  total += await directorySize(entryPath);
17983
18470
  }
@@ -18021,14 +18508,14 @@ init_scrub();
18021
18508
  // src/core/transactional/file-checkpoint-backend.ts
18022
18509
  import { cp, lstat as lstat11, mkdir as mkdir13, mkdtemp as mkdtemp5, readdir as readdir6, rm as rm9, writeFile as writeFile10 } from "node:fs/promises";
18023
18510
  import os5 from "node:os";
18024
- import path49 from "node:path";
18511
+ import path51 from "node:path";
18025
18512
 
18026
18513
  // src/core/transactional/file-checkpoint-git.ts
18027
18514
  init_path_utils();
18028
18515
  import { spawn as spawn8 } from "node:child_process";
18029
18516
  import { createHash as createHash13 } from "node:crypto";
18030
18517
  import { copyFile as copyFile3, lstat as lstat9, readdir as readdir4, readFile as readFile13 } from "node:fs/promises";
18031
- import path46 from "node:path";
18518
+ import path48 from "node:path";
18032
18519
  var FILE_CHECKPOINT_GIT_METADATA_CHANGED = "file_checkpoint_git_metadata_changed";
18033
18520
  var FILE_CHECKPOINT_SOURCE_CHANGED = "file_checkpoint_source_changed";
18034
18521
  var FILE_CHECKPOINT_CWD_OUTSIDE_ROOT = "file_checkpoint_cwd_outside_root";
@@ -18062,7 +18549,7 @@ function rethrowStableFileCheckpointError(error) {
18062
18549
  }
18063
18550
  async function rootGitMetadataPresent(repoRoot) {
18064
18551
  try {
18065
- await lstat9(path46.join(repoRoot, ".git"));
18552
+ await lstat9(path48.join(repoRoot, ".git"));
18066
18553
  return true;
18067
18554
  } catch {
18068
18555
  return false;
@@ -18111,10 +18598,10 @@ function execGit2(repoRoot, args) {
18111
18598
  }
18112
18599
  async function resolveGitPath(repoRoot, gitPath) {
18113
18600
  const trimmed = gitPath.trim();
18114
- if (path46.isAbsolute(trimmed)) {
18601
+ if (path48.isAbsolute(trimmed)) {
18115
18602
  return trimmed;
18116
18603
  }
18117
- return path46.join(repoRoot, trimmed);
18604
+ return path48.join(repoRoot, trimmed);
18118
18605
  }
18119
18606
  async function cloneBareWorktreeCopy(sourceRoot, destinationRoot) {
18120
18607
  await execGit2(sourceRoot, [
@@ -18156,7 +18643,7 @@ async function copyGitIndexState(sourceRoot, destinationRoot) {
18156
18643
  destinationRoot,
18157
18644
  await execGit2(destinationRoot, ["rev-parse", "--git-dir"])
18158
18645
  );
18159
- const destinationShared = path46.join(destinationGitDir, path46.basename(sourceShared));
18646
+ const destinationShared = path48.join(destinationGitDir, path48.basename(sourceShared));
18160
18647
  try {
18161
18648
  await copyFile3(sourceShared, destinationShared);
18162
18649
  } catch (error) {
@@ -18165,7 +18652,7 @@ async function copyGitIndexState(sourceRoot, destinationRoot) {
18165
18652
  }
18166
18653
  async function readGitFile(gitDir, relativePath) {
18167
18654
  try {
18168
- return await readFile13(path46.join(gitDir, relativePath));
18655
+ return await readFile13(path48.join(gitDir, relativePath));
18169
18656
  } catch {
18170
18657
  return null;
18171
18658
  }
@@ -18189,8 +18676,8 @@ async function hashResolvedGitPath(repoRoot, gitDir, gitPath, hash) {
18189
18676
  repoRoot,
18190
18677
  await execGit2(repoRoot, ["rev-parse", "--git-path", gitPath])
18191
18678
  );
18192
- const relative = path46.resolve(resolved).startsWith(path46.resolve(gitDir)) ? path46.relative(gitDir, resolved) : resolved;
18193
- const content = typeof relative === "string" && !relative.startsWith("..") && !path46.isAbsolute(relative) ? await readGitFile(gitDir, relative) : await readAbsoluteGitFile(resolved);
18679
+ const relative = path48.resolve(resolved).startsWith(path48.resolve(gitDir)) ? path48.relative(gitDir, resolved) : resolved;
18680
+ const content = typeof relative === "string" && !relative.startsWith("..") && !path48.isAbsolute(relative) ? await readGitFile(gitDir, relative) : await readAbsoluteGitFile(resolved);
18194
18681
  if (content !== null) {
18195
18682
  hashGitFileContent(hash, gitPath, content);
18196
18683
  }
@@ -18200,13 +18687,13 @@ async function hashResolvedGitPath(repoRoot, gitDir, gitPath, hash) {
18200
18687
  async function hashGitTree(gitDir, relativeDir, hash) {
18201
18688
  let names;
18202
18689
  try {
18203
- names = await readdir4(path46.join(gitDir, relativeDir));
18690
+ names = await readdir4(path48.join(gitDir, relativeDir));
18204
18691
  } catch {
18205
18692
  return;
18206
18693
  }
18207
18694
  for (const name of names.sort()) {
18208
- const relativePath = relativeDir ? path46.join(relativeDir, name) : name;
18209
- const absolutePath = path46.join(gitDir, relativePath);
18695
+ const relativePath = relativeDir ? path48.join(relativeDir, name) : name;
18696
+ const absolutePath = path48.join(gitDir, relativePath);
18210
18697
  let childNames = null;
18211
18698
  try {
18212
18699
  childNames = await readdir4(absolutePath);
@@ -18228,7 +18715,7 @@ async function hashGitTree(gitDir, relativeDir, hash) {
18228
18715
  }
18229
18716
  async function computeGitMetadataFingerprint(repoRoot) {
18230
18717
  const gitDirRel = (await execGit2(repoRoot, ["rev-parse", "--git-dir"])).trim();
18231
- const gitDir = path46.isAbsolute(gitDirRel) ? gitDirRel : path46.join(repoRoot, gitDirRel);
18718
+ const gitDir = path48.isAbsolute(gitDirRel) ? gitDirRel : path48.join(repoRoot, gitDirRel);
18232
18719
  const hash = createHash13("sha256");
18233
18720
  for (const file of [
18234
18721
  "HEAD",
@@ -18251,8 +18738,8 @@ async function computeGitMetadataFingerprint(repoRoot) {
18251
18738
  const sharedIndex = (await execGit2(repoRoot, ["rev-parse", "--shared-index-path"])).trim();
18252
18739
  if (sharedIndex) {
18253
18740
  const resolved = await resolveGitPath(repoRoot, sharedIndex);
18254
- const relative = path46.relative(gitDir, resolved);
18255
- const content = relative && !relative.startsWith("..") && !path46.isAbsolute(relative) ? await readGitFile(gitDir, relative) : await readAbsoluteGitFile(resolved);
18741
+ const relative = path48.relative(gitDir, resolved);
18742
+ const content = relative && !relative.startsWith("..") && !path48.isAbsolute(relative) ? await readGitFile(gitDir, relative) : await readAbsoluteGitFile(resolved);
18256
18743
  if (content !== null) {
18257
18744
  hashGitFileContent(hash, "shared-index", content);
18258
18745
  }
@@ -18263,7 +18750,7 @@ async function computeGitMetadataFingerprint(repoRoot) {
18263
18750
  await hashResolvedGitPath(repoRoot, gitDir, gitPath, hash);
18264
18751
  }
18265
18752
  try {
18266
- const rootGitPath = path46.join(repoRoot, ".git");
18753
+ const rootGitPath = path48.join(repoRoot, ".git");
18267
18754
  const rootGitInfo = await lstat9(rootGitPath);
18268
18755
  if (rootGitInfo.isFile()) {
18269
18756
  const content = await readAbsoluteGitFile(rootGitPath);
@@ -18279,14 +18766,14 @@ async function computeGitMetadataFingerprint(repoRoot) {
18279
18766
  function resolveExecutionCwdRelative(resourceRoot, cwd) {
18280
18767
  const resolvedCwd = canonicalPath(cwd);
18281
18768
  const resourceCanonical = canonicalPath(resourceRoot);
18282
- const relative = path46.relative(resourceCanonical, resolvedCwd);
18769
+ const relative = path48.relative(resourceCanonical, resolvedCwd);
18283
18770
  if (relative === "" || relative === ".") {
18284
18771
  return "";
18285
18772
  }
18286
- if (relative.startsWith("..") || path46.isAbsolute(relative)) {
18773
+ if (relative.startsWith("..") || path48.isAbsolute(relative)) {
18287
18774
  throw new Error(FILE_CHECKPOINT_CWD_OUTSIDE_ROOT);
18288
18775
  }
18289
- return relative.split(path46.sep).join("/");
18776
+ return relative.split(path48.sep).join("/");
18290
18777
  }
18291
18778
 
18292
18779
  // src/core/transactional/file-checkpoint-isolation.ts
@@ -18308,7 +18795,7 @@ function fileCheckpointIsolationReason(context) {
18308
18795
 
18309
18796
  // src/core/transactional/file-checkpoint-staging.ts
18310
18797
  import { readdir as readdir5, readFile as readFile14, rm as rm7, writeFile as writeFile8 } from "node:fs/promises";
18311
- import path47 from "node:path";
18798
+ import path49 from "node:path";
18312
18799
  function isOwnerProcessAlive(pid) {
18313
18800
  try {
18314
18801
  process.kill(pid, 0);
@@ -18318,12 +18805,12 @@ function isOwnerProcessAlive(pid) {
18318
18805
  }
18319
18806
  }
18320
18807
  async function writeOwnerMarker(stagingRoot, marker) {
18321
- await writeFile8(path47.join(stagingRoot, "owner.json"), `${JSON.stringify(marker)}
18808
+ await writeFile8(path49.join(stagingRoot, "owner.json"), `${JSON.stringify(marker)}
18322
18809
  `, "utf8");
18323
18810
  }
18324
18811
  async function readOwnerMarker(stagingRoot) {
18325
18812
  try {
18326
- const raw = await readFile14(path47.join(stagingRoot, "owner.json"), "utf8");
18813
+ const raw = await readFile14(path49.join(stagingRoot, "owner.json"), "utf8");
18327
18814
  return JSON.parse(raw.trim());
18328
18815
  } catch {
18329
18816
  return null;
@@ -18341,7 +18828,7 @@ async function collectDeadOwnerStaging(parentDir) {
18341
18828
  if (!name.startsWith("belay-file-checkpoint-")) {
18342
18829
  continue;
18343
18830
  }
18344
- const stagingRoot = path47.join(parentDir, name);
18831
+ const stagingRoot = path49.join(parentDir, name);
18345
18832
  const marker = await readOwnerMarker(stagingRoot);
18346
18833
  if (!marker) {
18347
18834
  dead.push(stagingRoot);
@@ -18373,7 +18860,7 @@ import {
18373
18860
  writeFile as writeFile9
18374
18861
  } from "node:fs/promises";
18375
18862
  import os4 from "node:os";
18376
- import path48 from "node:path";
18863
+ import path50 from "node:path";
18377
18864
  var FILE_CHECKPOINT_COPY_FAILED = "file_checkpoint_copy_failed";
18378
18865
  async function chmodSafe2(target, mode) {
18379
18866
  try {
@@ -18383,7 +18870,7 @@ async function chmodSafe2(target, mode) {
18383
18870
  }
18384
18871
  }
18385
18872
  async function copyRegularFile(sourcePath, destinationPath, mode, strategy) {
18386
- await mkdir12(path48.dirname(destinationPath), { recursive: true });
18873
+ await mkdir12(path50.dirname(destinationPath), { recursive: true });
18387
18874
  if (strategy === "clonefile" && fsConstants2.COPYFILE_FICLONE !== void 0) {
18388
18875
  try {
18389
18876
  await copyFile4(sourcePath, destinationPath, fsConstants2.COPYFILE_FICLONE);
@@ -18409,7 +18896,7 @@ async function copyNode(sourceRoot, destinationRoot, relativePath, strategy) {
18409
18896
  return strategy;
18410
18897
  }
18411
18898
  if (info.isSymbolicLink()) {
18412
- await mkdir12(path48.dirname(destinationPath), { recursive: true });
18899
+ await mkdir12(path50.dirname(destinationPath), { recursive: true });
18413
18900
  await symlink4(await readlink5(sourcePath), destinationPath);
18414
18901
  return strategy;
18415
18902
  }
@@ -18465,9 +18952,9 @@ async function mapWithConcurrency(items, concurrency, worker) {
18465
18952
  async function probeFileCloneStrategy() {
18466
18953
  let tempDir = null;
18467
18954
  try {
18468
- tempDir = await mkdtemp4(path48.join(os4.tmpdir(), "belay-clone-probe-"));
18469
- const source = path48.join(tempDir, "source.txt");
18470
- const destination = path48.join(tempDir, "dest.txt");
18955
+ tempDir = await mkdtemp4(path50.join(os4.tmpdir(), "belay-clone-probe-"));
18956
+ const source = path50.join(tempDir, "source.txt");
18957
+ const destination = path50.join(tempDir, "dest.txt");
18471
18958
  await writeFile9(source, "probe\n");
18472
18959
  if (fsConstants2.COPYFILE_FICLONE_FORCE !== void 0) {
18473
18960
  try {
@@ -18620,11 +19107,11 @@ async function protectedRootState(root) {
18620
19107
  return `directory:${node.hash}:${index.treeHash}`;
18621
19108
  }
18622
19109
  function executionProtectedRoot(resourceRoot, executionRoot, protectedRoot) {
18623
- const relative = path49.relative(path49.resolve(resourceRoot), path49.resolve(protectedRoot));
18624
- if (relative === "" || relative.startsWith("..") || path49.isAbsolute(relative)) {
19110
+ const relative = path51.relative(path51.resolve(resourceRoot), path51.resolve(protectedRoot));
19111
+ if (relative === "" || relative.startsWith("..") || path51.isAbsolute(relative)) {
18625
19112
  return null;
18626
19113
  }
18627
- return path49.join(executionRoot, relative);
19114
+ return path51.join(executionRoot, relative);
18628
19115
  }
18629
19116
  async function captureProtectedRootStates(resourceRoot, executionRoot, protectedRoots) {
18630
19117
  const states = /* @__PURE__ */ new Map();
@@ -18649,15 +19136,15 @@ async function directoryByteSize(root, deadlineMs) {
18649
19136
  }
18650
19137
  let total = 0;
18651
19138
  for (const name of await readdir6(root)) {
18652
- total += await directoryByteSize(path49.join(root, name), deadlineMs);
19139
+ total += await directoryByteSize(path51.join(root, name), deadlineMs);
18653
19140
  }
18654
19141
  return total;
18655
19142
  }
18656
19143
  async function copyGitMetadataDirectory(sourceRoot, destinationRoot) {
18657
19144
  const gitDirRel = (await execGit2(sourceRoot, ["rev-parse", "--git-dir"])).trim();
18658
- const sourceGitDir = path49.isAbsolute(gitDirRel) ? gitDirRel : path49.join(sourceRoot, gitDirRel);
18659
- const relativeGitDir = path49.relative(path49.resolve(sourceRoot), path49.resolve(sourceGitDir));
18660
- const destinationGitDir = relativeGitDir && !relativeGitDir.startsWith("..") ? path49.join(destinationRoot, relativeGitDir) : path49.join(destinationRoot, ".git");
19145
+ const sourceGitDir = path51.isAbsolute(gitDirRel) ? gitDirRel : path51.join(sourceRoot, gitDirRel);
19146
+ const relativeGitDir = path51.relative(path51.resolve(sourceRoot), path51.resolve(sourceGitDir));
19147
+ const destinationGitDir = relativeGitDir && !relativeGitDir.startsWith("..") ? path51.join(destinationRoot, relativeGitDir) : path51.join(destinationRoot, ".git");
18661
19148
  await cp(sourceGitDir, destinationGitDir, { recursive: true, force: true });
18662
19149
  }
18663
19150
  async function prepareDirtyGitSnapshot(context) {
@@ -18666,7 +19153,7 @@ async function prepareDirtyGitSnapshot(context) {
18666
19153
  const quotas = context.fileCheckpoint;
18667
19154
  const deadlineMs = Date.now() + quotas.prepareTimeoutMs;
18668
19155
  await removeDeadOwnerStaging(os5.tmpdir());
18669
- const stagingRoot = await mkdtemp5(path49.join(os5.tmpdir(), "belay-file-checkpoint-"));
19156
+ const stagingRoot = await mkdtemp5(path51.join(os5.tmpdir(), "belay-file-checkpoint-"));
18670
19157
  await writeOwnerMarker(stagingRoot, {
18671
19158
  version: 1,
18672
19159
  pid: process.pid,
@@ -18674,8 +19161,8 @@ async function prepareDirtyGitSnapshot(context) {
18674
19161
  resourceRoot: context.repoRoot,
18675
19162
  backend: "file_checkpoint"
18676
19163
  });
18677
- const baselineRoot = path49.join(stagingRoot, "baseline");
18678
- const executionRoot = path49.join(stagingRoot, "execution");
19164
+ const baselineRoot = path51.join(stagingRoot, "baseline");
19165
+ const executionRoot = path51.join(stagingRoot, "execution");
18679
19166
  try {
18680
19167
  resolveExecutionCwdRelative(context.repoRoot, context.cwd);
18681
19168
  const sourceGitMetadataFingerprint = await computeGitMetadataFingerprint(context.repoRoot);
@@ -18709,7 +19196,7 @@ async function prepareDirtyGitSnapshot(context) {
18709
19196
  throw new Error(FILE_CHECKPOINT_SOURCE_CHANGED);
18710
19197
  }
18711
19198
  await writeFile10(
18712
- path49.join(stagingRoot, "baseline-index.json"),
19199
+ path51.join(stagingRoot, "baseline-index.json"),
18713
19200
  `${JSON.stringify(baselineIndex)}
18714
19201
  `,
18715
19202
  "utf8"
@@ -18759,7 +19246,7 @@ async function prepareNonGitSnapshot(context) {
18759
19246
  const quotas = context.fileCheckpoint;
18760
19247
  const deadlineMs = Date.now() + quotas.prepareTimeoutMs;
18761
19248
  await removeDeadOwnerStaging(os5.tmpdir());
18762
- const stagingRoot = await mkdtemp5(path49.join(os5.tmpdir(), "belay-file-checkpoint-"));
19249
+ const stagingRoot = await mkdtemp5(path51.join(os5.tmpdir(), "belay-file-checkpoint-"));
18763
19250
  await writeOwnerMarker(stagingRoot, {
18764
19251
  version: 1,
18765
19252
  pid: process.pid,
@@ -18767,8 +19254,8 @@ async function prepareNonGitSnapshot(context) {
18767
19254
  resourceRoot: context.repoRoot,
18768
19255
  backend: "file_checkpoint"
18769
19256
  });
18770
- const baselineRoot = path49.join(stagingRoot, "baseline");
18771
- const executionRoot = path49.join(stagingRoot, "execution");
19257
+ const baselineRoot = path51.join(stagingRoot, "baseline");
19258
+ const executionRoot = path51.join(stagingRoot, "execution");
18772
19259
  try {
18773
19260
  resolveExecutionCwdRelative(context.repoRoot, context.cwd);
18774
19261
  const resourceIdentity = await currentRecoveryResourceIdentity(context.repoRoot, "directory");
@@ -18797,7 +19284,7 @@ async function prepareNonGitSnapshot(context) {
18797
19284
  throw new Error(FILE_CHECKPOINT_SOURCE_CHANGED);
18798
19285
  }
18799
19286
  await writeFile10(
18800
- path49.join(stagingRoot, "baseline-index.json"),
19287
+ path51.join(stagingRoot, "baseline-index.json"),
18801
19288
  `${JSON.stringify(baselineIndex)}
18802
19289
  `,
18803
19290
  "utf8"
@@ -19150,10 +19637,10 @@ async function selectTransactionalBackend(context) {
19150
19637
  }
19151
19638
 
19152
19639
  // src/core/transactional/diff-evaluator.ts
19153
- import path50 from "node:path";
19640
+ import path52 from "node:path";
19154
19641
  init_path_utils();
19155
19642
  function categorizeChange(change, ctx) {
19156
- const absolutePath = canonicalPath(path50.join(ctx.repoRoot, change.relativePath));
19643
+ const absolutePath = canonicalPath(path52.join(ctx.repoRoot, change.relativePath));
19157
19644
  if (!pathWithinRoot(ctx.repoRoot, absolutePath)) {
19158
19645
  return "repo_outside";
19159
19646
  }
@@ -19747,7 +20234,7 @@ async function notifyDeny(config, event) {
19747
20234
  init_path_utils();
19748
20235
 
19749
20236
  // src/adapters/layouts/protected-paths.ts
19750
- import path51 from "node:path";
20237
+ import path53 from "node:path";
19751
20238
  function protectedArtifactRoots(layout, repoRoot, controlPlaneDir) {
19752
20239
  const roots = [
19753
20240
  layout.configPath(repoRoot),
@@ -19759,7 +20246,7 @@ function protectedArtifactRoots(layout, repoRoot, controlPlaneDir) {
19759
20246
  if (controlPlaneDir) {
19760
20247
  roots.push(controlPlaneDir);
19761
20248
  }
19762
- return roots.map((entry) => path51.resolve(entry));
20249
+ return roots.map((entry) => path53.resolve(entry));
19763
20250
  }
19764
20251
 
19765
20252
  // src/adapters/shared/gate-runtime.ts
@@ -19812,8 +20299,8 @@ function createDefaultGateRuntimeDeps() {
19812
20299
  return loadJsonFile(configPath, {});
19813
20300
  },
19814
20301
  async appendAudit(ctx, event) {
19815
- const auditPath = path52.join(ctx.repoRoot, ctx.config.audit.logPath);
19816
- await mkdir14(path52.dirname(auditPath), { recursive: true });
20302
+ const auditPath = path54.join(ctx.repoRoot, ctx.config.audit.logPath);
20303
+ await mkdir14(path54.dirname(auditPath), { recursive: true });
19817
20304
  const provenance = auditProvenance(ctx.config);
19818
20305
  const record = {
19819
20306
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -19846,7 +20333,7 @@ function createDefaultGateRuntimeDeps() {
19846
20333
  };
19847
20334
  },
19848
20335
  async writeApprovals(filePath, state) {
19849
- await mkdir14(path52.dirname(filePath), { recursive: true });
20336
+ await mkdir14(path54.dirname(filePath), { recursive: true });
19850
20337
  await writeFile11(filePath, `${JSON.stringify(compactApprovals(state), null, 2)}
19851
20338
  `, "utf8");
19852
20339
  },
@@ -20008,7 +20495,7 @@ function deriveWorkspaceRootScopeHint(params) {
20008
20495
  if (!targetPath) {
20009
20496
  return void 0;
20010
20497
  }
20011
- const candidateRoot = canonicalPath(path52.dirname(targetPath));
20498
+ const candidateRoot = canonicalPath(path54.dirname(targetPath));
20012
20499
  const validation = validateTrustedWorkspaceRootCandidate({
20013
20500
  candidatePath: candidateRoot,
20014
20501
  repoRoot: action.repoRoot,
@@ -20342,6 +20829,7 @@ async function evaluateGatedAction(ctx, deps, params) {
20342
20829
  event: resolveGateAuditEvent(sourceEvent, params.kind),
20343
20830
  sourceEvent,
20344
20831
  kind: params.kind,
20832
+ ...typeof params.payload?.tool_use_id === "string" ? { toolInvocationCorrelationId: toolInvocationCorrelationId(params.payload.tool_use_id) } : {},
20345
20833
  fingerprint: verdict2.fingerprint,
20346
20834
  verdict: verdict2.verdict,
20347
20835
  reason: verdict2.reason,
@@ -20499,6 +20987,7 @@ async function evaluateGatedAction(ctx, deps, params) {
20499
20987
  const scrubbedPayload = fingerprintReplayPayload(params.kind, params.payload, scrubOpts);
20500
20988
  return gateDecisionToVerdict(ctx, deps, params.kind, result, {
20501
20989
  sourceEvent: params.sourceEvent,
20990
+ toolInvocationCorrelationId: typeof params.payload?.tool_use_id === "string" ? toolInvocationCorrelationId(params.payload.tool_use_id) : void 0,
20502
20991
  predictedAssessment,
20503
20992
  observedAssessment: observedAssessment2,
20504
20993
  transactionalLayer,
@@ -20582,6 +21071,7 @@ async function gateDecisionToVerdict(ctx, deps, kind, result, auditExtras = {})
20582
21071
  event: auditEvent,
20583
21072
  sourceEvent,
20584
21073
  kind,
21074
+ ...auditExtras.toolInvocationCorrelationId ? { toolInvocationCorrelationId: auditExtras.toolInvocationCorrelationId } : {},
20585
21075
  fingerprint: result.fingerprint,
20586
21076
  summary: result.normalizedCommand ?? result.summary ?? "",
20587
21077
  assessment: result.assessment,
@@ -21028,29 +21518,56 @@ function gateVerdictToClaudeUserPromptResponse(verdict2) {
21028
21518
  };
21029
21519
  }
21030
21520
  async function appendObservedAudit(ctx, deps, eventName, payload) {
21521
+ const rawToolUseId = typeof payload.tool_use_id === "string" ? payload.tool_use_id : void 0;
21522
+ const summaryPayload = redactToolInvocationId(payload, rawToolUseId);
21031
21523
  await deps.appendAudit(ctx, {
21032
21524
  event: eventName,
21033
21525
  kind: "audit",
21034
21526
  verdict: "allow",
21035
21527
  reason: "observed",
21036
- summary: canonicalStringify(payload)
21528
+ ...rawToolUseId ? { toolInvocationCorrelationId: toolInvocationCorrelationId(rawToolUseId) } : {},
21529
+ ...typeof summaryPayload.tool_name === "string" ? { toolName: summaryPayload.tool_name } : {},
21530
+ ...typeof summaryPayload.failure_type === "string" ? { failureType: summaryPayload.failure_type } : {},
21531
+ ...typeof summaryPayload.error_message === "string" ? { errorMessage: summaryPayload.error_message } : {},
21532
+ ...typeof payload.duration === "number" ? { durationMs: payload.duration } : {},
21533
+ ...typeof payload.is_interrupt === "boolean" ? { isInterrupt: payload.is_interrupt } : {},
21534
+ summary: canonicalStringify(summaryPayload)
21037
21535
  });
21038
21536
  }
21039
21537
 
21040
21538
  // src/adapters/shared/repo-root.ts
21041
21539
  import { existsSync as existsSync17 } from "node:fs";
21042
- import path53 from "node:path";
21540
+ import path55 from "node:path";
21541
+ function belayConfigPath(current, adapterName) {
21542
+ if (adapterName === "cursor") {
21543
+ return path55.join(current, ".cursor", "belay.config.json");
21544
+ }
21545
+ if (adapterName === "claude") {
21546
+ return path55.join(current, ".claude", "belay.config.json");
21547
+ }
21548
+ return path55.join(current, ".codex", "belay.config.json");
21549
+ }
21550
+ function markerMatches(current, marker, layout) {
21551
+ const markerPath = path55.join(current, marker);
21552
+ if (!existsSync17(markerPath)) {
21553
+ return false;
21554
+ }
21555
+ if (marker === ".cursor" || marker === ".claude" || marker === ".codex") {
21556
+ return existsSync17(belayConfigPath(current, layout.name));
21557
+ }
21558
+ return true;
21559
+ }
21043
21560
  function findRepoRoot(startPath, layout) {
21044
- let current = path53.resolve(startPath);
21561
+ let current = path55.resolve(startPath);
21045
21562
  while (true) {
21046
21563
  for (const marker of layout.repoRootMarkers) {
21047
- if (existsSync17(path53.join(current, marker))) {
21564
+ if (markerMatches(current, marker, layout)) {
21048
21565
  return current;
21049
21566
  }
21050
21567
  }
21051
- const parent = path53.dirname(current);
21568
+ const parent = path55.dirname(current);
21052
21569
  if (parent === current) {
21053
- return path53.resolve(startPath);
21570
+ return path55.resolve(startPath);
21054
21571
  }
21055
21572
  current = parent;
21056
21573
  }