@guilz-dev/belay 0.9.1 → 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 (61) 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 +5 -3
  7. package/dist/adapters/cursor/hooks.js +29 -20
  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 +1225 -287
  13. package/dist/bundle/codex-runtime.mjs +1247 -291
  14. package/dist/bundle/cursor-runtime.mjs +4320 -3154
  15. package/dist/cli.js +33 -3
  16. package/dist/commands/doctor.js +38 -9
  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-legacy-archive.d.ts +1 -0
  28. package/dist/core/audit-legacy-archive.js +5 -0
  29. package/dist/core/audit-query.d.ts +1 -0
  30. package/dist/core/audit-query.js +7 -0
  31. package/dist/core/audit-serialize.d.ts +3 -0
  32. package/dist/core/audit-serialize.js +39 -3
  33. package/dist/core/audit-summary.d.ts +9 -0
  34. package/dist/core/audit-summary.js +58 -1
  35. package/dist/core/audit-types.d.ts +4 -0
  36. package/dist/core/effect-ir/shell-lower.js +283 -27
  37. package/dist/core/replay-scrub.d.ts +1 -0
  38. package/dist/core/replay-scrub.js +22 -3
  39. package/dist/core/shell-tokenizer.d.ts +28 -0
  40. package/dist/core/shell-tokenizer.js +111 -29
  41. package/dist/core/verdict/docker-compose-run.d.ts +18 -0
  42. package/dist/core/verdict/docker-compose-run.js +136 -0
  43. package/dist/core/verdict/launcher-resolve.js +66 -25
  44. package/dist/core/verdict/makefile-expand.d.ts +4 -0
  45. package/dist/core/verdict/makefile-expand.js +151 -0
  46. package/dist/core/verdict/parser.d.ts +4 -0
  47. package/dist/core/verdict/parser.js +25 -30
  48. package/dist/core/verdict/recursive-invocation.d.ts +20 -0
  49. package/dist/core/verdict/recursive-invocation.js +224 -0
  50. package/dist/corpus/benign-probe-cores.d.ts +1 -1
  51. package/dist/corpus/benign-probe-cores.js +2 -0
  52. package/dist/defaults.js +16 -0
  53. package/dist/installer/scope-config.d.ts +2 -2
  54. package/dist/installer.d.ts +10 -1
  55. package/dist/installer.js +43 -2
  56. package/dist/types.d.ts +34 -1
  57. package/dist/version.d.ts +1 -1
  58. package/dist/version.js +1 -1
  59. package/package.json +5 -2
  60. package/skills/belay/SKILL.md +5 -0
  61. 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;
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;
385
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();
@@ -11125,12 +11249,349 @@ function worstEffectDecision(decisions) {
11125
11249
 
11126
11250
  // src/core/effect-ir/shell-lower.ts
11127
11251
  init_git_resource_identity();
11252
+ init_path_utils();
11128
11253
  init_shell_tokenizer();
11129
11254
  import { lstatSync as lstatSync2, realpathSync as realpathSync4 } from "node:fs";
11130
- import path39 from "node:path";
11255
+ import path41 from "node:path";
11131
11256
 
11132
- // 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
11133
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";
11134
11595
  var CURL_EFFECT_NEUTRAL_FLAGS = /* @__PURE__ */ new Set([
11135
11596
  "-f",
11136
11597
  "-L",
@@ -11165,7 +11626,7 @@ var GH_READ_COMMANDS = /* @__PURE__ */ new Set([
11165
11626
  "workflow view"
11166
11627
  ]);
11167
11628
  function decodeEgressEffects(params) {
11168
- const head = path35.basename(params.tokens[0] ?? "");
11629
+ const head = path37.basename(params.tokens[0] ?? "");
11169
11630
  if (head !== "curl" && head !== "wget" && head !== "gh") {
11170
11631
  return null;
11171
11632
  }
@@ -11173,7 +11634,7 @@ function decodeEgressEffects(params) {
11173
11634
  const provenance = { segment: params.segment };
11174
11635
  const requirements = [];
11175
11636
  for (const file of decoded.files) {
11176
- const resolved = path35.resolve(params.cwd, expandHome2(file));
11637
+ const resolved = path37.resolve(params.cwd, expandHome2(file));
11177
11638
  requirements.push(
11178
11639
  requirement("fs.read", "fs.read", { kind: "path", path: resolved }, params.segment, [
11179
11640
  "egress.explicit_file_read"
@@ -11195,7 +11656,7 @@ function decodeEgressEffects(params) {
11195
11656
  if (file === "-") {
11196
11657
  continue;
11197
11658
  }
11198
- const resolved = path35.resolve(params.cwd, expandHome2(file));
11659
+ const resolved = path37.resolve(params.cwd, expandHome2(file));
11199
11660
  if (resolved === "/dev/null") {
11200
11661
  continue;
11201
11662
  }
@@ -11673,13 +12134,13 @@ function decodeSingleCurlWgetGrammar(head, tokens) {
11673
12134
  if (head === "wget" && !explicitOutput) {
11674
12135
  outputFiles.push(
11675
12136
  ...endpointOutputNames.map(
11676
- (name) => outputDirectory ? path35.join(outputDirectory, name) : name
12137
+ (name) => outputDirectory ? path37.join(outputDirectory, name) : name
11677
12138
  )
11678
12139
  );
11679
12140
  } else if (head === "curl" && remoteNameOutput) {
11680
12141
  outputFiles.push(
11681
12142
  ...endpointOutputNames.map(
11682
- (name) => outputDirectory ? path35.join(outputDirectory, name) : name
12143
+ (name) => outputDirectory ? path37.join(outputDirectory, name) : name
11683
12144
  )
11684
12145
  );
11685
12146
  }
@@ -11693,7 +12154,7 @@ function decodeSingleCurlWgetGrammar(head, tokens) {
11693
12154
  outputFiles: [
11694
12155
  ...new Set(
11695
12156
  outputFiles.map(
11696
- (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
11697
12158
  )
11698
12159
  )
11699
12160
  ],
@@ -11708,7 +12169,7 @@ function remoteOutputName(spec) {
11708
12169
  } catch {
11709
12170
  pathname = spec.split(/[?#]/, 1)[0] ?? "";
11710
12171
  }
11711
- const name = path35.posix.basename(pathname);
12172
+ const name = path37.posix.basename(pathname);
11712
12173
  return name && name !== "/" ? name : "index.html";
11713
12174
  }
11714
12175
  function decodeGhGrammar(tokens) {
@@ -11873,7 +12334,7 @@ function expandHome2(value) {
11873
12334
  return process.env.HOME ?? value;
11874
12335
  }
11875
12336
  if (value.startsWith("~/")) {
11876
- return path35.join(process.env.HOME ?? "~", value.slice(2));
12337
+ return path37.join(process.env.HOME ?? "~", value.slice(2));
11877
12338
  }
11878
12339
  return value;
11879
12340
  }
@@ -11892,7 +12353,7 @@ function requirement(tag, action, resource, segment, signals) {
11892
12353
  }
11893
12354
 
11894
12355
  // src/core/verdict/git-classifier.ts
11895
- import path36 from "node:path";
12356
+ import path38 from "node:path";
11896
12357
  init_shell_tokenizer();
11897
12358
  var GIT_BRANCH_MUTATION_FLAGS = /* @__PURE__ */ new Set([
11898
12359
  "--copy",
@@ -11988,7 +12449,7 @@ var FILE_OPERAND_SUBCOMMANDS = /* @__PURE__ */ new Set([
11988
12449
  var COMPOUND_SUBCOMMAND_HEADS = /* @__PURE__ */ new Set(["worktree", "stash", "tag"]);
11989
12450
  var REF_ONLY_WITHOUT_TERMINATOR = /* @__PURE__ */ new Set(["checkout", "show", "log"]);
11990
12451
  function isGitExecutable(token) {
11991
- return path36.basename(token) === "git";
12452
+ return path38.basename(token) === "git";
11992
12453
  }
11993
12454
  function takesValue(flag) {
11994
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=");
@@ -12016,7 +12477,7 @@ function peelGlobalOptions(tokens, baseCwd) {
12016
12477
  if (token === "-C" || token === "--work-tree" || token === "--git-dir" || token === "-c") {
12017
12478
  const value = tokens[index + 1];
12018
12479
  if (token === "-C" && value) {
12019
- effectiveCwd = path36.resolve(baseCwd, value);
12480
+ effectiveCwd = path38.resolve(baseCwd, value);
12020
12481
  } else if (token === "--work-tree" && value) {
12021
12482
  workTree = value;
12022
12483
  } else if (token === "--git-dir" && value) {
@@ -12026,7 +12487,7 @@ function peelGlobalOptions(tokens, baseCwd) {
12026
12487
  continue;
12027
12488
  }
12028
12489
  if (token.startsWith("-C") && token.length > 2) {
12029
- effectiveCwd = path36.resolve(baseCwd, token.slice(2));
12490
+ effectiveCwd = path38.resolve(baseCwd, token.slice(2));
12030
12491
  index += 1;
12031
12492
  continue;
12032
12493
  }
@@ -12169,7 +12630,7 @@ function looksLikeDiffPathOperand(token) {
12169
12630
  if (!looksLikeFileOperand(token)) {
12170
12631
  return false;
12171
12632
  }
12172
- if (token.startsWith(".") || path36.isAbsolute(token)) {
12633
+ if (token.startsWith(".") || path38.isAbsolute(token)) {
12173
12634
  return true;
12174
12635
  }
12175
12636
  return token.includes(".");
@@ -12177,12 +12638,12 @@ function looksLikeDiffPathOperand(token) {
12177
12638
  function resolveGitWorkTree(baseCwd, effectiveCwd, workTree, gitDir) {
12178
12639
  const resolveBase = effectiveCwd ?? baseCwd;
12179
12640
  if (workTree) {
12180
- return path36.resolve(resolveBase, workTree);
12641
+ return path38.resolve(resolveBase, workTree);
12181
12642
  }
12182
12643
  if (gitDir) {
12183
- const resolvedGitDir = path36.resolve(resolveBase, gitDir);
12184
- if (path36.basename(resolvedGitDir) === ".git") {
12185
- return path36.dirname(resolvedGitDir);
12644
+ const resolvedGitDir = path38.resolve(resolveBase, gitDir);
12645
+ if (path38.basename(resolvedGitDir) === ".git") {
12646
+ return path38.dirname(resolvedGitDir);
12186
12647
  }
12187
12648
  }
12188
12649
  return void 0;
@@ -12263,7 +12724,7 @@ function classifyGitCommand(tokens, baseCwd) {
12263
12724
  const { subcommand, args, effectiveCwd, gitDir, workTree } = normalized;
12264
12725
  const normalizedKey = `git ${subcommand}`;
12265
12726
  const gitWorkTree = resolveGitWorkTree(baseCwd, effectiveCwd, workTree, gitDir);
12266
- const effectiveGitDir = gitDir ? path36.resolve(effectiveCwd ?? baseCwd, gitDir) : void 0;
12727
+ const effectiveGitDir = gitDir ? path38.resolve(effectiveCwd ?? baseCwd, gitDir) : void 0;
12267
12728
  const scopeTargets = [effectiveCwd, gitWorkTree, effectiveGitDir].filter(
12268
12729
  (target, index, targets) => Boolean(target) && targets.indexOf(target) === index
12269
12730
  );
@@ -12415,9 +12876,9 @@ function decodeGitEffects(params) {
12415
12876
  ...subcommand === "push" ? ["tier0_external"] : []
12416
12877
  ];
12417
12878
  const effectiveCwd = normalized.effectiveCwd ?? params.cwd;
12418
- const workTreeRoot = normalized.workTree ? path36.resolve(normalized.effectiveCwd ?? params.cwd, normalized.workTree) : normalized.effectiveCwd ?? params.repoRoot;
12419
- const gitRefRoot = normalized.gitDir ? path36.resolve(effectiveCwd, normalized.gitDir) : workTreeRoot;
12420
- 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");
12421
12882
  const requirements = [];
12422
12883
  if (subcommand === "fetch" || subcommand === "pull") {
12423
12884
  const positionals = gitRemotePositionals(args);
@@ -12576,7 +13037,7 @@ function decodeGitEffects(params) {
12576
13037
  gitRequirement(
12577
13038
  "control_plane.write",
12578
13039
  "control_plane.write",
12579
- { kind: "path", path: path36.join(gitControlRoot, "logs") },
13040
+ { kind: "path", path: path38.join(gitControlRoot, "logs") },
12580
13041
  params.segment,
12581
13042
  [...signals, "git_history_destructive", "git.reflog.mutate"]
12582
13043
  )
@@ -12634,7 +13095,7 @@ function decodeGitEffects(params) {
12634
13095
  gitRequirement(
12635
13096
  "fs.read",
12636
13097
  "fs.read",
12637
- { kind: "path", path: path36.resolve(workTreeRoot, operand) },
13098
+ { kind: "path", path: path38.resolve(workTreeRoot, operand) },
12638
13099
  params.segment,
12639
13100
  [...signals, "git.path.read"]
12640
13101
  )
@@ -12669,7 +13130,7 @@ function decodeGitEffects(params) {
12669
13130
  gitRequirement(
12670
13131
  "fs.write",
12671
13132
  "fs.write",
12672
- { kind: "path", path: path36.resolve(workTreeRoot, operand) },
13133
+ { kind: "path", path: path38.resolve(workTreeRoot, operand) },
12673
13134
  params.segment,
12674
13135
  [...signals, "git.path.write"]
12675
13136
  )
@@ -12854,7 +13315,144 @@ function gitRequirement(tag, action, resource, segment, signals) {
12854
13315
 
12855
13316
  // src/core/verdict/launcher-resolve.ts
12856
13317
  import { existsSync as existsSync11, readFileSync as readFileSync5 } from "node:fs";
12857
- import path37 from "node:path";
13318
+ import path39 from "node:path";
13319
+
13320
+ // src/core/verdict/makefile-expand.ts
13321
+ var MAX_EXPAND_DEPTH = 16;
13322
+ function parseMakefileVariables(content) {
13323
+ const variables = /* @__PURE__ */ new Map();
13324
+ for (const line of content.split("\n")) {
13325
+ const trimmed = line.trim();
13326
+ if (!trimmed || trimmed.startsWith("#")) {
13327
+ continue;
13328
+ }
13329
+ const match = /^([A-Za-z_][A-Za-z0-9_]*)\s*[:?]?=\s*(.+)$/.exec(trimmed);
13330
+ if (!match) {
13331
+ continue;
13332
+ }
13333
+ variables.set(match[1] ?? "", (match[2] ?? "").trim());
13334
+ }
13335
+ return variables;
13336
+ }
13337
+ function normalizeMakeRecipeLine(line) {
13338
+ let normalized = line.trim();
13339
+ while (normalized.startsWith("@") || normalized.startsWith("-") || normalized.startsWith("+")) {
13340
+ normalized = normalized.slice(1).trimStart();
13341
+ }
13342
+ return normalized;
13343
+ }
13344
+ function expandMakeExpression(expression, cliVars, makefileVars) {
13345
+ if (/\$\(\s*shell\b/i.test(expression) || expression.includes("$$")) {
13346
+ return null;
13347
+ }
13348
+ try {
13349
+ const expanded = expandMakeValue(expression, cliVars, makefileVars, 0);
13350
+ if (expanded === null || /\$\(/.test(expanded) || /\$\{/.test(expanded)) {
13351
+ return null;
13352
+ }
13353
+ return expanded;
13354
+ } catch {
13355
+ return null;
13356
+ }
13357
+ }
13358
+ function expandMakeValue(expression, cliVars, makefileVars, depth) {
13359
+ if (depth > MAX_EXPAND_DEPTH) {
13360
+ return null;
13361
+ }
13362
+ let value = expression.trim();
13363
+ let changed = true;
13364
+ let iterations = 0;
13365
+ while (changed && iterations < MAX_EXPAND_DEPTH) {
13366
+ changed = false;
13367
+ iterations += 1;
13368
+ const orMatch = value.match(/\$\(\s*or\s+([^()]*(?:\([^)]*\)[^()]*)*)\)/);
13369
+ if (orMatch) {
13370
+ const [fullMatch, inner] = orMatch;
13371
+ const parts = splitMakeFunctionArgs(inner ?? "");
13372
+ let selected = null;
13373
+ for (const part of parts) {
13374
+ const expanded = expandMakeValue(part.trim(), cliVars, makefileVars, depth + 1);
13375
+ if (expanded !== null && expanded.trim() !== "") {
13376
+ selected = expanded;
13377
+ break;
13378
+ }
13379
+ }
13380
+ if (selected === null) {
13381
+ const fallback = parts.at(-1)?.trim();
13382
+ selected = fallback === void 0 ? "" : expandMakeValue(fallback, cliVars, makefileVars, depth + 1);
13383
+ }
13384
+ if (selected === null) {
13385
+ return null;
13386
+ }
13387
+ value = value.replace(fullMatch, selected);
13388
+ changed = true;
13389
+ continue;
13390
+ }
13391
+ const varMatch = value.match(/\$\(([A-Za-z_][A-Za-z0-9_]*)\)/);
13392
+ if (varMatch) {
13393
+ const [fullMatch, name] = varMatch;
13394
+ const resolved = resolveMakeVariable(name ?? "", cliVars, makefileVars, depth + 1);
13395
+ if (resolved === null) {
13396
+ return null;
13397
+ }
13398
+ value = value.replace(fullMatch, resolved);
13399
+ changed = true;
13400
+ continue;
13401
+ }
13402
+ const bracedMatch = value.match(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/);
13403
+ if (bracedMatch) {
13404
+ const [fullMatch, name] = bracedMatch;
13405
+ const resolved = name === "PWD" ? "." : resolveMakeVariable(name ?? "", cliVars, makefileVars, depth + 1);
13406
+ if (resolved === null) {
13407
+ return null;
13408
+ }
13409
+ value = value.replace(fullMatch, resolved);
13410
+ changed = true;
13411
+ continue;
13412
+ }
13413
+ break;
13414
+ }
13415
+ return value;
13416
+ }
13417
+ function resolveMakeVariable(name, cliVars, makefileVars, depth) {
13418
+ if (Object.hasOwn(cliVars, name)) {
13419
+ return cliVars[name] ?? "";
13420
+ }
13421
+ const definition = makefileVars.get(name);
13422
+ if (definition === void 0) {
13423
+ return null;
13424
+ }
13425
+ return expandMakeValue(definition, cliVars, makefileVars, depth);
13426
+ }
13427
+ function splitMakeFunctionArgs(input) {
13428
+ const parts = [];
13429
+ let current = "";
13430
+ let depth = 0;
13431
+ for (const char of input) {
13432
+ if (char === "(") {
13433
+ depth += 1;
13434
+ current += char;
13435
+ continue;
13436
+ }
13437
+ if (char === ")") {
13438
+ depth -= 1;
13439
+ current += char;
13440
+ continue;
13441
+ }
13442
+ if (char === "," && depth === 0) {
13443
+ parts.push(current.trim());
13444
+ current = "";
13445
+ continue;
13446
+ }
13447
+ current += char;
13448
+ }
13449
+ if (current.trim()) {
13450
+ parts.push(current.trim());
13451
+ }
13452
+ return parts;
13453
+ }
13454
+
13455
+ // src/core/verdict/launcher-resolve.ts
12858
13456
  var MAX_RESOLVE_DEPTH = 8;
12859
13457
  var PNPM_BUILTIN_COMMANDS = /* @__PURE__ */ new Set([
12860
13458
  "add",
@@ -12890,7 +13488,7 @@ var PNPM_BUILTIN_COMMANDS = /* @__PURE__ */ new Set([
12890
13488
  "why"
12891
13489
  ]);
12892
13490
  function readPackageJson(dir) {
12893
- const packagePath = path37.join(dir, "package.json");
13491
+ const packagePath = path39.join(dir, "package.json");
12894
13492
  if (!existsSync11(packagePath)) {
12895
13493
  return null;
12896
13494
  }
@@ -12901,17 +13499,17 @@ function readPackageJson(dir) {
12901
13499
  }
12902
13500
  }
12903
13501
  function findPackageJson(startDir, stopDir) {
12904
- let current = path37.resolve(startDir);
12905
- const stop = path37.resolve(stopDir);
13502
+ let current = path39.resolve(startDir);
13503
+ const stop = path39.resolve(stopDir);
12906
13504
  while (true) {
12907
- const packagePath = path37.join(current, "package.json");
13505
+ const packagePath = path39.join(current, "package.json");
12908
13506
  if (existsSync11(packagePath)) {
12909
13507
  return packagePath;
12910
13508
  }
12911
- if (current === stop || current === path37.dirname(current)) {
13509
+ if (current === stop || current === path39.dirname(current)) {
12912
13510
  return existsSync11(packagePath) ? packagePath : null;
12913
13511
  }
12914
- const parent = path37.dirname(current);
13512
+ const parent = path39.dirname(current);
12915
13513
  if (!parent.startsWith(stop) && parent !== current) {
12916
13514
  }
12917
13515
  if (parent === current) {
@@ -12968,7 +13566,7 @@ function resolveNpmRecipe(cwd, repoRoot, scriptName, extraArgs) {
12968
13566
  }
12969
13567
  return { recipes: [], opaque: true, reason: "package_json_missing" };
12970
13568
  }
12971
- const pkg = readPackageJson(path37.dirname(packagePath));
13569
+ const pkg = readPackageJson(path39.dirname(packagePath));
12972
13570
  const scripts = pkg?.scripts;
12973
13571
  if (!scripts || typeof scripts !== "object") {
12974
13572
  return { recipes: [], opaque: true, reason: "package_scripts_missing" };
@@ -12995,10 +13593,9 @@ function resolveNpmRecipe(cwd, repoRoot, scriptName, extraArgs) {
12995
13593
  reason: "npm_script_resolved"
12996
13594
  };
12997
13595
  }
12998
- function parseMakefileRecipes(makefilePath) {
13596
+ function parseMakefileRecipeContent(content) {
12999
13597
  const targets = /* @__PURE__ */ new Map();
13000
13598
  try {
13001
- const content = readFileSync5(makefilePath, "utf8");
13002
13599
  const lines = content.split("\n");
13003
13600
  let currentTarget = null;
13004
13601
  let recipeLines = [];
@@ -13057,78 +13654,96 @@ function parseMakefileRecipes(makefilePath) {
13057
13654
  }
13058
13655
  return targets;
13059
13656
  }
13060
- function resolveMakeRecipe(cwd, repoRoot, target) {
13657
+ function resolveMakeRecipe(cwd, repoRoot, target, cliVars = {}) {
13061
13658
  const candidates = ["Makefile", "makefile", "GNUmakefile"];
13062
13659
  let makefilePath = null;
13063
- let searchDir = path37.resolve(cwd);
13064
- const stop = path37.resolve(repoRoot);
13660
+ let searchDir = path39.resolve(cwd);
13661
+ const stop = path39.resolve(repoRoot);
13065
13662
  while (true) {
13066
13663
  for (const name of candidates) {
13067
- const candidate = path37.join(searchDir, name);
13664
+ const candidate = path39.join(searchDir, name);
13068
13665
  if (existsSync11(candidate)) {
13069
13666
  makefilePath = candidate;
13070
13667
  break;
13071
13668
  }
13072
13669
  }
13073
- if (makefilePath || searchDir === stop || searchDir === path37.dirname(searchDir)) {
13670
+ if (makefilePath || searchDir === stop || searchDir === path39.dirname(searchDir)) {
13074
13671
  break;
13075
13672
  }
13076
- searchDir = path37.dirname(searchDir);
13673
+ searchDir = path39.dirname(searchDir);
13077
13674
  }
13078
13675
  if (!makefilePath) {
13079
13676
  return { recipes: [], opaque: true, reason: "unknown_local_effect" };
13080
13677
  }
13081
- const targets = parseMakefileRecipes(makefilePath);
13678
+ const makefileContent = readFileSync5(makefilePath, "utf8");
13679
+ const makefileVars = parseMakefileVariables(makefileContent);
13680
+ const targets = parseMakefileRecipeContent(makefileContent);
13082
13681
  if (!targets.has(target)) {
13083
13682
  return { recipes: [], opaque: true, reason: "make_target_undefined" };
13084
13683
  }
13085
13684
  const recipeLines = [];
13086
13685
  const visiting = /* @__PURE__ */ new Set();
13087
13686
  const visited = /* @__PURE__ */ new Set();
13088
- let opaquePrerequisites = false;
13687
+ let hasDynamicPrerequisite = false;
13688
+ let hasUndefinedPrerequisite = false;
13689
+ let hasDependencyCycle = false;
13089
13690
  const collect = (name) => {
13090
13691
  if (visited.has(name)) {
13091
- return true;
13692
+ return;
13092
13693
  }
13093
13694
  if (visiting.has(name)) {
13094
- return false;
13695
+ hasDependencyCycle = true;
13696
+ return;
13095
13697
  }
13096
13698
  const entry = targets.get(name);
13097
13699
  if (!entry) {
13098
- return true;
13700
+ if (!existsSync11(path39.resolve(path39.dirname(makefilePath), name))) {
13701
+ hasUndefinedPrerequisite = true;
13702
+ }
13703
+ return;
13099
13704
  }
13100
13705
  visiting.add(name);
13101
- opaquePrerequisites ||= entry.opaquePrerequisites;
13706
+ hasDynamicPrerequisite ||= entry.opaquePrerequisites;
13102
13707
  for (const prerequisite of entry.prerequisites) {
13103
- if (targets.has(prerequisite) && !collect(prerequisite)) {
13104
- return false;
13105
- }
13708
+ collect(prerequisite);
13106
13709
  }
13107
13710
  recipeLines.push(...entry.recipes);
13108
13711
  visiting.delete(name);
13109
13712
  visited.add(name);
13110
- return true;
13111
13713
  };
13112
- if (!collect(target)) {
13113
- return { recipes: recipeLines, opaque: true, reason: "make_dependency_cycle" };
13114
- }
13714
+ collect(target);
13715
+ const expandedRecipes = [];
13115
13716
  for (const line of recipeLines) {
13116
- if (/\$\(/.test(line) || /\$\{/.test(line)) {
13717
+ const normalized = normalizeMakeRecipeLine(line);
13718
+ const expanded = expandMakeExpression(normalized, cliVars, makefileVars);
13719
+ if (expanded === null) {
13117
13720
  return { recipes: recipeLines, opaque: true, reason: "make_recipe_dynamic" };
13118
13721
  }
13722
+ expandedRecipes.push(expanded);
13723
+ }
13724
+ for (const line of expandedRecipes) {
13725
+ if (/\$\(/.test(line) || /\$\{/.test(line)) {
13726
+ return { recipes: expandedRecipes, opaque: true, reason: "make_recipe_dynamic" };
13727
+ }
13119
13728
  }
13120
- if (opaquePrerequisites) {
13121
- return { recipes: recipeLines, opaque: true, reason: "make_prerequisite_dynamic" };
13729
+ if (hasDependencyCycle) {
13730
+ return { recipes: expandedRecipes, opaque: true, reason: "make_dependency_cycle" };
13122
13731
  }
13123
- return { recipes: recipeLines, opaque: false, reason: "make_recipe_resolved" };
13732
+ if (hasDynamicPrerequisite) {
13733
+ return { recipes: expandedRecipes, opaque: true, reason: "make_prerequisite_dynamic" };
13734
+ }
13735
+ if (hasUndefinedPrerequisite) {
13736
+ return { recipes: expandedRecipes, opaque: true, reason: "make_prerequisite_undefined" };
13737
+ }
13738
+ return { recipes: expandedRecipes, opaque: false, reason: "make_recipe_resolved" };
13124
13739
  }
13125
13740
  function resolveLauncherRecipe(params) {
13126
- if (params.depth >= MAX_RESOLVE_DEPTH) {
13127
- return { recipes: [], opaque: true, reason: "launcher_depth_exceeded" };
13128
- }
13129
13741
  const tokens = params.tokens;
13130
13742
  const scriptName = npmScriptName(tokens);
13131
13743
  if (scriptName) {
13744
+ if (params.depth >= MAX_RESOLVE_DEPTH) {
13745
+ return { recipes: [], opaque: true, reason: "launcher_depth_exceeded" };
13746
+ }
13132
13747
  const resolution = resolveNpmRecipe(
13133
13748
  params.cwd,
13134
13749
  params.repoRoot,
@@ -13144,8 +13759,31 @@ function resolveLauncherRecipe(params) {
13144
13759
  }
13145
13760
  return resolution;
13146
13761
  }
13147
- if (tokens[0] === "make" && tokens[1] && !tokens[1].startsWith("-")) {
13148
- return resolveMakeRecipe(params.cwd, params.repoRoot, tokens[1]);
13762
+ if (tokens[0] === "make") {
13763
+ if (params.depth >= MAX_RESOLVE_DEPTH) {
13764
+ return { recipes: [], opaque: true, reason: "launcher_depth_exceeded" };
13765
+ }
13766
+ if (tokens.includes("-n") || tokens.includes("--dry-run")) {
13767
+ return null;
13768
+ }
13769
+ let target = null;
13770
+ const cliVars = {};
13771
+ for (const token of tokens.slice(1)) {
13772
+ if (token.startsWith("-")) {
13773
+ continue;
13774
+ }
13775
+ const assignment = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/.exec(token);
13776
+ if (assignment) {
13777
+ cliVars[assignment[1] ?? ""] = assignment[2] ?? "";
13778
+ continue;
13779
+ }
13780
+ if (!target) {
13781
+ target = token;
13782
+ }
13783
+ }
13784
+ if (target) {
13785
+ return resolveMakeRecipe(params.cwd, params.repoRoot, target, cliVars);
13786
+ }
13149
13787
  }
13150
13788
  if (tokens[0] === "pnpm" && tokens[1] === "exec" && tokens[2]) {
13151
13789
  return {
@@ -13158,7 +13796,7 @@ function resolveLauncherRecipe(params) {
13158
13796
  }
13159
13797
 
13160
13798
  // src/core/verdict/parser.ts
13161
- import path38 from "node:path";
13799
+ import path40 from "node:path";
13162
13800
 
13163
13801
  // src/core/shell-substitution.ts
13164
13802
  function findStructuralCommandSubstitutions(command) {
@@ -13383,9 +14021,8 @@ function hasUnbalancedDollarParen(command) {
13383
14021
  // src/core/verdict/parser.ts
13384
14022
  var ENV_PREFIX_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*=(?:'[^']*'|"[^"]*"|\S+)$/;
13385
14023
  var MAX_WRAPPER_PEEL_DEPTH = 32;
13386
- var SHELL_INTERPRETERS = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "dash", "fish"]);
14024
+ var SHELL_INTERPRETERS2 = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "dash", "fish"]);
13387
14025
  var CODE_INTERPRETERS = /* @__PURE__ */ new Set(["python", "python3", "node", "ruby", "perl", "osascript"]);
13388
- var SCRIPT_FLAGS = /* @__PURE__ */ new Set(["-c", "-lc", "-e", "--eval"]);
13389
14026
  var INTERPRETER_SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([
13390
14027
  ".js",
13391
14028
  ".mjs",
@@ -13397,7 +14034,7 @@ var INTERPRETER_SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([
13397
14034
  ".sh"
13398
14035
  ]);
13399
14036
  function normalizeHead(token) {
13400
- const base = path38.basename(token);
14037
+ const base = path40.basename(token);
13401
14038
  if (base && base !== "." && base !== "..") {
13402
14039
  return base;
13403
14040
  }
@@ -13409,10 +14046,6 @@ function peelTransparentWrappers(tokens) {
13409
14046
  let encounteredXargs = false;
13410
14047
  let peelDepth = 0;
13411
14048
  while (current.length > 0) {
13412
- if (peelDepth >= MAX_WRAPPER_PEEL_DEPTH) {
13413
- return { tokens: current, xargsStdinOpaque: false, encounteredXargs, opaque: true };
13414
- }
13415
- peelDepth += 1;
13416
14049
  while (current.length > 0 && ENV_PREFIX_PATTERN.test(current[0] ?? "")) {
13417
14050
  current.shift();
13418
14051
  }
@@ -13422,6 +14055,10 @@ function peelTransparentWrappers(tokens) {
13422
14055
  const head = normalizeHead(current[0] ?? "");
13423
14056
  if (head === "xargs") {
13424
14057
  encounteredXargs = true;
14058
+ if (peelDepth >= MAX_WRAPPER_PEEL_DEPTH) {
14059
+ return { tokens: current, xargsStdinOpaque: false, encounteredXargs, opaque: true };
14060
+ }
14061
+ peelDepth += 1;
13425
14062
  const wrapper2 = peelXargsWrapper(current);
13426
14063
  if (wrapper2.kind === "opaque") {
13427
14064
  xargsStdinOpaque = current.length === 1;
@@ -13439,6 +14076,10 @@ function peelTransparentWrappers(tokens) {
13439
14076
  if (!wrapper) {
13440
14077
  break;
13441
14078
  }
14079
+ if (peelDepth >= MAX_WRAPPER_PEEL_DEPTH) {
14080
+ return { tokens: current, xargsStdinOpaque: false, encounteredXargs, opaque: true };
14081
+ }
14082
+ peelDepth += 1;
13442
14083
  if (wrapper.kind === "opaque") {
13443
14084
  return { tokens: current, xargsStdinOpaque: false, encounteredXargs, opaque: true };
13444
14085
  }
@@ -13662,34 +14303,20 @@ function extractRecursiveScript(tokens) {
13662
14303
  return null;
13663
14304
  }
13664
14305
  const head = normalizeHead(filtered[0] ?? "");
13665
- const second = filtered[1] ?? "";
13666
14306
  if (head === "eval") {
13667
14307
  const body = filtered.slice(1).join(" ").trim();
13668
14308
  return body || null;
13669
14309
  }
13670
- if (SHELL_INTERPRETERS.has(head) || CODE_INTERPRETERS.has(head)) {
13671
- const flagIndex = filtered.findIndex((token) => SCRIPT_FLAGS.has(token));
13672
- if (flagIndex !== -1) {
13673
- const body = filtered.slice(flagIndex + 1).join(" ").replace(/^['"]|['"]$/g, "").trim();
13674
- return body || null;
13675
- }
13676
- }
13677
- if (head === "bash" && (second === "-lc" || second === "-c")) {
13678
- const body = filtered.slice(2).join(" ").replace(/^['"]|['"]$/g, "").trim();
13679
- return body || null;
13680
- }
13681
- return null;
14310
+ const invocation = decodeRecursiveInvocation(
14311
+ shellTokensFromValues(filtered, { detectExpansion: false })
14312
+ );
14313
+ return invocation.kind === "static" ? invocation.script || null : null;
13682
14314
  }
13683
- function isDynamicRecursiveEvaluation(tokens) {
13684
- const { tokens: filtered, opaque } = peelTransparentWrappers(tokens);
13685
- if (opaque) {
13686
- return false;
13687
- }
13688
- const head = normalizeHead(filtered[0] ?? "");
13689
- if (head === "eval") {
13690
- return true;
13691
- }
13692
- 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));
13693
14320
  }
13694
14321
  function isCommandInspection(tokens) {
13695
14322
  return normalizeHead(tokens[0] ?? "") === "command" && peelCommandWrapper(tokens).kind === "preserve";
@@ -13703,11 +14330,10 @@ function isBareInterpreter(tokens) {
13703
14330
  return false;
13704
14331
  }
13705
14332
  const head = normalizeHead(peeled[0] ?? "");
13706
- if (!SHELL_INTERPRETERS.has(head) && !CODE_INTERPRETERS.has(head)) {
14333
+ if (!SHELL_INTERPRETERS2.has(head) && !CODE_INTERPRETERS.has(head)) {
13707
14334
  return false;
13708
14335
  }
13709
- const hasScriptFlag = peeled.some((token) => SCRIPT_FLAGS.has(token));
13710
- if (hasScriptFlag) {
14336
+ if (decodeRecursiveInvocation(shellTokensFromValues(peeled)).kind !== "none") {
13711
14337
  return false;
13712
14338
  }
13713
14339
  const args = peeled.slice(1);
@@ -13718,7 +14344,7 @@ function isBareInterpreter(tokens) {
13718
14344
  return false;
13719
14345
  }
13720
14346
  const scriptArg = args.find((token) => !token.startsWith("-"));
13721
- if (scriptArg && INTERPRETER_SCRIPT_EXTENSIONS.has(path38.extname(scriptArg))) {
14347
+ if (scriptArg && INTERPRETER_SCRIPT_EXTENSIONS.has(path40.extname(scriptArg))) {
13722
14348
  return false;
13723
14349
  }
13724
14350
  if (scriptArg) {
@@ -14010,11 +14636,11 @@ function lowerTopLevelSegments(command, context) {
14010
14636
  }
14011
14637
  function startsLocalPostgresService(command) {
14012
14638
  const tokens = tokenizeShell(command);
14013
- 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");
14014
14640
  }
14015
14641
  function resolveCdTransition(command, currentCwd) {
14016
14642
  const tokens = tokenizeShell(command);
14017
- if (path39.basename(tokens[0] ?? "") !== "cd") {
14643
+ if (path41.basename(tokens[0] ?? "") !== "cd") {
14018
14644
  return null;
14019
14645
  }
14020
14646
  const target = tokens[1] ?? "~";
@@ -14035,17 +14661,32 @@ function joinNestedOpacity(outer, nested) {
14035
14661
  }
14036
14662
  function lowerSegment(command, context) {
14037
14663
  const commandRedacted = redactCommand(command);
14038
- const rawTokens = tokenizeShell(command);
14664
+ const lexed = lexShell(command);
14665
+ const rawTokens = lexed.tokens.map((token) => token.value);
14039
14666
  const environment = extractEnvironment(rawTokens, context.env);
14040
14667
  const env = environment.env;
14041
14668
  const parsed = parseSegment(command);
14042
- const tokens = stripRedirects(environment.commandTokens ?? parsed.tokens).map(
14043
- (token) => expandKnownVariables(token, env)
14669
+ const parsedTokens = environment.commandTokens ?? parsed.tokens;
14670
+ const tokens = stripRedirects(
14671
+ parsedTokens.length === 0 && rawTokens.length > 0 && rawTokens.every((token) => ENV_PREFIX_PATTERN2.test(token)) ? rawTokens : parsedTokens
14672
+ ).map((token) => expandKnownVariables(token, env));
14673
+ const decoderTokens = alignStructuredTokens(
14674
+ stripStructuredRedirects(lexed.tokens),
14675
+ stripRedirects(parsedTokens)
14044
14676
  );
14045
- const head = path39.basename(tokens[0] ?? parsed.head);
14677
+ const head = path41.basename(tokens[0] ?? parsed.head);
14046
14678
  let opacity = segmentOpacity(command);
14047
14679
  const signals = /* @__PURE__ */ new Set();
14048
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
+ }
14049
14690
  addRedirectEffects(requirements, rawTokens, env, context, commandRedacted);
14050
14691
  addSubstitutionEffects(requirements, command, context, commandRedacted, signals);
14051
14692
  if (environment.malformed) {
@@ -14065,7 +14706,7 @@ function lowerSegment(command, context) {
14065
14706
  signals.add("shell.xargs_stdin_dynamic");
14066
14707
  opacity = joinEffectOpacity(opacity, "opaque");
14067
14708
  }
14068
- if (context.depth >= MAX_LOWER_DEPTH) {
14709
+ if (context.depth > MAX_LOWER_DEPTH) {
14069
14710
  requirements.push(
14070
14711
  requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
14071
14712
  "shell.lower_depth_exceeded"
@@ -14112,37 +14753,96 @@ function lowerSegment(command, context) {
14112
14753
  }
14113
14754
  return shellSegment(commandRedacted, head, requirements, opacity, signals);
14114
14755
  }
14115
- const recursiveScript = extractRecursiveScript(tokens);
14116
- if (recursiveScript && opacity !== "opaque" && opacity !== "unparseable") {
14117
- const dynamicEvaluation = isDynamicRecursiveEvaluation(tokens);
14756
+ const recursive = decodeRecursiveInvocationTokens(decoderTokens);
14757
+ if (recursive.kind === "static" && opacity !== "opaque" && opacity !== "unparseable") {
14118
14758
  requirements.push(
14119
- processRequirement(head || "sh", "spawn", commandRedacted, [
14759
+ processRequirement(recursive.interpreter, "spawn", commandRedacted, [
14120
14760
  "shell.recursive_wrapper",
14121
- ...dynamicEvaluation ? ["dynamic_shell_evaluation"] : []
14761
+ "dynamic_shell_evaluation"
14122
14762
  ])
14123
14763
  );
14124
- const nested = lowerTopLevelSegments(recursiveScript, {
14125
- ...context,
14126
- command: recursiveScript,
14127
- env,
14128
- depth: context.depth + 1
14129
- });
14130
- for (const nestedSegment of nested) {
14131
- requirements.push(
14132
- ...nestedSegment.requirements.map(
14133
- (entry) => withInnerProvenance(entry, recursiveScript, head, commandRedacted)
14134
- )
14135
- );
14136
- for (const signal of nestedSegment.signals) {
14137
- 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);
14138
14778
  }
14139
14779
  }
14140
14780
  signals.add("shell.recursive_wrapper");
14141
- if (dynamicEvaluation) {
14142
- signals.add("dynamic_shell_evaluation");
14781
+ signals.add("dynamic_shell_evaluation");
14782
+ return shellSegment(commandRedacted, head, requirements, "recursive", signals);
14783
+ }
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") {
14806
+ requirements.push(
14807
+ processRequirement(head, "spawn", commandRedacted, ["process.docker_compose_run"])
14808
+ );
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);
14824
+ }
14143
14825
  }
14826
+ signals.add("process.docker_compose_run");
14144
14827
  return shellSegment(commandRedacted, head, requirements, "recursive", signals);
14145
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
+ }
14146
14846
  const launcher = resolveLauncherRecipe({
14147
14847
  tokens,
14148
14848
  cwd: context.cwd,
@@ -14354,7 +15054,7 @@ function isMetadataOnlyArgv(argv) {
14354
15054
  return argv.length > 0 && argv.every((token) => METADATA_ONLY_FLAGS.has(token));
14355
15055
  }
14356
15056
  function executableBaseName(head) {
14357
- return path39.basename(head);
15057
+ return path41.basename(head);
14358
15058
  }
14359
15059
  var RAILS_READ_ONLY_SUBCOMMANDS = /* @__PURE__ */ new Set(["routes", "middleware", "stats", "about", "version"]);
14360
15060
  function railsReadOnlySubcommand(args) {
@@ -14364,6 +15064,111 @@ function railsReadOnlySubcommand(args) {
14364
15064
  }
14365
15065
  return RAILS_READ_ONLY_SUBCOMMANDS.has(subcommand);
14366
15066
  }
15067
+ function isRubyTestScript(scriptPath) {
15068
+ const base = path41.basename(scriptPath);
15069
+ return base.endsWith("_test.rb") || base.endsWith("_spec.rb");
15070
+ }
15071
+ function parseRubyTestInvocation(args) {
15072
+ const includePaths = [];
15073
+ for (let index = 0; index < args.length; index += 1) {
15074
+ const arg = args[index] ?? "";
15075
+ if (arg === "-e" || arg === "-r") {
15076
+ return null;
15077
+ }
15078
+ if (arg === "-I") {
15079
+ const includePath = args[index + 1];
15080
+ if (!includePath) {
15081
+ return null;
15082
+ }
15083
+ includePaths.push(includePath);
15084
+ index += 1;
15085
+ continue;
15086
+ }
15087
+ if (arg.startsWith("-I") && arg.length > 2) {
15088
+ includePaths.push(arg.slice(2));
15089
+ continue;
15090
+ }
15091
+ if (arg.startsWith("-")) {
15092
+ if (arg === "-n") {
15093
+ if (!args[index + 1]) {
15094
+ return null;
15095
+ }
15096
+ index += 1;
15097
+ continue;
15098
+ }
15099
+ if (arg.startsWith("-n")) {
15100
+ continue;
15101
+ }
15102
+ return null;
15103
+ }
15104
+ if (isRubyTestScript(arg)) {
15105
+ return { includePaths, scriptPath: arg };
15106
+ }
15107
+ return null;
15108
+ }
15109
+ return null;
15110
+ }
15111
+ function isRubocopMutating(args) {
15112
+ return args.some(
15113
+ (arg) => arg === "-A" || arg === "-a" || arg === "--auto-correct" || arg === "--autocorrect" || arg.startsWith("--auto-correct-all") || arg.startsWith("--autocorrect-all")
15114
+ );
15115
+ }
15116
+ function decodeBundleExecInner(innerHead, innerArgs, segment) {
15117
+ const innerBase = executableBaseName(innerHead);
15118
+ if (innerBase === "rubocop") {
15119
+ const mutating = isRubocopMutating(innerArgs);
15120
+ return [
15121
+ processRequirement(
15122
+ innerHead,
15123
+ mutating ? "spawn" : "inspect",
15124
+ segment,
15125
+ mutating ? ["process.linter.mutating"] : ["process.inspect.linter"]
15126
+ )
15127
+ ];
15128
+ }
15129
+ if (innerBase === "rspec") {
15130
+ const targetArgs = innerArgs.filter((arg) => !arg.startsWith("-"));
15131
+ if (targetArgs.length === 0) {
15132
+ return null;
15133
+ }
15134
+ return [processRequirement(innerHead, "spawn", segment, ["process.test_runner.rspec"])];
15135
+ }
15136
+ return null;
15137
+ }
15138
+ function decodeRuby(args, cwd, repoRoot, segment) {
15139
+ const parsed = parseRubyTestInvocation(args);
15140
+ if (!parsed) {
15141
+ return unsupportedProcess("ruby", segment, "process.ruby_grammar_incomplete");
15142
+ }
15143
+ const scriptPath = resolvePathOperand(parsed.scriptPath, cwd);
15144
+ if (!pathWithinRoot(canonicalPath(repoRoot), canonicalPath(scriptPath))) {
15145
+ return unsupportedProcess("ruby", segment, "process.ruby_outside_repo");
15146
+ }
15147
+ for (const includePath of parsed.includePaths) {
15148
+ const resolvedInclude = resolvePathOperand(includePath, cwd);
15149
+ if (!pathWithinRoot(canonicalPath(repoRoot), canonicalPath(resolvedInclude))) {
15150
+ return unsupportedProcess("ruby", segment, "process.ruby_outside_repo");
15151
+ }
15152
+ }
15153
+ const lowered = [
15154
+ processRequirement("ruby", "spawn", segment, ["process.test_runner.minitest"]),
15155
+ requirement2("fs.read", "fs.read", { kind: "path", path: scriptPath }, segment, [
15156
+ "ruby.minitest_script_read"
15157
+ ])
15158
+ ];
15159
+ for (const includePath of parsed.includePaths) {
15160
+ lowered.push(
15161
+ requirement2(
15162
+ "fs.read",
15163
+ "fs.read",
15164
+ { kind: "path", path: resolvePathOperand(includePath, cwd) },
15165
+ segment,
15166
+ ["ruby.minitest_load_path_read"]
15167
+ )
15168
+ );
15169
+ }
15170
+ return lowered;
15171
+ }
14367
15172
  function decodeRuntimeMetadataProcess(head, args, segment) {
14368
15173
  if (head === "bundle") {
14369
15174
  if (args.length === 1 && isMetadataOnlyArgv(args)) {
@@ -14385,6 +15190,10 @@ function decodeRuntimeMetadataProcess(head, args, segment) {
14385
15190
  ])
14386
15191
  ];
14387
15192
  }
15193
+ const bundleExecInner = decodeBundleExecInner(innerHead, innerArgs, segment);
15194
+ if (bundleExecInner) {
15195
+ return bundleExecInner;
15196
+ }
14388
15197
  }
14389
15198
  return null;
14390
15199
  }
@@ -14400,9 +15209,74 @@ function decodeRuntimeMetadataProcess(head, args, segment) {
14400
15209
  }
14401
15210
  return null;
14402
15211
  }
15212
+ function decodeSetBuiltin(args) {
15213
+ let index = 0;
15214
+ while (index < args.length) {
15215
+ const arg = args[index] ?? "";
15216
+ if (arg === "--") {
15217
+ index += 1;
15218
+ continue;
15219
+ }
15220
+ if (arg === "-o" || arg === "+o") {
15221
+ if (!args[index + 1]) {
15222
+ return false;
15223
+ }
15224
+ index += 2;
15225
+ continue;
15226
+ }
15227
+ if (/^[-+][A-Za-z0-9]+$/.test(arg)) {
15228
+ index += 1;
15229
+ continue;
15230
+ }
15231
+ return false;
15232
+ }
15233
+ return true;
15234
+ }
15235
+ function decodeShellControlBuiltin(head, args) {
15236
+ if (head === "set") {
15237
+ return decodeSetBuiltin(args) ? [] : null;
15238
+ }
15239
+ if (head === "wait") {
15240
+ if (args.length === 0 || args.every((arg) => /^\d+$/.test(arg))) {
15241
+ return [];
15242
+ }
15243
+ return null;
15244
+ }
15245
+ if (head === "exit") {
15246
+ if (args.length === 0 || args.length === 1 && /^-?\d+$/.test(args[0] ?? "")) {
15247
+ return [];
15248
+ }
15249
+ return null;
15250
+ }
15251
+ return null;
15252
+ }
15253
+ function decodeDockerComposeRun2(head, args, segment) {
15254
+ let composeArgs = null;
15255
+ let command = head;
15256
+ if (head === "docker-compose") {
15257
+ composeArgs = args;
15258
+ } else if (head === "docker" && args[0] === "compose") {
15259
+ composeArgs = args.slice(1);
15260
+ command = "docker";
15261
+ }
15262
+ if (!composeArgs) {
15263
+ return null;
15264
+ }
15265
+ if (composeArgs.includes("run")) {
15266
+ return [processRequirement(command, "spawn", segment, ["process.docker_compose_run"])];
15267
+ }
15268
+ return unsupportedProcess(command, segment, "process.docker_compose_grammar_incomplete");
15269
+ }
14403
15270
  function decodeProcessOrFilesystem(params) {
14404
15271
  const { tokens, head, env, cwd, repoRoot, segment } = params;
14405
15272
  const args = tokens.slice(1);
15273
+ if (tokens.length > 0 && tokens.every((token) => ENV_PREFIX_PATTERN2.test(token))) {
15274
+ return [];
15275
+ }
15276
+ const shellControl = decodeShellControlBuiltin(head, args);
15277
+ if (shellControl) {
15278
+ return shellControl;
15279
+ }
14406
15280
  if (isCommandInspection(tokens)) {
14407
15281
  return [processRequirement(head, "inspect", segment, ["process.inspect.command_lookup"])];
14408
15282
  }
@@ -14470,7 +15344,7 @@ function decodeProcessOrFilesystem(params) {
14470
15344
  requirement2(
14471
15345
  "fs.read",
14472
15346
  "fs.read",
14473
- { kind: "path", path: path39.resolve(cwd, syntax) },
15347
+ { kind: "path", path: path41.resolve(cwd, syntax) },
14474
15348
  segment,
14475
15349
  ["shell.syntax_source_read"]
14476
15350
  )
@@ -14485,6 +15359,19 @@ function decodeProcessOrFilesystem(params) {
14485
15359
  if (runtimeMetadata) {
14486
15360
  return runtimeMetadata;
14487
15361
  }
15362
+ if (head === "ruby") {
15363
+ return decodeRuby(args, cwd, repoRoot, segment);
15364
+ }
15365
+ if (head === "rubocop" || head === "rspec") {
15366
+ const decoded = decodeBundleExecInner(head, args, segment);
15367
+ if (decoded) {
15368
+ return decoded;
15369
+ }
15370
+ }
15371
+ const dockerCompose = decodeDockerComposeRun2(head, args, segment);
15372
+ if (dockerCompose) {
15373
+ return dockerCompose;
15374
+ }
14488
15375
  if ((head === "npm" || head === "pnpm") && args.length === 1 && isMetadataOnlyArgv(args)) {
14489
15376
  return [processRequirement(head, "inspect", segment, ["process.inspect.package_manager"])];
14490
15377
  }
@@ -14507,7 +15394,7 @@ function decodeProcessOrFilesystem(params) {
14507
15394
  return [processRequirement(head, "inspect", segment, ["process.inspect.base64_stdin"])];
14508
15395
  }
14509
15396
  if (head === "node") {
14510
- return decodeNode(args, cwd, segment);
15397
+ return decodeNode2(args, cwd, segment);
14511
15398
  }
14512
15399
  if (head === "vite" || head === "vite-node") {
14513
15400
  return [processRequirement(head, "spawn", segment, ["process.local_dev_spawn"])];
@@ -14597,7 +15484,7 @@ function decodeBelay(args, repoRoot, segment) {
14597
15484
  requirement2(
14598
15485
  "control_plane.write",
14599
15486
  "control_plane.write",
14600
- { kind: "path", path: path39.join(repoRoot, ".belay-control-plane") },
15487
+ { kind: "path", path: path41.join(repoRoot, ".belay-control-plane") },
14601
15488
  segment,
14602
15489
  ["belay.config_non_judge_mutation"]
14603
15490
  )
@@ -14759,11 +15646,11 @@ function decodeRm(args, cwd, repoRoot, segment) {
14759
15646
  function canonicalRmOperand(targetPath, finalOperandIsSymlink) {
14760
15647
  try {
14761
15648
  if (finalOperandIsSymlink) {
14762
- return path39.join(realpathSync4.native(path39.dirname(targetPath)), path39.basename(targetPath));
15649
+ return path41.join(realpathSync4.native(path41.dirname(targetPath)), path41.basename(targetPath));
14763
15650
  }
14764
15651
  return realpathSync4.native(targetPath);
14765
15652
  } catch {
14766
- return path39.resolve(targetPath);
15653
+ return path41.resolve(targetPath);
14767
15654
  }
14768
15655
  }
14769
15656
  function isSymbolicLink(targetPath) {
@@ -14774,8 +15661,8 @@ function isSymbolicLink(targetPath) {
14774
15661
  }
14775
15662
  }
14776
15663
  function pathContains(ancestor, candidate) {
14777
- const relative = path39.relative(path39.resolve(ancestor), path39.resolve(candidate));
14778
- 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);
14779
15666
  }
14780
15667
  function decodeGo(args, segment) {
14781
15668
  if (["test", "list", "vet"].includes(args[0] ?? "")) {
@@ -14921,7 +15808,7 @@ function decodeSed(args, cwd, segment) {
14921
15808
  }
14922
15809
  return lowered;
14923
15810
  }
14924
- function decodeNode(args, cwd, segment) {
15811
+ function decodeNode2(args, cwd, segment) {
14925
15812
  if (args.length > 0 && args.every((arg) => ["--help", "--version", "-h", "-v"].includes(arg))) {
14926
15813
  return [processRequirement("node", "inspect", segment, ["process.inspect.node_metadata"])];
14927
15814
  }
@@ -15187,15 +16074,36 @@ function stripRedirects(tokens) {
15187
16074
  stripped.push(token);
15188
16075
  continue;
15189
16076
  }
15190
- if (token.includes(">") || token.includes("<")) {
15191
- const inline = token.replace(/^\d*(?:>>?|<<?|<>|>\|)/, "");
15192
- if (!inline) {
15193
- index += 1;
15194
- }
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;
15195
16088
  }
16089
+ if (!isRedirectOperator(token.value)) {
16090
+ stripped.push(token);
16091
+ continue;
16092
+ }
16093
+ index += 1;
15196
16094
  }
15197
16095
  return stripped;
15198
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
+ }
15199
16107
  function shellSegment(commandRedacted, segmentHead, requirements, opacity, signals) {
15200
16108
  const normalizedRequirements = requirements.flatMap((entry) => {
15201
16109
  const dynamicSignal = dynamicResourceSignal(entry.resource);
@@ -15595,9 +16503,9 @@ function resolvePathOperand(operand, cwd) {
15595
16503
  return process.env.HOME ?? operand;
15596
16504
  }
15597
16505
  if (operand.startsWith("~/")) {
15598
- return path39.join(process.env.HOME ?? "~", operand.slice(2));
16506
+ return path41.join(process.env.HOME ?? "~", operand.slice(2));
15599
16507
  }
15600
- return path39.resolve(cwd, operand);
16508
+ return path41.resolve(cwd, operand);
15601
16509
  }
15602
16510
  function isShellHead(head) {
15603
16511
  return head === "bash" || head === "sh" || head === "zsh" || head === "dash" || head === "fish";
@@ -16121,7 +17029,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
16121
17029
  };
16122
17030
  }
16123
17031
  const signals = [];
16124
- const resolvedPath = path40.isAbsolute(filePath) ? filePath : path40.resolve(cwd, filePath);
17032
+ const resolvedPath = path42.isAbsolute(filePath) ? filePath : path42.resolve(cwd, filePath);
16125
17033
  const hitsProtectedRoot = protectedRoots.some((root) => pathWithinRoot(root, resolvedPath));
16126
17034
  if (hitsProtectedRoot) {
16127
17035
  signals.push("control_plane_path");
@@ -16723,7 +17631,7 @@ function hashDecisionConfig(config) {
16723
17631
  init_fingerprint2();
16724
17632
 
16725
17633
  // src/version.ts
16726
- var PACKAGE_VERSION = "0.9.1";
17634
+ var PACKAGE_VERSION = "0.9.3";
16727
17635
 
16728
17636
  // src/runtime-provenance.ts
16729
17637
  function resolveRuntimeArtifactHash(artifactHash) {
@@ -16798,7 +17706,7 @@ init_path_utils();
16798
17706
  import { randomUUID as randomUUID5 } from "node:crypto";
16799
17707
  import { existsSync as existsSync15 } from "node:fs";
16800
17708
  import { mkdir as mkdir11, readdir as readdir3, readFile as readFile12, rename as rename3, rm as rm6 } from "node:fs/promises";
16801
- import path45 from "node:path";
17709
+ import path47 from "node:path";
16802
17710
 
16803
17711
  // src/core/recovery/artifact-store.ts
16804
17712
  init_fingerprint2();
@@ -16806,7 +17714,7 @@ init_path_utils();
16806
17714
  import { randomUUID as randomUUID4 } from "node:crypto";
16807
17715
  import { existsSync as existsSync13 } from "node:fs";
16808
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";
16809
- import path42 from "node:path";
17717
+ import path44 from "node:path";
16810
17718
 
16811
17719
  // src/core/recovery/snapshot-node.ts
16812
17720
  init_fingerprint2();
@@ -16826,25 +17734,25 @@ import {
16826
17734
  symlink as symlink3,
16827
17735
  writeFile as writeFile6
16828
17736
  } from "node:fs/promises";
16829
- import path41 from "node:path";
17737
+ import path43 from "node:path";
16830
17738
  var RECOVERY_UNSUPPORTED_FILE_KIND = "recovery_unsupported_file_kind";
16831
17739
  function validRecoveryRelativePath(relativePath) {
16832
- if (!relativePath || relativePath.includes("\0") || path41.isAbsolute(relativePath)) return false;
16833
- const normalized = path41.normalize(relativePath);
16834
- 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}`);
16835
17743
  }
16836
17744
  async function assertRecoverySafeTarget(resourceRoot, relativePath) {
16837
17745
  if (!validRecoveryRelativePath(relativePath)) throw new Error("recovery_path_escape");
16838
17746
  const root = canonicalPath(resourceRoot);
16839
- const target = path41.resolve(root, relativePath);
16840
- const relative = path41.relative(root, target);
16841
- 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)) {
16842
17750
  throw new Error("recovery_path_escape");
16843
17751
  }
16844
17752
  let current = root;
16845
- 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);
16846
17754
  for (const part of parentParts) {
16847
- current = path41.join(current, part);
17755
+ current = path43.join(current, part);
16848
17756
  if (!existsSync12(current)) break;
16849
17757
  const info = await lstat6(current);
16850
17758
  if (info.isSymbolicLink()) throw new Error("recovery_symlink_escape");
@@ -16900,7 +17808,7 @@ async function captureRecoverySnapshot(filePath, options) {
16900
17808
  let blob;
16901
17809
  if (options?.blobDir) {
16902
17810
  await mkdir9(options.blobDir, { recursive: true, mode: 448 });
16903
- const blobPath = path41.join(options.blobDir, hash);
17811
+ const blobPath = path43.join(options.blobDir, hash);
16904
17812
  if (!existsSync12(blobPath)) {
16905
17813
  await writeFile6(blobPath, content, { mode: 384 });
16906
17814
  await fsyncPath(blobPath);
@@ -16955,7 +17863,7 @@ async function validateRecoverySnapshot(params) {
16955
17863
  if (record.blob !== `blobs/${record.hash}`) throw new Error(params.corruptReason);
16956
17864
  let content;
16957
17865
  try {
16958
- content = await readFile8(path41.join(params.artifactDir, record.blob));
17866
+ content = await readFile8(path43.join(params.artifactDir, record.blob));
16959
17867
  } catch {
16960
17868
  throw new Error(params.corruptReason);
16961
17869
  }
@@ -16983,13 +17891,13 @@ var RECOVERY_STATES = /* @__PURE__ */ new Set([
16983
17891
  ]);
16984
17892
  var STAGING_STALE_MS = 5 * 6e4;
16985
17893
  function checkpointsRoot(stateDir) {
16986
- return path42.join(stateDir, "recovery", "checkpoints");
17894
+ return path44.join(stateDir, "recovery", "checkpoints");
16987
17895
  }
16988
17896
  function checkpointDir(stateDir, checkpointId) {
16989
17897
  if (!/^cp_[a-f0-9]{24}$/.test(checkpointId)) {
16990
17898
  throw new Error("invalid_recovery_checkpoint_id");
16991
17899
  }
16992
- return path42.join(checkpointsRoot(stateDir), checkpointId);
17900
+ return path44.join(checkpointsRoot(stateDir), checkpointId);
16993
17901
  }
16994
17902
  async function fsyncPath2(filePath) {
16995
17903
  const handle = await open5(filePath, "r");
@@ -17000,13 +17908,13 @@ async function fsyncPath2(filePath) {
17000
17908
  }
17001
17909
  }
17002
17910
  async function atomicWriteJson(filePath, value) {
17003
- await mkdir10(path42.dirname(filePath), { recursive: true, mode: 448 });
17911
+ await mkdir10(path44.dirname(filePath), { recursive: true, mode: 448 });
17004
17912
  const temporary = `${filePath}.tmp-${randomUUID4()}`;
17005
17913
  await writeFile7(temporary, `${JSON.stringify(value, null, 2)}
17006
17914
  `, { mode: 384 });
17007
17915
  await fsyncPath2(temporary);
17008
17916
  await rename2(temporary, filePath);
17009
- await fsyncPath2(path42.dirname(filePath));
17917
+ await fsyncPath2(path44.dirname(filePath));
17010
17918
  }
17011
17919
  async function writeRecoveryState(artifactDir, state, manifestHash, detail) {
17012
17920
  const value = {
@@ -17016,13 +17924,13 @@ async function writeRecoveryState(artifactDir, state, manifestHash, detail) {
17016
17924
  manifestHash,
17017
17925
  ...detail ? { detail } : {}
17018
17926
  };
17019
- await atomicWriteJson(path42.join(artifactDir, "state.json"), value);
17927
+ await atomicWriteJson(path44.join(artifactDir, "state.json"), value);
17020
17928
  }
17021
17929
  async function directorySize(root) {
17022
17930
  if (!existsSync13(root)) return 0;
17023
17931
  let total = 0;
17024
17932
  for (const entry of await readdir2(root, { withFileTypes: true })) {
17025
- const entryPath = path42.join(root, entry.name);
17933
+ const entryPath = path44.join(root, entry.name);
17026
17934
  if (entry.isDirectory()) total += await directorySize(entryPath);
17027
17935
  else total += (await lstat7(entryPath)).size;
17028
17936
  }
@@ -17067,9 +17975,9 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
17067
17975
  let rawManifest;
17068
17976
  let state;
17069
17977
  try {
17070
- rawManifest = JSON.parse(await readFile9(path42.join(artifactDir, "manifest.json"), "utf8"));
17978
+ rawManifest = JSON.parse(await readFile9(path44.join(artifactDir, "manifest.json"), "utf8"));
17071
17979
  state = JSON.parse(
17072
- await readFile9(path42.join(artifactDir, "state.json"), "utf8")
17980
+ await readFile9(path44.join(artifactDir, "state.json"), "utf8")
17073
17981
  );
17074
17982
  } catch {
17075
17983
  throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
@@ -17085,10 +17993,10 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
17085
17993
  }
17086
17994
  const entryPaths = /* @__PURE__ */ new Set();
17087
17995
  for (const entry of manifest.entries) {
17088
- 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))) {
17089
17997
  throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
17090
17998
  }
17091
- entryPaths.add(path42.normalize(entry.path));
17999
+ entryPaths.add(path44.normalize(entry.path));
17092
18000
  for (const [side, snapshot] of [
17093
18001
  ["before", entry.before],
17094
18002
  ["after", entry.after]
@@ -17102,7 +18010,7 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
17102
18010
  });
17103
18011
  }
17104
18012
  }
17105
- const receiptPath = path42.join(artifactDir, "receipt.json");
18013
+ const receiptPath = path44.join(artifactDir, "receipt.json");
17106
18014
  let receipt;
17107
18015
  if (["applied", "restoring", "restored", "conflict"].includes(state.state) || existsSync13(receiptPath)) {
17108
18016
  receipt = await readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash);
@@ -17112,7 +18020,7 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
17112
18020
  async function readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash) {
17113
18021
  let rawReceipt;
17114
18022
  try {
17115
- rawReceipt = JSON.parse(await readFile9(path42.join(artifactDir, "receipt.json"), "utf8"));
18023
+ rawReceipt = JSON.parse(await readFile9(path44.join(artifactDir, "receipt.json"), "utf8"));
17116
18024
  } catch {
17117
18025
  throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
17118
18026
  }
@@ -17135,7 +18043,7 @@ async function readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHas
17135
18043
  return receipt;
17136
18044
  }
17137
18045
  async function ensureRecoveryReceipt(artifactDir, manifest, manifestHash) {
17138
- const receiptPath = path42.join(artifactDir, "receipt.json");
18046
+ const receiptPath = path44.join(artifactDir, "receipt.json");
17139
18047
  if (existsSync13(receiptPath)) {
17140
18048
  return readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash);
17141
18049
  }
@@ -17160,7 +18068,7 @@ async function artifactRepoRoot(stateDir, checkpointId) {
17160
18068
  const artifactDir = checkpointDir(stateDir, checkpointId);
17161
18069
  try {
17162
18070
  const manifest = JSON.parse(
17163
- await readFile9(path42.join(artifactDir, "manifest.json"), "utf8")
18071
+ await readFile9(path44.join(artifactDir, "manifest.json"), "utf8")
17164
18072
  );
17165
18073
  if (typeof manifest.repoRoot === "string" && manifest.repoRoot) {
17166
18074
  return canonicalPath(manifest.repoRoot);
@@ -17168,7 +18076,7 @@ async function artifactRepoRoot(stateDir, checkpointId) {
17168
18076
  } catch {
17169
18077
  }
17170
18078
  try {
17171
- 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"));
17172
18080
  return typeof owner.repoRoot === "string" && owner.repoRoot ? canonicalPath(owner.repoRoot) : null;
17173
18081
  } catch {
17174
18082
  return null;
@@ -17188,10 +18096,10 @@ async function cleanupOrphanedStaging(stateDir) {
17188
18096
  const now = Date.now();
17189
18097
  for (const entry of await readdir2(root, { withFileTypes: true })) {
17190
18098
  if (!entry.isDirectory() || !/^\.tmp-cp_[a-f0-9]{24}$/.test(entry.name)) continue;
17191
- const stagingPath = path42.join(root, entry.name);
18099
+ const stagingPath = path44.join(root, entry.name);
17192
18100
  let stale = false;
17193
18101
  try {
17194
- 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"));
17195
18103
  const pid = typeof owner.pid === "number" ? owner.pid : Number.NaN;
17196
18104
  const createdAt = typeof owner.createdAt === "string" ? Date.parse(owner.createdAt) : NaN;
17197
18105
  let alive = false;
@@ -17233,7 +18141,7 @@ async function markRecoveryCheckpointApplied(stateDir, checkpoint) {
17233
18141
  init_fingerprint2();
17234
18142
  import { existsSync as existsSync14 } from "node:fs";
17235
18143
  import { readFile as readFile10 } from "node:fs/promises";
17236
- import path43 from "node:path";
18144
+ import path45 from "node:path";
17237
18145
  async function matchRecoverySide(resourceRoot, entries, side) {
17238
18146
  for (const entry of entries) {
17239
18147
  const target = await assertRecoverySafeTarget(resourceRoot, entry.path);
@@ -17248,7 +18156,7 @@ async function reconcileRecoveryCheckpoint(stateDir, checkpointId) {
17248
18156
  } catch {
17249
18157
  const artifactDir = checkpointDir(stateDir, checkpointId);
17250
18158
  if (existsSync14(artifactDir)) {
17251
- const manifestPath = path43.join(artifactDir, "manifest.json");
18159
+ const manifestPath = path45.join(artifactDir, "manifest.json");
17252
18160
  const hash = existsSync14(manifestPath) ? hashValue(await readFile10(manifestPath, "utf8")) : "unavailable";
17253
18161
  await writeRecoveryState(artifactDir, "corrupt", hash, RECOVERY_CHECKPOINT_CORRUPT);
17254
18162
  }
@@ -17285,7 +18193,7 @@ async function reconcileRecoveryCheckpoint(stateDir, checkpointId) {
17285
18193
  // src/core/recovery/resource-identity.ts
17286
18194
  init_fingerprint2();
17287
18195
  import { lstat as lstat8, readFile as readFile11, realpath as realpath3 } from "node:fs/promises";
17288
- import path44 from "node:path";
18196
+ import path46 from "node:path";
17289
18197
  async function currentRecoveryResourceIdentity(resourceRoot, resourceKind) {
17290
18198
  const resolvedRoot = await realpath3(resourceRoot);
17291
18199
  if (resourceKind === "directory") {
@@ -17293,13 +18201,13 @@ async function currentRecoveryResourceIdentity(resourceRoot, resourceKind) {
17293
18201
  if (!rootInfo.isDirectory()) throw new Error("recovery_repo_identity_unavailable");
17294
18202
  return hashValue(`${resolvedRoot}\0${rootInfo.dev}:${rootInfo.ino}:${rootInfo.birthtimeMs}`);
17295
18203
  }
17296
- const dotGit = path44.join(resolvedRoot, ".git");
18204
+ const dotGit = path46.join(resolvedRoot, ".git");
17297
18205
  const gitInfo = await lstat8(dotGit);
17298
18206
  let gitMetadataPath = dotGit;
17299
18207
  if (gitInfo.isFile()) {
17300
18208
  const marker = (await readFile11(dotGit, "utf8")).trim();
17301
18209
  if (!marker.startsWith("gitdir:")) throw new Error("recovery_repo_identity_unavailable");
17302
- gitMetadataPath = path44.resolve(resolvedRoot, marker.slice("gitdir:".length).trim());
18210
+ gitMetadataPath = path46.resolve(resolvedRoot, marker.slice("gitdir:".length).trim());
17303
18211
  } else if (!gitInfo.isDirectory()) {
17304
18212
  throw new Error("recovery_repo_identity_unavailable");
17305
18213
  }
@@ -17372,16 +18280,16 @@ async function prepareRecoveryCheckpoint(params) {
17372
18280
  throw new Error(RECOVERY_CHECKPOINT_QUOTA);
17373
18281
  }
17374
18282
  const checkpointId = `cp_${randomUUID5().replaceAll("-", "").slice(0, 24)}`;
17375
- const temporary = path45.join(checkpointsRoot(params.stateDir), `.tmp-${checkpointId}`);
18283
+ const temporary = path47.join(checkpointsRoot(params.stateDir), `.tmp-${checkpointId}`);
17376
18284
  const finalDir = checkpointDir(params.stateDir, checkpointId);
17377
18285
  await mkdir11(temporary, { recursive: true, mode: 448 });
17378
- await atomicWriteJson(path45.join(temporary, "owner.json"), {
18286
+ await atomicWriteJson(path47.join(temporary, "owner.json"), {
17379
18287
  version: 1,
17380
18288
  pid: process.pid,
17381
18289
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
17382
18290
  repoRoot: canonicalPath(params.repoRoot)
17383
18291
  });
17384
- await mkdir11(path45.join(temporary, "blobs"), { recursive: true, mode: 448 });
18292
+ await mkdir11(path47.join(temporary, "blobs"), { recursive: true, mode: 448 });
17385
18293
  try {
17386
18294
  const entries = [];
17387
18295
  const protectedRoots = (params.protectedRoots ?? []).map(canonicalPath);
@@ -17390,8 +18298,8 @@ async function prepareRecoveryCheckpoint(params) {
17390
18298
  )) {
17391
18299
  const target = await assertRecoverySafeTarget(params.repoRoot, change.relativePath);
17392
18300
  if (protectedRoots.some((root) => {
17393
- const relative = path45.relative(root, target);
17394
- 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);
17395
18303
  })) {
17396
18304
  throw new Error("recovery_protected_path");
17397
18305
  }
@@ -17403,7 +18311,7 @@ async function prepareRecoveryCheckpoint(params) {
17403
18311
  entries.push({
17404
18312
  path: change.relativePath,
17405
18313
  before: await captureRecoverySnapshot(baseline, {
17406
- blobDir: path45.join(temporary, "blobs")
18314
+ blobDir: path47.join(temporary, "blobs")
17407
18315
  }),
17408
18316
  after: withoutRecoveryBlob(await captureRecoverySnapshot(source))
17409
18317
  });
@@ -17440,7 +18348,7 @@ async function prepareRecoveryCheckpoint(params) {
17440
18348
  entries
17441
18349
  };
17442
18350
  const manifestHash = hashValue(canonicalStringify(manifest));
17443
- await atomicWriteJson(path45.join(temporary, "manifest.json"), manifest);
18351
+ await atomicWriteJson(path47.join(temporary, "manifest.json"), manifest);
17444
18352
  await writeRecoveryState(temporary, "prepared", manifestHash);
17445
18353
  await fsyncPath2(temporary);
17446
18354
  const projectedBytes = await recoveryCheckpointStorageBytes(params.stateDir, params.repoRoot);
@@ -17483,7 +18391,7 @@ async function listRecoveryCheckpoints(stateDir, repoRoot) {
17483
18391
  } catch {
17484
18392
  try {
17485
18393
  const raw = JSON.parse(
17486
- await readFile12(path45.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
18394
+ await readFile12(path47.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
17487
18395
  );
17488
18396
  rootFromArtifact = typeof raw.repoRoot === "string" && raw.repoRoot ? raw.repoRoot : void 0;
17489
18397
  } catch {
@@ -17517,7 +18425,7 @@ async function listRecoveryCheckpoints(stateDir, repoRoot) {
17517
18425
  } catch {
17518
18426
  try {
17519
18427
  const manifest = JSON.parse(
17520
- await readFile12(path45.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
18428
+ await readFile12(path47.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
17521
18429
  );
17522
18430
  if (manifest.checkpointId !== id || ![1, 2].includes(manifest.version)) continue;
17523
18431
  if (repoRoot && canonicalPath(manifest.repoRoot) !== canonicalPath(repoRoot)) continue;
@@ -17547,7 +18455,7 @@ async function recoveryCheckpointStorageBytes(stateDir, repoRoot) {
17547
18455
  let total = 0;
17548
18456
  for (const entry of await readdir3(root, { withFileTypes: true })) {
17549
18457
  if (!entry.isDirectory()) continue;
17550
- const entryPath = path45.join(root, entry.name);
18458
+ const entryPath = path47.join(root, entry.name);
17551
18459
  if (/^cp_[a-f0-9]{24}$/.test(entry.name)) {
17552
18460
  if (await artifactRepoRoot(stateDir, entry.name) === expected) {
17553
18461
  total += await directorySize(entryPath);
@@ -17556,7 +18464,7 @@ async function recoveryCheckpointStorageBytes(stateDir, repoRoot) {
17556
18464
  }
17557
18465
  if (/^\.tmp-cp_[a-f0-9]{24}$/.test(entry.name)) {
17558
18466
  try {
17559
- 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"));
17560
18468
  if (typeof owner.repoRoot === "string" && canonicalPath(owner.repoRoot) === expected) {
17561
18469
  total += await directorySize(entryPath);
17562
18470
  }
@@ -17600,14 +18508,14 @@ init_scrub();
17600
18508
  // src/core/transactional/file-checkpoint-backend.ts
17601
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";
17602
18510
  import os5 from "node:os";
17603
- import path49 from "node:path";
18511
+ import path51 from "node:path";
17604
18512
 
17605
18513
  // src/core/transactional/file-checkpoint-git.ts
17606
18514
  init_path_utils();
17607
18515
  import { spawn as spawn8 } from "node:child_process";
17608
18516
  import { createHash as createHash13 } from "node:crypto";
17609
18517
  import { copyFile as copyFile3, lstat as lstat9, readdir as readdir4, readFile as readFile13 } from "node:fs/promises";
17610
- import path46 from "node:path";
18518
+ import path48 from "node:path";
17611
18519
  var FILE_CHECKPOINT_GIT_METADATA_CHANGED = "file_checkpoint_git_metadata_changed";
17612
18520
  var FILE_CHECKPOINT_SOURCE_CHANGED = "file_checkpoint_source_changed";
17613
18521
  var FILE_CHECKPOINT_CWD_OUTSIDE_ROOT = "file_checkpoint_cwd_outside_root";
@@ -17641,7 +18549,7 @@ function rethrowStableFileCheckpointError(error) {
17641
18549
  }
17642
18550
  async function rootGitMetadataPresent(repoRoot) {
17643
18551
  try {
17644
- await lstat9(path46.join(repoRoot, ".git"));
18552
+ await lstat9(path48.join(repoRoot, ".git"));
17645
18553
  return true;
17646
18554
  } catch {
17647
18555
  return false;
@@ -17690,10 +18598,10 @@ function execGit2(repoRoot, args) {
17690
18598
  }
17691
18599
  async function resolveGitPath(repoRoot, gitPath) {
17692
18600
  const trimmed = gitPath.trim();
17693
- if (path46.isAbsolute(trimmed)) {
18601
+ if (path48.isAbsolute(trimmed)) {
17694
18602
  return trimmed;
17695
18603
  }
17696
- return path46.join(repoRoot, trimmed);
18604
+ return path48.join(repoRoot, trimmed);
17697
18605
  }
17698
18606
  async function cloneBareWorktreeCopy(sourceRoot, destinationRoot) {
17699
18607
  await execGit2(sourceRoot, [
@@ -17735,7 +18643,7 @@ async function copyGitIndexState(sourceRoot, destinationRoot) {
17735
18643
  destinationRoot,
17736
18644
  await execGit2(destinationRoot, ["rev-parse", "--git-dir"])
17737
18645
  );
17738
- const destinationShared = path46.join(destinationGitDir, path46.basename(sourceShared));
18646
+ const destinationShared = path48.join(destinationGitDir, path48.basename(sourceShared));
17739
18647
  try {
17740
18648
  await copyFile3(sourceShared, destinationShared);
17741
18649
  } catch (error) {
@@ -17744,7 +18652,7 @@ async function copyGitIndexState(sourceRoot, destinationRoot) {
17744
18652
  }
17745
18653
  async function readGitFile(gitDir, relativePath) {
17746
18654
  try {
17747
- return await readFile13(path46.join(gitDir, relativePath));
18655
+ return await readFile13(path48.join(gitDir, relativePath));
17748
18656
  } catch {
17749
18657
  return null;
17750
18658
  }
@@ -17768,8 +18676,8 @@ async function hashResolvedGitPath(repoRoot, gitDir, gitPath, hash) {
17768
18676
  repoRoot,
17769
18677
  await execGit2(repoRoot, ["rev-parse", "--git-path", gitPath])
17770
18678
  );
17771
- const relative = path46.resolve(resolved).startsWith(path46.resolve(gitDir)) ? path46.relative(gitDir, resolved) : resolved;
17772
- 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);
17773
18681
  if (content !== null) {
17774
18682
  hashGitFileContent(hash, gitPath, content);
17775
18683
  }
@@ -17779,13 +18687,13 @@ async function hashResolvedGitPath(repoRoot, gitDir, gitPath, hash) {
17779
18687
  async function hashGitTree(gitDir, relativeDir, hash) {
17780
18688
  let names;
17781
18689
  try {
17782
- names = await readdir4(path46.join(gitDir, relativeDir));
18690
+ names = await readdir4(path48.join(gitDir, relativeDir));
17783
18691
  } catch {
17784
18692
  return;
17785
18693
  }
17786
18694
  for (const name of names.sort()) {
17787
- const relativePath = relativeDir ? path46.join(relativeDir, name) : name;
17788
- const absolutePath = path46.join(gitDir, relativePath);
18695
+ const relativePath = relativeDir ? path48.join(relativeDir, name) : name;
18696
+ const absolutePath = path48.join(gitDir, relativePath);
17789
18697
  let childNames = null;
17790
18698
  try {
17791
18699
  childNames = await readdir4(absolutePath);
@@ -17807,7 +18715,7 @@ async function hashGitTree(gitDir, relativeDir, hash) {
17807
18715
  }
17808
18716
  async function computeGitMetadataFingerprint(repoRoot) {
17809
18717
  const gitDirRel = (await execGit2(repoRoot, ["rev-parse", "--git-dir"])).trim();
17810
- const gitDir = path46.isAbsolute(gitDirRel) ? gitDirRel : path46.join(repoRoot, gitDirRel);
18718
+ const gitDir = path48.isAbsolute(gitDirRel) ? gitDirRel : path48.join(repoRoot, gitDirRel);
17811
18719
  const hash = createHash13("sha256");
17812
18720
  for (const file of [
17813
18721
  "HEAD",
@@ -17830,8 +18738,8 @@ async function computeGitMetadataFingerprint(repoRoot) {
17830
18738
  const sharedIndex = (await execGit2(repoRoot, ["rev-parse", "--shared-index-path"])).trim();
17831
18739
  if (sharedIndex) {
17832
18740
  const resolved = await resolveGitPath(repoRoot, sharedIndex);
17833
- const relative = path46.relative(gitDir, resolved);
17834
- 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);
17835
18743
  if (content !== null) {
17836
18744
  hashGitFileContent(hash, "shared-index", content);
17837
18745
  }
@@ -17842,7 +18750,7 @@ async function computeGitMetadataFingerprint(repoRoot) {
17842
18750
  await hashResolvedGitPath(repoRoot, gitDir, gitPath, hash);
17843
18751
  }
17844
18752
  try {
17845
- const rootGitPath = path46.join(repoRoot, ".git");
18753
+ const rootGitPath = path48.join(repoRoot, ".git");
17846
18754
  const rootGitInfo = await lstat9(rootGitPath);
17847
18755
  if (rootGitInfo.isFile()) {
17848
18756
  const content = await readAbsoluteGitFile(rootGitPath);
@@ -17858,14 +18766,14 @@ async function computeGitMetadataFingerprint(repoRoot) {
17858
18766
  function resolveExecutionCwdRelative(resourceRoot, cwd) {
17859
18767
  const resolvedCwd = canonicalPath(cwd);
17860
18768
  const resourceCanonical = canonicalPath(resourceRoot);
17861
- const relative = path46.relative(resourceCanonical, resolvedCwd);
18769
+ const relative = path48.relative(resourceCanonical, resolvedCwd);
17862
18770
  if (relative === "" || relative === ".") {
17863
18771
  return "";
17864
18772
  }
17865
- if (relative.startsWith("..") || path46.isAbsolute(relative)) {
18773
+ if (relative.startsWith("..") || path48.isAbsolute(relative)) {
17866
18774
  throw new Error(FILE_CHECKPOINT_CWD_OUTSIDE_ROOT);
17867
18775
  }
17868
- return relative.split(path46.sep).join("/");
18776
+ return relative.split(path48.sep).join("/");
17869
18777
  }
17870
18778
 
17871
18779
  // src/core/transactional/file-checkpoint-isolation.ts
@@ -17887,7 +18795,7 @@ function fileCheckpointIsolationReason(context) {
17887
18795
 
17888
18796
  // src/core/transactional/file-checkpoint-staging.ts
17889
18797
  import { readdir as readdir5, readFile as readFile14, rm as rm7, writeFile as writeFile8 } from "node:fs/promises";
17890
- import path47 from "node:path";
18798
+ import path49 from "node:path";
17891
18799
  function isOwnerProcessAlive(pid) {
17892
18800
  try {
17893
18801
  process.kill(pid, 0);
@@ -17897,12 +18805,12 @@ function isOwnerProcessAlive(pid) {
17897
18805
  }
17898
18806
  }
17899
18807
  async function writeOwnerMarker(stagingRoot, marker) {
17900
- await writeFile8(path47.join(stagingRoot, "owner.json"), `${JSON.stringify(marker)}
18808
+ await writeFile8(path49.join(stagingRoot, "owner.json"), `${JSON.stringify(marker)}
17901
18809
  `, "utf8");
17902
18810
  }
17903
18811
  async function readOwnerMarker(stagingRoot) {
17904
18812
  try {
17905
- const raw = await readFile14(path47.join(stagingRoot, "owner.json"), "utf8");
18813
+ const raw = await readFile14(path49.join(stagingRoot, "owner.json"), "utf8");
17906
18814
  return JSON.parse(raw.trim());
17907
18815
  } catch {
17908
18816
  return null;
@@ -17920,7 +18828,7 @@ async function collectDeadOwnerStaging(parentDir) {
17920
18828
  if (!name.startsWith("belay-file-checkpoint-")) {
17921
18829
  continue;
17922
18830
  }
17923
- const stagingRoot = path47.join(parentDir, name);
18831
+ const stagingRoot = path49.join(parentDir, name);
17924
18832
  const marker = await readOwnerMarker(stagingRoot);
17925
18833
  if (!marker) {
17926
18834
  dead.push(stagingRoot);
@@ -17952,7 +18860,7 @@ import {
17952
18860
  writeFile as writeFile9
17953
18861
  } from "node:fs/promises";
17954
18862
  import os4 from "node:os";
17955
- import path48 from "node:path";
18863
+ import path50 from "node:path";
17956
18864
  var FILE_CHECKPOINT_COPY_FAILED = "file_checkpoint_copy_failed";
17957
18865
  async function chmodSafe2(target, mode) {
17958
18866
  try {
@@ -17962,7 +18870,7 @@ async function chmodSafe2(target, mode) {
17962
18870
  }
17963
18871
  }
17964
18872
  async function copyRegularFile(sourcePath, destinationPath, mode, strategy) {
17965
- await mkdir12(path48.dirname(destinationPath), { recursive: true });
18873
+ await mkdir12(path50.dirname(destinationPath), { recursive: true });
17966
18874
  if (strategy === "clonefile" && fsConstants2.COPYFILE_FICLONE !== void 0) {
17967
18875
  try {
17968
18876
  await copyFile4(sourcePath, destinationPath, fsConstants2.COPYFILE_FICLONE);
@@ -17988,7 +18896,7 @@ async function copyNode(sourceRoot, destinationRoot, relativePath, strategy) {
17988
18896
  return strategy;
17989
18897
  }
17990
18898
  if (info.isSymbolicLink()) {
17991
- await mkdir12(path48.dirname(destinationPath), { recursive: true });
18899
+ await mkdir12(path50.dirname(destinationPath), { recursive: true });
17992
18900
  await symlink4(await readlink5(sourcePath), destinationPath);
17993
18901
  return strategy;
17994
18902
  }
@@ -18044,9 +18952,9 @@ async function mapWithConcurrency(items, concurrency, worker) {
18044
18952
  async function probeFileCloneStrategy() {
18045
18953
  let tempDir = null;
18046
18954
  try {
18047
- tempDir = await mkdtemp4(path48.join(os4.tmpdir(), "belay-clone-probe-"));
18048
- const source = path48.join(tempDir, "source.txt");
18049
- 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");
18050
18958
  await writeFile9(source, "probe\n");
18051
18959
  if (fsConstants2.COPYFILE_FICLONE_FORCE !== void 0) {
18052
18960
  try {
@@ -18199,11 +19107,11 @@ async function protectedRootState(root) {
18199
19107
  return `directory:${node.hash}:${index.treeHash}`;
18200
19108
  }
18201
19109
  function executionProtectedRoot(resourceRoot, executionRoot, protectedRoot) {
18202
- const relative = path49.relative(path49.resolve(resourceRoot), path49.resolve(protectedRoot));
18203
- 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)) {
18204
19112
  return null;
18205
19113
  }
18206
- return path49.join(executionRoot, relative);
19114
+ return path51.join(executionRoot, relative);
18207
19115
  }
18208
19116
  async function captureProtectedRootStates(resourceRoot, executionRoot, protectedRoots) {
18209
19117
  const states = /* @__PURE__ */ new Map();
@@ -18228,15 +19136,15 @@ async function directoryByteSize(root, deadlineMs) {
18228
19136
  }
18229
19137
  let total = 0;
18230
19138
  for (const name of await readdir6(root)) {
18231
- total += await directoryByteSize(path49.join(root, name), deadlineMs);
19139
+ total += await directoryByteSize(path51.join(root, name), deadlineMs);
18232
19140
  }
18233
19141
  return total;
18234
19142
  }
18235
19143
  async function copyGitMetadataDirectory(sourceRoot, destinationRoot) {
18236
19144
  const gitDirRel = (await execGit2(sourceRoot, ["rev-parse", "--git-dir"])).trim();
18237
- const sourceGitDir = path49.isAbsolute(gitDirRel) ? gitDirRel : path49.join(sourceRoot, gitDirRel);
18238
- const relativeGitDir = path49.relative(path49.resolve(sourceRoot), path49.resolve(sourceGitDir));
18239
- 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");
18240
19148
  await cp(sourceGitDir, destinationGitDir, { recursive: true, force: true });
18241
19149
  }
18242
19150
  async function prepareDirtyGitSnapshot(context) {
@@ -18245,7 +19153,7 @@ async function prepareDirtyGitSnapshot(context) {
18245
19153
  const quotas = context.fileCheckpoint;
18246
19154
  const deadlineMs = Date.now() + quotas.prepareTimeoutMs;
18247
19155
  await removeDeadOwnerStaging(os5.tmpdir());
18248
- const stagingRoot = await mkdtemp5(path49.join(os5.tmpdir(), "belay-file-checkpoint-"));
19156
+ const stagingRoot = await mkdtemp5(path51.join(os5.tmpdir(), "belay-file-checkpoint-"));
18249
19157
  await writeOwnerMarker(stagingRoot, {
18250
19158
  version: 1,
18251
19159
  pid: process.pid,
@@ -18253,8 +19161,8 @@ async function prepareDirtyGitSnapshot(context) {
18253
19161
  resourceRoot: context.repoRoot,
18254
19162
  backend: "file_checkpoint"
18255
19163
  });
18256
- const baselineRoot = path49.join(stagingRoot, "baseline");
18257
- const executionRoot = path49.join(stagingRoot, "execution");
19164
+ const baselineRoot = path51.join(stagingRoot, "baseline");
19165
+ const executionRoot = path51.join(stagingRoot, "execution");
18258
19166
  try {
18259
19167
  resolveExecutionCwdRelative(context.repoRoot, context.cwd);
18260
19168
  const sourceGitMetadataFingerprint = await computeGitMetadataFingerprint(context.repoRoot);
@@ -18288,7 +19196,7 @@ async function prepareDirtyGitSnapshot(context) {
18288
19196
  throw new Error(FILE_CHECKPOINT_SOURCE_CHANGED);
18289
19197
  }
18290
19198
  await writeFile10(
18291
- path49.join(stagingRoot, "baseline-index.json"),
19199
+ path51.join(stagingRoot, "baseline-index.json"),
18292
19200
  `${JSON.stringify(baselineIndex)}
18293
19201
  `,
18294
19202
  "utf8"
@@ -18338,7 +19246,7 @@ async function prepareNonGitSnapshot(context) {
18338
19246
  const quotas = context.fileCheckpoint;
18339
19247
  const deadlineMs = Date.now() + quotas.prepareTimeoutMs;
18340
19248
  await removeDeadOwnerStaging(os5.tmpdir());
18341
- const stagingRoot = await mkdtemp5(path49.join(os5.tmpdir(), "belay-file-checkpoint-"));
19249
+ const stagingRoot = await mkdtemp5(path51.join(os5.tmpdir(), "belay-file-checkpoint-"));
18342
19250
  await writeOwnerMarker(stagingRoot, {
18343
19251
  version: 1,
18344
19252
  pid: process.pid,
@@ -18346,8 +19254,8 @@ async function prepareNonGitSnapshot(context) {
18346
19254
  resourceRoot: context.repoRoot,
18347
19255
  backend: "file_checkpoint"
18348
19256
  });
18349
- const baselineRoot = path49.join(stagingRoot, "baseline");
18350
- const executionRoot = path49.join(stagingRoot, "execution");
19257
+ const baselineRoot = path51.join(stagingRoot, "baseline");
19258
+ const executionRoot = path51.join(stagingRoot, "execution");
18351
19259
  try {
18352
19260
  resolveExecutionCwdRelative(context.repoRoot, context.cwd);
18353
19261
  const resourceIdentity = await currentRecoveryResourceIdentity(context.repoRoot, "directory");
@@ -18376,7 +19284,7 @@ async function prepareNonGitSnapshot(context) {
18376
19284
  throw new Error(FILE_CHECKPOINT_SOURCE_CHANGED);
18377
19285
  }
18378
19286
  await writeFile10(
18379
- path49.join(stagingRoot, "baseline-index.json"),
19287
+ path51.join(stagingRoot, "baseline-index.json"),
18380
19288
  `${JSON.stringify(baselineIndex)}
18381
19289
  `,
18382
19290
  "utf8"
@@ -18729,10 +19637,10 @@ async function selectTransactionalBackend(context) {
18729
19637
  }
18730
19638
 
18731
19639
  // src/core/transactional/diff-evaluator.ts
18732
- import path50 from "node:path";
19640
+ import path52 from "node:path";
18733
19641
  init_path_utils();
18734
19642
  function categorizeChange(change, ctx) {
18735
- const absolutePath = canonicalPath(path50.join(ctx.repoRoot, change.relativePath));
19643
+ const absolutePath = canonicalPath(path52.join(ctx.repoRoot, change.relativePath));
18736
19644
  if (!pathWithinRoot(ctx.repoRoot, absolutePath)) {
18737
19645
  return "repo_outside";
18738
19646
  }
@@ -19326,7 +20234,7 @@ async function notifyDeny(config, event) {
19326
20234
  init_path_utils();
19327
20235
 
19328
20236
  // src/adapters/layouts/protected-paths.ts
19329
- import path51 from "node:path";
20237
+ import path53 from "node:path";
19330
20238
  function protectedArtifactRoots(layout, repoRoot, controlPlaneDir) {
19331
20239
  const roots = [
19332
20240
  layout.configPath(repoRoot),
@@ -19338,7 +20246,7 @@ function protectedArtifactRoots(layout, repoRoot, controlPlaneDir) {
19338
20246
  if (controlPlaneDir) {
19339
20247
  roots.push(controlPlaneDir);
19340
20248
  }
19341
- return roots.map((entry) => path51.resolve(entry));
20249
+ return roots.map((entry) => path53.resolve(entry));
19342
20250
  }
19343
20251
 
19344
20252
  // src/adapters/shared/gate-runtime.ts
@@ -19391,8 +20299,8 @@ function createDefaultGateRuntimeDeps() {
19391
20299
  return loadJsonFile(configPath, {});
19392
20300
  },
19393
20301
  async appendAudit(ctx, event) {
19394
- const auditPath = path52.join(ctx.repoRoot, ctx.config.audit.logPath);
19395
- 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 });
19396
20304
  const provenance = auditProvenance(ctx.config);
19397
20305
  const record = {
19398
20306
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -19425,7 +20333,7 @@ function createDefaultGateRuntimeDeps() {
19425
20333
  };
19426
20334
  },
19427
20335
  async writeApprovals(filePath, state) {
19428
- await mkdir14(path52.dirname(filePath), { recursive: true });
20336
+ await mkdir14(path54.dirname(filePath), { recursive: true });
19429
20337
  await writeFile11(filePath, `${JSON.stringify(compactApprovals(state), null, 2)}
19430
20338
  `, "utf8");
19431
20339
  },
@@ -19587,7 +20495,7 @@ function deriveWorkspaceRootScopeHint(params) {
19587
20495
  if (!targetPath) {
19588
20496
  return void 0;
19589
20497
  }
19590
- const candidateRoot = canonicalPath(path52.dirname(targetPath));
20498
+ const candidateRoot = canonicalPath(path54.dirname(targetPath));
19591
20499
  const validation = validateTrustedWorkspaceRootCandidate({
19592
20500
  candidatePath: candidateRoot,
19593
20501
  repoRoot: action.repoRoot,
@@ -19921,6 +20829,7 @@ async function evaluateGatedAction(ctx, deps, params) {
19921
20829
  event: resolveGateAuditEvent(sourceEvent, params.kind),
19922
20830
  sourceEvent,
19923
20831
  kind: params.kind,
20832
+ ...typeof params.payload?.tool_use_id === "string" ? { toolInvocationCorrelationId: toolInvocationCorrelationId(params.payload.tool_use_id) } : {},
19924
20833
  fingerprint: verdict2.fingerprint,
19925
20834
  verdict: verdict2.verdict,
19926
20835
  reason: verdict2.reason,
@@ -20078,6 +20987,7 @@ async function evaluateGatedAction(ctx, deps, params) {
20078
20987
  const scrubbedPayload = fingerprintReplayPayload(params.kind, params.payload, scrubOpts);
20079
20988
  return gateDecisionToVerdict(ctx, deps, params.kind, result, {
20080
20989
  sourceEvent: params.sourceEvent,
20990
+ toolInvocationCorrelationId: typeof params.payload?.tool_use_id === "string" ? toolInvocationCorrelationId(params.payload.tool_use_id) : void 0,
20081
20991
  predictedAssessment,
20082
20992
  observedAssessment: observedAssessment2,
20083
20993
  transactionalLayer,
@@ -20161,6 +21071,7 @@ async function gateDecisionToVerdict(ctx, deps, kind, result, auditExtras = {})
20161
21071
  event: auditEvent,
20162
21072
  sourceEvent,
20163
21073
  kind,
21074
+ ...auditExtras.toolInvocationCorrelationId ? { toolInvocationCorrelationId: auditExtras.toolInvocationCorrelationId } : {},
20164
21075
  fingerprint: result.fingerprint,
20165
21076
  summary: result.normalizedCommand ?? result.summary ?? "",
20166
21077
  assessment: result.assessment,
@@ -20607,29 +21518,56 @@ function gateVerdictToClaudeUserPromptResponse(verdict2) {
20607
21518
  };
20608
21519
  }
20609
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);
20610
21523
  await deps.appendAudit(ctx, {
20611
21524
  event: eventName,
20612
21525
  kind: "audit",
20613
21526
  verdict: "allow",
20614
21527
  reason: "observed",
20615
- 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)
20616
21535
  });
20617
21536
  }
20618
21537
 
20619
21538
  // src/adapters/shared/repo-root.ts
20620
21539
  import { existsSync as existsSync17 } from "node:fs";
20621
- 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
+ }
20622
21560
  function findRepoRoot(startPath, layout) {
20623
- let current = path53.resolve(startPath);
21561
+ let current = path55.resolve(startPath);
20624
21562
  while (true) {
20625
21563
  for (const marker of layout.repoRootMarkers) {
20626
- if (existsSync17(path53.join(current, marker))) {
21564
+ if (markerMatches(current, marker, layout)) {
20627
21565
  return current;
20628
21566
  }
20629
21567
  }
20630
- const parent = path53.dirname(current);
21568
+ const parent = path55.dirname(current);
20631
21569
  if (parent === current) {
20632
- return path53.resolve(startPath);
21570
+ return path55.resolve(startPath);
20633
21571
  }
20634
21572
  current = parent;
20635
21573
  }