@guilz-dev/belay 0.9.2 → 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +1 -1
  2. package/dist/adapters/codex/runtime-entry.d.ts +3 -0
  3. package/dist/adapters/codex/runtime-entry.js +27 -4
  4. package/dist/adapters/cursor/cwd-resolution.d.ts +10 -0
  5. package/dist/adapters/cursor/cwd-resolution.js +58 -0
  6. package/dist/adapters/cursor/hooks.d.ts +1 -0
  7. package/dist/adapters/cursor/hooks.js +21 -0
  8. package/dist/adapters/cursor/runtime-entry.d.ts +1 -0
  9. package/dist/adapters/cursor/runtime-entry.js +107 -6
  10. package/dist/adapters/shared/gate-runtime.js +26 -3
  11. package/dist/adapters/shared/repo-root.js +20 -1
  12. package/dist/bundle/claude-runtime.mjs +855 -338
  13. package/dist/bundle/codex-runtime.mjs +877 -342
  14. package/dist/bundle/cursor-runtime.mjs +3886 -3141
  15. package/dist/cli.js +33 -3
  16. package/dist/commands/doctor.js +24 -1
  17. package/dist/commands/health-snapshot.d.ts +3 -0
  18. package/dist/commands/health-snapshot.js +56 -0
  19. package/dist/commands/report.js +14 -0
  20. package/dist/commands/status.js +15 -0
  21. package/dist/commands/where.d.ts +4 -0
  22. package/dist/commands/where.js +52 -0
  23. package/dist/core/approval-repo-lookup.d.ts +16 -0
  24. package/dist/core/approval-repo-lookup.js +48 -0
  25. package/dist/core/audit-io.d.ts +1 -1
  26. package/dist/core/audit-io.js +1 -1
  27. package/dist/core/audit-query.d.ts +1 -0
  28. package/dist/core/audit-query.js +7 -0
  29. package/dist/core/audit-serialize.d.ts +3 -0
  30. package/dist/core/audit-serialize.js +39 -3
  31. package/dist/core/audit-summary.d.ts +9 -0
  32. package/dist/core/audit-summary.js +58 -1
  33. package/dist/core/audit-types.d.ts +4 -0
  34. package/dist/core/effect-ir/shell-lower.js +93 -40
  35. package/dist/core/replay-scrub.d.ts +1 -0
  36. package/dist/core/replay-scrub.js +22 -3
  37. package/dist/core/shell-tokenizer.d.ts +28 -0
  38. package/dist/core/shell-tokenizer.js +111 -29
  39. package/dist/core/verdict/docker-compose-run.d.ts +18 -0
  40. package/dist/core/verdict/docker-compose-run.js +136 -0
  41. package/dist/core/verdict/launcher-resolve.js +28 -28
  42. package/dist/core/verdict/parser.d.ts +3 -0
  43. package/dist/core/verdict/parser.js +23 -40
  44. package/dist/core/verdict/recursive-invocation.d.ts +20 -0
  45. package/dist/core/verdict/recursive-invocation.js +224 -0
  46. package/dist/defaults.js +7 -0
  47. package/dist/installer/scope-config.d.ts +2 -2
  48. package/dist/installer.d.ts +10 -1
  49. package/dist/installer.js +43 -2
  50. package/dist/types.d.ts +34 -1
  51. package/dist/version.d.ts +1 -1
  52. package/dist/version.js +1 -1
  53. package/package.json +5 -2
  54. package/skills/belay/SKILL.md +5 -0
  55. package/skills/belay/belay-report.md +4 -1
@@ -824,6 +824,22 @@ import { createHash } from "node:crypto";
824
824
  function approvalCorrelationId(approvalId) {
825
825
  return createHash("sha256").update(approvalId).digest("hex").slice(0, 16);
826
826
  }
827
+ function canonicalToolUseIdForCorrelation(toolUseId) {
828
+ const trimmed = toolUseId.trim();
829
+ if (trimmed.startsWith("tool_")) {
830
+ const remainder = trimmed.slice("tool_".length);
831
+ if (TOOL_USE_UUID_PATTERN.test(remainder)) {
832
+ return remainder.toLowerCase();
833
+ }
834
+ }
835
+ if (TOOL_USE_UUID_PATTERN.test(trimmed)) {
836
+ return trimmed.toLowerCase();
837
+ }
838
+ return trimmed;
839
+ }
840
+ function toolInvocationCorrelationId(toolUseId) {
841
+ return createHash("sha256").update(canonicalToolUseIdForCorrelation(toolUseId)).digest("hex").slice(0, 16);
842
+ }
827
843
  function isValidApprovalCorrelationId(value) {
828
844
  return /^[a-f0-9]{16}$/.test(value);
829
845
  }
@@ -846,7 +862,16 @@ function isValidPreservedHashField(field, value) {
846
862
  return isValidAuditFingerprint(value);
847
863
  }
848
864
  function scrubAuditContainer(value, options) {
849
- return scrubValue(value, {
865
+ const withoutRawToolIds = (input) => {
866
+ if (Array.isArray(input)) return input.map(withoutRawToolIds);
867
+ if (input && typeof input === "object") {
868
+ return Object.fromEntries(
869
+ Object.entries(input).filter(([key]) => key !== "tool_use_id").map(([key, child]) => [key, withoutRawToolIds(child)])
870
+ );
871
+ }
872
+ return input;
873
+ };
874
+ return scrubValue(withoutRawToolIds(value), {
850
875
  ...options,
851
876
  maskHighEntropyStrings: true
852
877
  });
@@ -865,7 +890,7 @@ function serializeAuditField(key, value, options) {
865
890
  if (key === "timestamp" && typeof value === "string" && isValidAuditTimestamp(value)) {
866
891
  return value;
867
892
  }
868
- if (key === "approvalCorrelationId" && typeof value === "string" && isValidApprovalCorrelationId(value)) {
893
+ if ((key === "approvalCorrelationId" || key === "toolInvocationCorrelationId") && typeof value === "string" && isValidApprovalCorrelationId(value)) {
869
894
  return value;
870
895
  }
871
896
  if ((key === "runtimeVersion" || key === "runtimeBuildStamp" || key === "boundaryProfile") && typeof value === "string" && value.length > 0) {
@@ -917,7 +942,7 @@ function serializeAuditRecordV3(record, options) {
917
942
  serialized.approvalCorrelationId = record.approvalCorrelationId;
918
943
  }
919
944
  for (const [key, value] of Object.entries(record)) {
920
- if (key === "timestamp" || key === "ts" || key === "approvalId" || key === "schemaVersion") {
945
+ if (key === "timestamp" || key === "ts" || key === "approvalId" || key === "tool_use_id" || key === "schemaVersion") {
921
946
  continue;
922
947
  }
923
948
  const next = serializeAuditField(key, value, options);
@@ -927,7 +952,7 @@ function serializeAuditRecordV3(record, options) {
927
952
  }
928
953
  return serialized;
929
954
  }
930
- var AUDIT_SCHEMA_VERSION, ISO8601_PATTERN, HEX64_PATTERN, SCRUB_PLACEHOLDERS, PRESERVED_HASH_FIELDS, PRESERVED_LITERAL_FIELDS, SCRUBBED_CONTAINER_FIELDS;
955
+ var AUDIT_SCHEMA_VERSION, ISO8601_PATTERN, HEX64_PATTERN, SCRUB_PLACEHOLDERS, PRESERVED_HASH_FIELDS, PRESERVED_LITERAL_FIELDS, SCRUBBED_CONTAINER_FIELDS, TOOL_USE_UUID_PATTERN;
931
956
  var init_audit_serialize = __esm({
932
957
  "src/core/audit-serialize.ts"() {
933
958
  "use strict";
@@ -949,6 +974,7 @@ var init_audit_serialize = __esm({
949
974
  PRESERVED_LITERAL_FIELDS = /* @__PURE__ */ new Set([
950
975
  "timestamp",
951
976
  "approvalCorrelationId",
977
+ "toolInvocationCorrelationId",
952
978
  "runtimeVersion",
953
979
  "runtimeBuildStamp",
954
980
  "boundaryProfile",
@@ -969,6 +995,7 @@ var init_audit_serialize = __esm({
969
995
  "predictedAssessment",
970
996
  "observedAssessment"
971
997
  ]);
998
+ 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;
972
999
  }
973
1000
  });
974
1001
 
@@ -2209,60 +2236,135 @@ function isRedirectOperator(token) {
2209
2236
  function isFdDuplication(token) {
2210
2237
  return FD_DUPLICATION_PATTERN.test(token);
2211
2238
  }
2212
- function tokenizeShell(input) {
2239
+ function lexShell(input) {
2213
2240
  const tokens = [];
2214
- let buffer = "";
2241
+ let value = "";
2242
+ let wordStart = null;
2243
+ let parts = [];
2215
2244
  let quote = null;
2216
- let escaping = false;
2217
- const flush = () => {
2218
- if (buffer.length > 0) {
2219
- tokens.push(buffer);
2220
- buffer = "";
2245
+ let quoteStart = -1;
2246
+ let quoteHadContent = false;
2247
+ let complete = true;
2248
+ const startWord = (index) => {
2249
+ wordStart ??= index;
2250
+ };
2251
+ const append = (decoded, start, end, mode, hasExpansion) => {
2252
+ startWord(start);
2253
+ value += decoded;
2254
+ const previous = parts.at(-1);
2255
+ if (previous && previous.quote === mode && previous.hasExpansion === hasExpansion && previous.end === start) {
2256
+ previous.value += decoded;
2257
+ previous.raw += input.slice(start, end);
2258
+ previous.end = end;
2259
+ return;
2221
2260
  }
2261
+ parts.push({
2262
+ value: decoded,
2263
+ raw: input.slice(start, end),
2264
+ start,
2265
+ end,
2266
+ quote: mode,
2267
+ hasExpansion
2268
+ });
2269
+ };
2270
+ const flushWord = (end) => {
2271
+ if (wordStart === null) return;
2272
+ tokens.push({
2273
+ kind: "word",
2274
+ value,
2275
+ raw: input.slice(wordStart, end),
2276
+ start: wordStart,
2277
+ end,
2278
+ parts
2279
+ });
2280
+ value = "";
2281
+ wordStart = null;
2282
+ parts = [];
2283
+ };
2284
+ const pushOperator = (token, start, end) => {
2285
+ tokens.push({ kind: "operator", value: token, raw: input.slice(start, end), start, end });
2222
2286
  };
2223
2287
  for (let index = 0; index < input.length; index += 1) {
2224
- const char = input[index];
2225
- if (escaping) {
2226
- buffer += char;
2227
- escaping = false;
2228
- continue;
2229
- }
2230
- if (char === "\\") {
2231
- escaping = true;
2288
+ const char = input[index] ?? "";
2289
+ if (quote === "single") {
2290
+ if (char === "'") {
2291
+ if (!quoteHadContent) append("", quoteStart, index + 1, "single", false);
2292
+ quote = null;
2293
+ } else {
2294
+ append(char, index, index + 1, "single", false);
2295
+ quoteHadContent = true;
2296
+ }
2232
2297
  continue;
2233
2298
  }
2234
- if (quote) {
2235
- if (char === quote) {
2299
+ if (quote === "double") {
2300
+ if (char === '"') {
2301
+ if (!quoteHadContent) append("", quoteStart, index + 1, "double", false);
2236
2302
  quote = null;
2237
- } else {
2238
- buffer += char;
2303
+ continue;
2304
+ }
2305
+ if (char === "\\") {
2306
+ const next = input[index + 1];
2307
+ if (next === void 0) {
2308
+ append("\\", index, index + 1, "double", false);
2309
+ complete = false;
2310
+ continue;
2311
+ }
2312
+ if (next === "$" || next === "`" || next === '"' || next === "\\" || next === "\n") {
2313
+ append(next === "\n" ? "" : next, index, index + 2, "double", false);
2314
+ quoteHadContent = true;
2315
+ index += 1;
2316
+ continue;
2317
+ }
2318
+ append("\\", index, index + 1, "double", false);
2319
+ quoteHadContent = true;
2320
+ continue;
2239
2321
  }
2322
+ append(char, index, index + 1, "double", char === "$" || char === "`");
2323
+ quoteHadContent = true;
2240
2324
  continue;
2241
2325
  }
2242
- if (char === '"' || char === "'") {
2243
- quote = char;
2326
+ if (char === "'" || char === '"') {
2327
+ startWord(index);
2328
+ quote = char === "'" ? "single" : "double";
2329
+ quoteStart = index;
2330
+ quoteHadContent = false;
2331
+ continue;
2332
+ }
2333
+ if (char === "\\") {
2334
+ const next = input[index + 1];
2335
+ if (next === void 0) {
2336
+ append("\\", index, index + 1, "unquoted", false);
2337
+ complete = false;
2338
+ continue;
2339
+ }
2340
+ append(next, index, index + 2, "unquoted", false);
2341
+ index += 1;
2244
2342
  continue;
2245
2343
  }
2246
2344
  const operator = readShellOperator(input, index);
2247
2345
  if (operator) {
2248
- flush();
2249
- tokens.push(operator.token);
2346
+ flushWord(index);
2347
+ pushOperator(operator.token, index, index + operator.length);
2250
2348
  index += operator.length - 1;
2251
2349
  continue;
2252
2350
  }
2253
2351
  if (char === "\n" || char === "\r") {
2254
- flush();
2255
- tokens.push(";");
2352
+ flushWord(index);
2353
+ pushOperator(";", index, index + 1);
2256
2354
  continue;
2257
2355
  }
2258
2356
  if (/\s/.test(char)) {
2259
- flush();
2357
+ flushWord(index);
2260
2358
  continue;
2261
2359
  }
2262
- buffer += char;
2360
+ append(char, index, index + 1, "unquoted", char === "$" || char === "`");
2263
2361
  }
2264
- flush();
2265
- return tokens;
2362
+ if (quote !== null) complete = false;
2363
+ flushWord(input.length);
2364
+ return { tokens, complete };
2365
+ }
2366
+ function tokenizeShell(input) {
2367
+ return lexShell(input).tokens.map((token) => token.value);
2266
2368
  }
2267
2369
  function commandKey(tokens) {
2268
2370
  const filtered = tokens.filter((token) => token !== "sudo");
@@ -3472,6 +3574,7 @@ var init_config_io = __esm({
3472
3574
 
3473
3575
  // src/adapters/codex/runtime-entry.ts
3474
3576
  init_codex();
3577
+ import path56 from "node:path";
3475
3578
  import process2 from "node:process";
3476
3579
 
3477
3580
  // src/adapters/shared/gate-runtime.ts
@@ -3480,7 +3583,7 @@ init_approval_replay();
3480
3583
  import { randomUUID as randomUUID6 } from "node:crypto";
3481
3584
  import { existsSync as existsSync16 } from "node:fs";
3482
3585
  import { mkdir as mkdir14, readFile as readFile15, writeFile as writeFile11 } from "node:fs/promises";
3483
- import path52 from "node:path";
3586
+ import path54 from "node:path";
3484
3587
 
3485
3588
  // src/core/approval-service.ts
3486
3589
  init_config_io();
@@ -10867,6 +10970,24 @@ init_fingerprint2();
10867
10970
 
10868
10971
  // src/core/replay-scrub.ts
10869
10972
  init_scrub();
10973
+ function redactToolInvocationId(value, rawToolUseId) {
10974
+ if (typeof value === "string") {
10975
+ return rawToolUseId ? value.replaceAll(rawToolUseId, "<tool-use-id>") : value;
10976
+ }
10977
+ if (Array.isArray(value)) {
10978
+ return value.map((item) => redactToolInvocationId(item, rawToolUseId));
10979
+ }
10980
+ if (value && typeof value === "object") {
10981
+ const result = {};
10982
+ for (const [key, child] of Object.entries(value)) {
10983
+ if (key !== "tool_use_id") {
10984
+ result[key] = redactToolInvocationId(child, rawToolUseId);
10985
+ }
10986
+ }
10987
+ return result;
10988
+ }
10989
+ return value;
10990
+ }
10870
10991
  function subagentFingerprintSource(payload, scrubOptions) {
10871
10992
  const toolInput = payload.tool_input;
10872
10993
  if (toolInput && typeof toolInput === "object") {
@@ -10899,16 +11020,20 @@ function fingerprintReplayPayload(kind, payload, scrubOptions) {
10899
11020
  if (!payload) {
10900
11021
  return void 0;
10901
11022
  }
11023
+ const replayPayload = redactToolInvocationId(
11024
+ payload,
11025
+ typeof payload.tool_use_id === "string" ? payload.tool_use_id : void 0
11026
+ );
10902
11027
  if (kind === "tool") {
10903
- const toolInput = payload.tool_input;
11028
+ const toolInput = replayPayload.tool_input;
10904
11029
  if (toolInput && typeof toolInput === "object") {
10905
11030
  return scrubValue(toolInput, scrubOptions);
10906
11031
  }
10907
11032
  }
10908
11033
  if (kind === "subagent") {
10909
- return subagentFingerprintSource(payload, scrubOptions);
11034
+ return subagentFingerprintSource(replayPayload, scrubOptions);
10910
11035
  }
10911
- return scrubValue(payload, scrubOptions);
11036
+ return scrubValue(replayPayload, scrubOptions);
10912
11037
  }
10913
11038
 
10914
11039
  // src/core/classify-subagent.ts
@@ -11000,7 +11125,7 @@ function classifySubagent(payload, repoRoot, options = {}, config) {
11000
11125
  }
11001
11126
 
11002
11127
  // src/core/classify-tool.ts
11003
- import path40 from "node:path";
11128
+ import path42 from "node:path";
11004
11129
  init_fingerprint2();
11005
11130
  init_path_utils();
11006
11131
  init_scrub();
@@ -11123,10 +11248,346 @@ init_git_resource_identity();
11123
11248
  init_path_utils();
11124
11249
  init_shell_tokenizer();
11125
11250
  import { lstatSync as lstatSync2, realpathSync as realpathSync4 } from "node:fs";
11126
- import path39 from "node:path";
11251
+ import path41 from "node:path";
11127
11252
 
11128
- // src/core/verdict/egress-classify.ts
11253
+ // src/core/verdict/docker-compose-run.ts
11254
+ import path36 from "node:path";
11255
+
11256
+ // src/core/verdict/recursive-invocation.ts
11129
11257
  import path35 from "node:path";
11258
+ var SHELL_INTERPRETERS = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "dash", "fish"]);
11259
+ var PYTHON_INTERPRETERS = /* @__PURE__ */ new Set(["python", "python3"]);
11260
+ var SHELL_SHORT_OPTIONS = /* @__PURE__ */ new Set(["c", "l", "e", "x", "u"]);
11261
+ var SHELL_NON_SCRIPT_SHORT_OPTIONS = /* @__PURE__ */ new Set(["n"]);
11262
+ var SHELL_TERMINAL_OPTIONS = /* @__PURE__ */ new Map([
11263
+ ["bash", /* @__PURE__ */ new Set(["--help", "--version"])],
11264
+ ["zsh", /* @__PURE__ */ new Set(["--version"])],
11265
+ ["fish", /* @__PURE__ */ new Set(["-h", "--help", "-v", "--version"])]
11266
+ ]);
11267
+ var SHELL_VALUE_OPTIONS = /* @__PURE__ */ new Set(["-O", "+O", "--init-file", "--rcfile"]);
11268
+ var NODE_TERMINAL_OPTIONS = /* @__PURE__ */ new Set(["-h", "--help", "--help-all", "-v", "--version"]);
11269
+ var NODE_FILE_OPTIONS = /* @__PURE__ */ new Set(["-c", "--check"]);
11270
+ var PYTHON_PROFILE = {
11271
+ scriptOptions: /* @__PURE__ */ new Set(["-c"]),
11272
+ terminalOptions: /* @__PURE__ */ new Set(["-h", "--help", "-V", "-VV", "--version"]),
11273
+ terminalValueOptions: /* @__PURE__ */ new Set(["-m"]),
11274
+ flagOptions: /* @__PURE__ */ new Set([
11275
+ "-b",
11276
+ "-bb",
11277
+ "-B",
11278
+ "-d",
11279
+ "-E",
11280
+ "-I",
11281
+ "-O",
11282
+ "-OO",
11283
+ "-P",
11284
+ "-q",
11285
+ "-s",
11286
+ "-S",
11287
+ "-u",
11288
+ "-v",
11289
+ "-x"
11290
+ ]),
11291
+ valueOptions: /* @__PURE__ */ new Set(["-W", "-X"]),
11292
+ attachedValuePrefixes: ["-W", "-X"]
11293
+ };
11294
+ var RUBY_PROFILE = {
11295
+ scriptOptions: /* @__PURE__ */ new Set(["-e"]),
11296
+ terminalOptions: /* @__PURE__ */ new Set(["-h", "--help", "-v", "--version", "--copyright"]),
11297
+ terminalValueOptions: /* @__PURE__ */ new Set([]),
11298
+ flagOptions: /* @__PURE__ */ new Set(["-d", "--debug", "-w"]),
11299
+ valueOptions: /* @__PURE__ */ new Set(["-I"]),
11300
+ attachedValuePrefixes: ["-I"]
11301
+ };
11302
+ var PERL_PROFILE = {
11303
+ scriptOptions: /* @__PURE__ */ new Set(["-e"]),
11304
+ terminalOptions: /* @__PURE__ */ new Set(["-h", "--help", "-v", "--version"]),
11305
+ terminalValueOptions: /* @__PURE__ */ new Set([]),
11306
+ flagOptions: /* @__PURE__ */ new Set([]),
11307
+ valueOptions: /* @__PURE__ */ new Set(["-I"]),
11308
+ attachedValuePrefixes: ["-I"]
11309
+ };
11310
+ var OSASCRIPT_PROFILE = {
11311
+ scriptOptions: /* @__PURE__ */ new Set(["-e"]),
11312
+ terminalOptions: /* @__PURE__ */ new Set(["-h", "--help"]),
11313
+ terminalValueOptions: /* @__PURE__ */ new Set([]),
11314
+ flagOptions: /* @__PURE__ */ new Set([]),
11315
+ valueOptions: /* @__PURE__ */ new Set(["-l"]),
11316
+ attachedValuePrefixes: []
11317
+ };
11318
+ function normalizeInterpreter(value) {
11319
+ return path35.basename(value);
11320
+ }
11321
+ function scriptResult(interpreter, token) {
11322
+ if (!token) {
11323
+ return { kind: "indeterminate", interpreter, signal: "shell.interpreter_argv_incomplete" };
11324
+ }
11325
+ if (token.parts.some((part) => part.hasExpansion)) {
11326
+ return { kind: "dynamic", interpreter, signal: "shell.script_expanded" };
11327
+ }
11328
+ return { kind: "static", interpreter, script: token.value };
11329
+ }
11330
+ function decodeShell(words, interpreter) {
11331
+ for (let index = 1; index < words.length; index += 1) {
11332
+ const option = words[index]?.value ?? "";
11333
+ if (option === "--") return { kind: "none" };
11334
+ if (SHELL_TERMINAL_OPTIONS.get(interpreter)?.has(option)) return { kind: "none" };
11335
+ if (interpreter === "bash" && SHELL_VALUE_OPTIONS.has(option)) {
11336
+ const operand = words[index + 1]?.value;
11337
+ if (!operand || operand.startsWith("-")) {
11338
+ return { kind: "indeterminate", interpreter, signal: "shell.interpreter_option_unknown" };
11339
+ }
11340
+ index += 1;
11341
+ continue;
11342
+ }
11343
+ if (!option.startsWith("-") || option === "-") return { kind: "none" };
11344
+ const flags = [...option.slice(1)];
11345
+ if (flags.length === 0) {
11346
+ return { kind: "indeterminate", interpreter, signal: "shell.interpreter_option_unknown" };
11347
+ }
11348
+ if (flags.every((flag) => SHELL_SHORT_OPTIONS.has(flag))) {
11349
+ if (!flags.includes("c")) continue;
11350
+ return scriptResult(interpreter, words[index + 1]);
11351
+ }
11352
+ if (flags.every(
11353
+ (flag) => SHELL_SHORT_OPTIONS.has(flag) || SHELL_NON_SCRIPT_SHORT_OPTIONS.has(flag)
11354
+ ) && flags.some((flag) => SHELL_NON_SCRIPT_SHORT_OPTIONS.has(flag))) {
11355
+ return { kind: "none" };
11356
+ }
11357
+ return { kind: "indeterminate", interpreter, signal: "shell.interpreter_option_unknown" };
11358
+ }
11359
+ return { kind: "none" };
11360
+ }
11361
+ function decodeSeparated(words, interpreter, profile) {
11362
+ for (let index = 1; index < words.length; index += 1) {
11363
+ const option = words[index]?.value ?? "";
11364
+ if (option === "--" || !option.startsWith("-") || option === "-") return { kind: "none" };
11365
+ if (profile.scriptOptions.has(option)) {
11366
+ return scriptResult(interpreter, words[index + 1]);
11367
+ }
11368
+ if (profile.terminalOptions.has(option)) return { kind: "none" };
11369
+ if (profile.terminalValueOptions.has(option)) {
11370
+ const operand = words[index + 1]?.value;
11371
+ if (!operand || operand.startsWith("-")) {
11372
+ return { kind: "indeterminate", interpreter, signal: "shell.interpreter_option_unknown" };
11373
+ }
11374
+ return { kind: "none" };
11375
+ }
11376
+ if (profile.flagOptions.has(option)) {
11377
+ continue;
11378
+ }
11379
+ if (profile.valueOptions.has(option)) {
11380
+ const operand = words[index + 1]?.value;
11381
+ if (!operand || operand.startsWith("-")) {
11382
+ return { kind: "indeterminate", interpreter, signal: "shell.interpreter_option_unknown" };
11383
+ }
11384
+ index += 1;
11385
+ continue;
11386
+ }
11387
+ if (profile.attachedValuePrefixes.some(
11388
+ (prefix) => option.startsWith(prefix) && option.length > prefix.length
11389
+ )) {
11390
+ continue;
11391
+ }
11392
+ return { kind: "indeterminate", interpreter, signal: "shell.interpreter_option_unknown" };
11393
+ }
11394
+ return { kind: "none" };
11395
+ }
11396
+ function decodeNode(words, interpreter) {
11397
+ const option = words[1]?.value ?? "";
11398
+ if (option === "--" || !option.startsWith("-") || option === "-") return { kind: "none" };
11399
+ if (option === "-e" || option === "--eval") {
11400
+ return scriptResult(interpreter, words[2]);
11401
+ }
11402
+ if (option.startsWith("--eval=")) {
11403
+ const script = option.slice("--eval=".length);
11404
+ if (words[1]?.parts.some((part) => part.hasExpansion)) {
11405
+ return { kind: "dynamic", interpreter, signal: "shell.script_expanded" };
11406
+ }
11407
+ return { kind: "static", interpreter, script };
11408
+ }
11409
+ if (NODE_TERMINAL_OPTIONS.has(option)) return { kind: "none" };
11410
+ if (NODE_FILE_OPTIONS.has(option)) return { kind: "none" };
11411
+ return { kind: "indeterminate", interpreter, signal: "shell.interpreter_option_unknown" };
11412
+ }
11413
+ function decodeEval(words) {
11414
+ const arguments_ = words.slice(1);
11415
+ if (arguments_.length === 0) return { kind: "none" };
11416
+ if (arguments_.some((word) => word.parts.some((part) => part.hasExpansion))) {
11417
+ return { kind: "dynamic", interpreter: "eval", signal: "shell.script_expanded" };
11418
+ }
11419
+ return {
11420
+ kind: "static",
11421
+ interpreter: "eval",
11422
+ script: arguments_.map((word) => word.value).join(" ")
11423
+ };
11424
+ }
11425
+ function decodeRecursiveInvocation(tokens) {
11426
+ if (tokens.some((token) => token.kind === "operator")) return { kind: "none" };
11427
+ const words = tokens.filter((token) => token.kind === "word");
11428
+ const interpreter = normalizeInterpreter(words[0]?.value ?? "");
11429
+ if (!interpreter) return { kind: "none" };
11430
+ if (interpreter === "eval") return decodeEval(words);
11431
+ if (SHELL_INTERPRETERS.has(interpreter)) return decodeShell(words, interpreter);
11432
+ if (PYTHON_INTERPRETERS.has(interpreter)) {
11433
+ return decodeSeparated(words, interpreter, PYTHON_PROFILE);
11434
+ }
11435
+ if (interpreter === "node") return decodeNode(words, interpreter);
11436
+ if (interpreter === "ruby") return decodeSeparated(words, interpreter, RUBY_PROFILE);
11437
+ if (interpreter === "perl") return decodeSeparated(words, interpreter, PERL_PROFILE);
11438
+ if (interpreter === "osascript") return decodeSeparated(words, interpreter, OSASCRIPT_PROFILE);
11439
+ return { kind: "none" };
11440
+ }
11441
+ function shellTokensFromValues(values, options = {}) {
11442
+ let offset = 0;
11443
+ return values.map((value) => {
11444
+ const start = offset;
11445
+ const end = start + value.length;
11446
+ offset = end + 1;
11447
+ return {
11448
+ kind: "word",
11449
+ value,
11450
+ raw: value,
11451
+ start,
11452
+ end,
11453
+ parts: [
11454
+ {
11455
+ value,
11456
+ raw: value,
11457
+ start,
11458
+ end,
11459
+ quote: "unquoted",
11460
+ hasExpansion: options.detectExpansion !== false && (value.includes("$") || value.includes("`"))
11461
+ }
11462
+ ]
11463
+ };
11464
+ });
11465
+ }
11466
+
11467
+ // src/core/verdict/docker-compose-run.ts
11468
+ var COMPOSE_GLOBAL_OPTIONS = /* @__PURE__ */ new Map([
11469
+ ["--all-resources", 0],
11470
+ ["--ansi", 1],
11471
+ ["--compatibility", 0],
11472
+ ["--dry-run", 0],
11473
+ ["--env-file", 1],
11474
+ ["-f", 1],
11475
+ ["--file", 1],
11476
+ ["--parallel", 1],
11477
+ ["--profile", 1],
11478
+ ["--progress", 1],
11479
+ ["--project-directory", 1],
11480
+ ["-p", 1],
11481
+ ["--project-name", 1]
11482
+ ]);
11483
+ var COMPOSE_RUN_OPTIONS = /* @__PURE__ */ new Map([
11484
+ ["--build", 0],
11485
+ ["--cap-add", 1],
11486
+ ["--cap-drop", 1],
11487
+ ["-d", 0],
11488
+ ["--detach", 0],
11489
+ ["--entrypoint", 1],
11490
+ ["-e", 1],
11491
+ ["--env", 1],
11492
+ ["--env-from-file", 1],
11493
+ ["-i", 0],
11494
+ ["--interactive", 0],
11495
+ ["-l", 1],
11496
+ ["--label", 1],
11497
+ ["--name", 1],
11498
+ ["--no-deps", 0],
11499
+ ["-T", 0],
11500
+ ["--no-tty", 0],
11501
+ ["-p", 1],
11502
+ ["--publish", 1],
11503
+ ["--pull", 1],
11504
+ ["-q", 0],
11505
+ ["--quiet", 0],
11506
+ ["--quiet-build", 0],
11507
+ ["--quiet-pull", 0],
11508
+ ["--remove-orphans", 0],
11509
+ ["--rm", 0],
11510
+ ["-P", 0],
11511
+ ["--service-ports", 0],
11512
+ ["--use-aliases", 0],
11513
+ ["-u", 1],
11514
+ ["--user", 1],
11515
+ ["-v", 1],
11516
+ ["--volume", 1],
11517
+ ["-w", 1],
11518
+ ["--workdir", 1]
11519
+ ]);
11520
+ function parseOptions(words, start, options) {
11521
+ let index = start;
11522
+ while (index < words.length) {
11523
+ const value = words[index]?.value ?? "";
11524
+ if (value === "--") return { kind: "ok", index: index + 1 };
11525
+ if (!value.startsWith("-") || value === "-") return { kind: "ok", index };
11526
+ const equalsIndex = value.indexOf("=");
11527
+ const name = equalsIndex === -1 ? value : value.slice(0, equalsIndex);
11528
+ const arity = options.get(name);
11529
+ if (arity === void 0) return { kind: "indeterminate" };
11530
+ if (equalsIndex !== -1) {
11531
+ if (!value.startsWith("--") || arity !== 1 || equalsIndex === value.length - 1) {
11532
+ return { kind: "indeterminate" };
11533
+ }
11534
+ index += 1;
11535
+ continue;
11536
+ }
11537
+ if (arity === 1) {
11538
+ if (!words[index + 1]) return { kind: "indeterminate" };
11539
+ index += 2;
11540
+ continue;
11541
+ }
11542
+ index += 1;
11543
+ }
11544
+ return { kind: "ok", index };
11545
+ }
11546
+ function decodeDockerComposeRun(tokens) {
11547
+ if (tokens.some((token) => token.kind === "operator")) return { kind: "none" };
11548
+ const words = tokens.filter((token) => token.kind === "word");
11549
+ const head = path36.basename(words[0]?.value ?? "");
11550
+ let index;
11551
+ if (head === "docker-compose") {
11552
+ index = 1;
11553
+ } else if (head === "docker" && words[1]?.value === "compose") {
11554
+ index = 2;
11555
+ } else {
11556
+ return { kind: "none" };
11557
+ }
11558
+ const globalOptions = parseOptions(words, index, COMPOSE_GLOBAL_OPTIONS);
11559
+ if (globalOptions.kind === "indeterminate") {
11560
+ return { kind: "indeterminate", signal: "shell.compose_argv_indeterminate" };
11561
+ }
11562
+ index = globalOptions.index;
11563
+ if (words[index]?.value !== "run") return { kind: "none" };
11564
+ const runOptions = parseOptions(words, index + 1, COMPOSE_RUN_OPTIONS);
11565
+ if (runOptions.kind === "indeterminate") {
11566
+ return { kind: "indeterminate", signal: "shell.compose_argv_indeterminate" };
11567
+ }
11568
+ index = runOptions.index;
11569
+ const service = words[index]?.value;
11570
+ if (!service) return { kind: "indeterminate", signal: "shell.compose_argv_indeterminate" };
11571
+ const command = words.slice(index + 1);
11572
+ if (command.length === 0) return { kind: "none" };
11573
+ const recursive = decodeRecursiveInvocation(command);
11574
+ if (recursive.kind === "static") {
11575
+ return {
11576
+ kind: "recursive",
11577
+ service,
11578
+ interpreter: recursive.interpreter,
11579
+ script: recursive.script
11580
+ };
11581
+ }
11582
+ if (recursive.kind === "dynamic") return { kind: "dynamic", service, signal: recursive.signal };
11583
+ if (recursive.kind === "indeterminate") {
11584
+ return { kind: "indeterminate", signal: "shell.compose_argv_indeterminate" };
11585
+ }
11586
+ return { kind: "none" };
11587
+ }
11588
+
11589
+ // src/core/verdict/egress-classify.ts
11590
+ import path37 from "node:path";
11130
11591
  var CURL_EFFECT_NEUTRAL_FLAGS = /* @__PURE__ */ new Set([
11131
11592
  "-f",
11132
11593
  "-L",
@@ -11161,7 +11622,7 @@ var GH_READ_COMMANDS = /* @__PURE__ */ new Set([
11161
11622
  "workflow view"
11162
11623
  ]);
11163
11624
  function decodeEgressEffects(params) {
11164
- const head = path35.basename(params.tokens[0] ?? "");
11625
+ const head = path37.basename(params.tokens[0] ?? "");
11165
11626
  if (head !== "curl" && head !== "wget" && head !== "gh") {
11166
11627
  return null;
11167
11628
  }
@@ -11169,7 +11630,7 @@ function decodeEgressEffects(params) {
11169
11630
  const provenance = { segment: params.segment };
11170
11631
  const requirements = [];
11171
11632
  for (const file of decoded.files) {
11172
- const resolved = path35.resolve(params.cwd, expandHome2(file));
11633
+ const resolved = path37.resolve(params.cwd, expandHome2(file));
11173
11634
  requirements.push(
11174
11635
  requirement("fs.read", "fs.read", { kind: "path", path: resolved }, params.segment, [
11175
11636
  "egress.explicit_file_read"
@@ -11191,7 +11652,7 @@ function decodeEgressEffects(params) {
11191
11652
  if (file === "-") {
11192
11653
  continue;
11193
11654
  }
11194
- const resolved = path35.resolve(params.cwd, expandHome2(file));
11655
+ const resolved = path37.resolve(params.cwd, expandHome2(file));
11195
11656
  if (resolved === "/dev/null") {
11196
11657
  continue;
11197
11658
  }
@@ -11669,13 +12130,13 @@ function decodeSingleCurlWgetGrammar(head, tokens) {
11669
12130
  if (head === "wget" && !explicitOutput) {
11670
12131
  outputFiles.push(
11671
12132
  ...endpointOutputNames.map(
11672
- (name) => outputDirectory ? path35.join(outputDirectory, name) : name
12133
+ (name) => outputDirectory ? path37.join(outputDirectory, name) : name
11673
12134
  )
11674
12135
  );
11675
12136
  } else if (head === "curl" && remoteNameOutput) {
11676
12137
  outputFiles.push(
11677
12138
  ...endpointOutputNames.map(
11678
- (name) => outputDirectory ? path35.join(outputDirectory, name) : name
12139
+ (name) => outputDirectory ? path37.join(outputDirectory, name) : name
11679
12140
  )
11680
12141
  );
11681
12142
  }
@@ -11689,7 +12150,7 @@ function decodeSingleCurlWgetGrammar(head, tokens) {
11689
12150
  outputFiles: [
11690
12151
  ...new Set(
11691
12152
  outputFiles.map(
11692
- (file) => outputDirectory && directoryEligibleOutputs.has(file) && !path35.isAbsolute(file) ? path35.join(outputDirectory, file) : file
12153
+ (file) => outputDirectory && directoryEligibleOutputs.has(file) && !path37.isAbsolute(file) ? path37.join(outputDirectory, file) : file
11693
12154
  )
11694
12155
  )
11695
12156
  ],
@@ -11704,7 +12165,7 @@ function remoteOutputName(spec) {
11704
12165
  } catch {
11705
12166
  pathname = spec.split(/[?#]/, 1)[0] ?? "";
11706
12167
  }
11707
- const name = path35.posix.basename(pathname);
12168
+ const name = path37.posix.basename(pathname);
11708
12169
  return name && name !== "/" ? name : "index.html";
11709
12170
  }
11710
12171
  function decodeGhGrammar(tokens) {
@@ -11869,7 +12330,7 @@ function expandHome2(value) {
11869
12330
  return process.env.HOME ?? value;
11870
12331
  }
11871
12332
  if (value.startsWith("~/")) {
11872
- return path35.join(process.env.HOME ?? "~", value.slice(2));
12333
+ return path37.join(process.env.HOME ?? "~", value.slice(2));
11873
12334
  }
11874
12335
  return value;
11875
12336
  }
@@ -11888,7 +12349,7 @@ function requirement(tag, action, resource, segment, signals) {
11888
12349
  }
11889
12350
 
11890
12351
  // src/core/verdict/git-classifier.ts
11891
- import path36 from "node:path";
12352
+ import path38 from "node:path";
11892
12353
  init_shell_tokenizer();
11893
12354
  var GIT_BRANCH_MUTATION_FLAGS = /* @__PURE__ */ new Set([
11894
12355
  "--copy",
@@ -11984,7 +12445,7 @@ var FILE_OPERAND_SUBCOMMANDS = /* @__PURE__ */ new Set([
11984
12445
  var COMPOUND_SUBCOMMAND_HEADS = /* @__PURE__ */ new Set(["worktree", "stash", "tag"]);
11985
12446
  var REF_ONLY_WITHOUT_TERMINATOR = /* @__PURE__ */ new Set(["checkout", "show", "log"]);
11986
12447
  function isGitExecutable(token) {
11987
- return path36.basename(token) === "git";
12448
+ return path38.basename(token) === "git";
11988
12449
  }
11989
12450
  function takesValue(flag) {
11990
12451
  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=");
@@ -12012,7 +12473,7 @@ function peelGlobalOptions(tokens, baseCwd) {
12012
12473
  if (token === "-C" || token === "--work-tree" || token === "--git-dir" || token === "-c") {
12013
12474
  const value = tokens[index + 1];
12014
12475
  if (token === "-C" && value) {
12015
- effectiveCwd = path36.resolve(baseCwd, value);
12476
+ effectiveCwd = path38.resolve(baseCwd, value);
12016
12477
  } else if (token === "--work-tree" && value) {
12017
12478
  workTree = value;
12018
12479
  } else if (token === "--git-dir" && value) {
@@ -12022,7 +12483,7 @@ function peelGlobalOptions(tokens, baseCwd) {
12022
12483
  continue;
12023
12484
  }
12024
12485
  if (token.startsWith("-C") && token.length > 2) {
12025
- effectiveCwd = path36.resolve(baseCwd, token.slice(2));
12486
+ effectiveCwd = path38.resolve(baseCwd, token.slice(2));
12026
12487
  index += 1;
12027
12488
  continue;
12028
12489
  }
@@ -12165,7 +12626,7 @@ function looksLikeDiffPathOperand(token) {
12165
12626
  if (!looksLikeFileOperand(token)) {
12166
12627
  return false;
12167
12628
  }
12168
- if (token.startsWith(".") || path36.isAbsolute(token)) {
12629
+ if (token.startsWith(".") || path38.isAbsolute(token)) {
12169
12630
  return true;
12170
12631
  }
12171
12632
  return token.includes(".");
@@ -12173,12 +12634,12 @@ function looksLikeDiffPathOperand(token) {
12173
12634
  function resolveGitWorkTree(baseCwd, effectiveCwd, workTree, gitDir) {
12174
12635
  const resolveBase = effectiveCwd ?? baseCwd;
12175
12636
  if (workTree) {
12176
- return path36.resolve(resolveBase, workTree);
12637
+ return path38.resolve(resolveBase, workTree);
12177
12638
  }
12178
12639
  if (gitDir) {
12179
- const resolvedGitDir = path36.resolve(resolveBase, gitDir);
12180
- if (path36.basename(resolvedGitDir) === ".git") {
12181
- return path36.dirname(resolvedGitDir);
12640
+ const resolvedGitDir = path38.resolve(resolveBase, gitDir);
12641
+ if (path38.basename(resolvedGitDir) === ".git") {
12642
+ return path38.dirname(resolvedGitDir);
12182
12643
  }
12183
12644
  }
12184
12645
  return void 0;
@@ -12259,7 +12720,7 @@ function classifyGitCommand(tokens, baseCwd) {
12259
12720
  const { subcommand, args, effectiveCwd, gitDir, workTree } = normalized;
12260
12721
  const normalizedKey = `git ${subcommand}`;
12261
12722
  const gitWorkTree = resolveGitWorkTree(baseCwd, effectiveCwd, workTree, gitDir);
12262
- const effectiveGitDir = gitDir ? path36.resolve(effectiveCwd ?? baseCwd, gitDir) : void 0;
12723
+ const effectiveGitDir = gitDir ? path38.resolve(effectiveCwd ?? baseCwd, gitDir) : void 0;
12263
12724
  const scopeTargets = [effectiveCwd, gitWorkTree, effectiveGitDir].filter(
12264
12725
  (target, index, targets) => Boolean(target) && targets.indexOf(target) === index
12265
12726
  );
@@ -12411,9 +12872,9 @@ function decodeGitEffects(params) {
12411
12872
  ...subcommand === "push" ? ["tier0_external"] : []
12412
12873
  ];
12413
12874
  const effectiveCwd = normalized.effectiveCwd ?? params.cwd;
12414
- const workTreeRoot = normalized.workTree ? path36.resolve(normalized.effectiveCwd ?? params.cwd, normalized.workTree) : normalized.effectiveCwd ?? params.repoRoot;
12415
- const gitRefRoot = normalized.gitDir ? path36.resolve(effectiveCwd, normalized.gitDir) : workTreeRoot;
12416
- const gitControlRoot = normalized.gitDir ? gitRefRoot : path36.join(gitRefRoot, ".git");
12875
+ const workTreeRoot = normalized.workTree ? path38.resolve(normalized.effectiveCwd ?? params.cwd, normalized.workTree) : normalized.effectiveCwd ?? params.repoRoot;
12876
+ const gitRefRoot = normalized.gitDir ? path38.resolve(effectiveCwd, normalized.gitDir) : workTreeRoot;
12877
+ const gitControlRoot = normalized.gitDir ? gitRefRoot : path38.join(gitRefRoot, ".git");
12417
12878
  const requirements = [];
12418
12879
  if (subcommand === "fetch" || subcommand === "pull") {
12419
12880
  const positionals = gitRemotePositionals(args);
@@ -12572,7 +13033,7 @@ function decodeGitEffects(params) {
12572
13033
  gitRequirement(
12573
13034
  "control_plane.write",
12574
13035
  "control_plane.write",
12575
- { kind: "path", path: path36.join(gitControlRoot, "logs") },
13036
+ { kind: "path", path: path38.join(gitControlRoot, "logs") },
12576
13037
  params.segment,
12577
13038
  [...signals, "git_history_destructive", "git.reflog.mutate"]
12578
13039
  )
@@ -12630,7 +13091,7 @@ function decodeGitEffects(params) {
12630
13091
  gitRequirement(
12631
13092
  "fs.read",
12632
13093
  "fs.read",
12633
- { kind: "path", path: path36.resolve(workTreeRoot, operand) },
13094
+ { kind: "path", path: path38.resolve(workTreeRoot, operand) },
12634
13095
  params.segment,
12635
13096
  [...signals, "git.path.read"]
12636
13097
  )
@@ -12665,7 +13126,7 @@ function decodeGitEffects(params) {
12665
13126
  gitRequirement(
12666
13127
  "fs.write",
12667
13128
  "fs.write",
12668
- { kind: "path", path: path36.resolve(workTreeRoot, operand) },
13129
+ { kind: "path", path: path38.resolve(workTreeRoot, operand) },
12669
13130
  params.segment,
12670
13131
  [...signals, "git.path.write"]
12671
13132
  )
@@ -12850,7 +13311,7 @@ function gitRequirement(tag, action, resource, segment, signals) {
12850
13311
 
12851
13312
  // src/core/verdict/launcher-resolve.ts
12852
13313
  import { existsSync as existsSync11, readFileSync as readFileSync5 } from "node:fs";
12853
- import path37 from "node:path";
13314
+ import path39 from "node:path";
12854
13315
 
12855
13316
  // src/core/verdict/makefile-expand.ts
12856
13317
  var MAX_EXPAND_DEPTH = 16;
@@ -12869,22 +13330,6 @@ function parseMakefileVariables(content) {
12869
13330
  }
12870
13331
  return variables;
12871
13332
  }
12872
- function parsePhonyTargets(content) {
12873
- const phony = /* @__PURE__ */ new Set();
12874
- for (const line of content.split("\n")) {
12875
- const trimmed = line.trim();
12876
- const match = /^\.PHONY:\s*(.+)$/.exec(trimmed);
12877
- if (!match) {
12878
- continue;
12879
- }
12880
- for (const token of (match[1] ?? "").split(/\s+/)) {
12881
- if (token) {
12882
- phony.add(token);
12883
- }
12884
- }
12885
- }
12886
- return phony;
12887
- }
12888
13333
  function normalizeMakeRecipeLine(line) {
12889
13334
  let normalized = line.trim();
12890
13335
  while (normalized.startsWith("@") || normalized.startsWith("-") || normalized.startsWith("+")) {
@@ -13039,7 +13484,7 @@ var PNPM_BUILTIN_COMMANDS = /* @__PURE__ */ new Set([
13039
13484
  "why"
13040
13485
  ]);
13041
13486
  function readPackageJson(dir) {
13042
- const packagePath = path37.join(dir, "package.json");
13487
+ const packagePath = path39.join(dir, "package.json");
13043
13488
  if (!existsSync11(packagePath)) {
13044
13489
  return null;
13045
13490
  }
@@ -13050,17 +13495,17 @@ function readPackageJson(dir) {
13050
13495
  }
13051
13496
  }
13052
13497
  function findPackageJson(startDir, stopDir) {
13053
- let current = path37.resolve(startDir);
13054
- const stop = path37.resolve(stopDir);
13498
+ let current = path39.resolve(startDir);
13499
+ const stop = path39.resolve(stopDir);
13055
13500
  while (true) {
13056
- const packagePath = path37.join(current, "package.json");
13501
+ const packagePath = path39.join(current, "package.json");
13057
13502
  if (existsSync11(packagePath)) {
13058
13503
  return packagePath;
13059
13504
  }
13060
- if (current === stop || current === path37.dirname(current)) {
13505
+ if (current === stop || current === path39.dirname(current)) {
13061
13506
  return existsSync11(packagePath) ? packagePath : null;
13062
13507
  }
13063
- const parent = path37.dirname(current);
13508
+ const parent = path39.dirname(current);
13064
13509
  if (!parent.startsWith(stop) && parent !== current) {
13065
13510
  }
13066
13511
  if (parent === current) {
@@ -13117,7 +13562,7 @@ function resolveNpmRecipe(cwd, repoRoot, scriptName, extraArgs) {
13117
13562
  }
13118
13563
  return { recipes: [], opaque: true, reason: "package_json_missing" };
13119
13564
  }
13120
- const pkg = readPackageJson(path37.dirname(packagePath));
13565
+ const pkg = readPackageJson(path39.dirname(packagePath));
13121
13566
  const scripts = pkg?.scripts;
13122
13567
  if (!scripts || typeof scripts !== "object") {
13123
13568
  return { recipes: [], opaque: true, reason: "package_scripts_missing" };
@@ -13208,27 +13653,26 @@ function parseMakefileRecipeContent(content) {
13208
13653
  function resolveMakeRecipe(cwd, repoRoot, target, cliVars = {}) {
13209
13654
  const candidates = ["Makefile", "makefile", "GNUmakefile"];
13210
13655
  let makefilePath = null;
13211
- let searchDir = path37.resolve(cwd);
13212
- const stop = path37.resolve(repoRoot);
13656
+ let searchDir = path39.resolve(cwd);
13657
+ const stop = path39.resolve(repoRoot);
13213
13658
  while (true) {
13214
13659
  for (const name of candidates) {
13215
- const candidate = path37.join(searchDir, name);
13660
+ const candidate = path39.join(searchDir, name);
13216
13661
  if (existsSync11(candidate)) {
13217
13662
  makefilePath = candidate;
13218
13663
  break;
13219
13664
  }
13220
13665
  }
13221
- if (makefilePath || searchDir === stop || searchDir === path37.dirname(searchDir)) {
13666
+ if (makefilePath || searchDir === stop || searchDir === path39.dirname(searchDir)) {
13222
13667
  break;
13223
13668
  }
13224
- searchDir = path37.dirname(searchDir);
13669
+ searchDir = path39.dirname(searchDir);
13225
13670
  }
13226
13671
  if (!makefilePath) {
13227
13672
  return { recipes: [], opaque: true, reason: "unknown_local_effect" };
13228
13673
  }
13229
13674
  const makefileContent = readFileSync5(makefilePath, "utf8");
13230
13675
  const makefileVars = parseMakefileVariables(makefileContent);
13231
- const phonyTargets = parsePhonyTargets(makefileContent);
13232
13676
  const targets = parseMakefileRecipeContent(makefileContent);
13233
13677
  if (!targets.has(target)) {
13234
13678
  return { recipes: [], opaque: true, reason: "make_target_undefined" };
@@ -13236,36 +13680,34 @@ function resolveMakeRecipe(cwd, repoRoot, target, cliVars = {}) {
13236
13680
  const recipeLines = [];
13237
13681
  const visiting = /* @__PURE__ */ new Set();
13238
13682
  const visited = /* @__PURE__ */ new Set();
13239
- let opaquePrerequisites = false;
13683
+ let hasDynamicPrerequisite = false;
13684
+ let hasUndefinedPrerequisite = false;
13685
+ let hasDependencyCycle = false;
13240
13686
  const collect = (name) => {
13241
13687
  if (visited.has(name)) {
13242
- return true;
13688
+ return;
13243
13689
  }
13244
13690
  if (visiting.has(name)) {
13245
- return false;
13691
+ hasDependencyCycle = true;
13692
+ return;
13246
13693
  }
13247
13694
  const entry = targets.get(name);
13248
13695
  if (!entry) {
13249
- return true;
13696
+ if (!existsSync11(path39.resolve(path39.dirname(makefilePath), name))) {
13697
+ hasUndefinedPrerequisite = true;
13698
+ }
13699
+ return;
13250
13700
  }
13251
13701
  visiting.add(name);
13252
- opaquePrerequisites ||= entry.opaquePrerequisites;
13702
+ hasDynamicPrerequisite ||= entry.opaquePrerequisites;
13253
13703
  for (const prerequisite of entry.prerequisites) {
13254
- if (targets.has(prerequisite) && !collect(prerequisite)) {
13255
- return false;
13256
- }
13257
- }
13258
- const skipPhonyPrerequisiteRecipes = name !== target && (phonyTargets.has(name) || name.startsWith("_")) && (targets.get(target)?.recipes.length ?? 0) > 0;
13259
- if (!skipPhonyPrerequisiteRecipes) {
13260
- recipeLines.push(...entry.recipes);
13704
+ collect(prerequisite);
13261
13705
  }
13706
+ recipeLines.push(...entry.recipes);
13262
13707
  visiting.delete(name);
13263
13708
  visited.add(name);
13264
- return true;
13265
13709
  };
13266
- if (!collect(target)) {
13267
- return { recipes: recipeLines, opaque: true, reason: "make_dependency_cycle" };
13268
- }
13710
+ collect(target);
13269
13711
  const expandedRecipes = [];
13270
13712
  for (const line of recipeLines) {
13271
13713
  const normalized = normalizeMakeRecipeLine(line);
@@ -13280,18 +13722,24 @@ function resolveMakeRecipe(cwd, repoRoot, target, cliVars = {}) {
13280
13722
  return { recipes: expandedRecipes, opaque: true, reason: "make_recipe_dynamic" };
13281
13723
  }
13282
13724
  }
13283
- if (opaquePrerequisites) {
13725
+ if (hasDependencyCycle) {
13726
+ return { recipes: expandedRecipes, opaque: true, reason: "make_dependency_cycle" };
13727
+ }
13728
+ if (hasDynamicPrerequisite) {
13284
13729
  return { recipes: expandedRecipes, opaque: true, reason: "make_prerequisite_dynamic" };
13285
13730
  }
13731
+ if (hasUndefinedPrerequisite) {
13732
+ return { recipes: expandedRecipes, opaque: true, reason: "make_prerequisite_undefined" };
13733
+ }
13286
13734
  return { recipes: expandedRecipes, opaque: false, reason: "make_recipe_resolved" };
13287
13735
  }
13288
13736
  function resolveLauncherRecipe(params) {
13289
- if (params.depth >= MAX_RESOLVE_DEPTH) {
13290
- return { recipes: [], opaque: true, reason: "launcher_depth_exceeded" };
13291
- }
13292
13737
  const tokens = params.tokens;
13293
13738
  const scriptName = npmScriptName(tokens);
13294
13739
  if (scriptName) {
13740
+ if (params.depth >= MAX_RESOLVE_DEPTH) {
13741
+ return { recipes: [], opaque: true, reason: "launcher_depth_exceeded" };
13742
+ }
13295
13743
  const resolution = resolveNpmRecipe(
13296
13744
  params.cwd,
13297
13745
  params.repoRoot,
@@ -13308,6 +13756,9 @@ function resolveLauncherRecipe(params) {
13308
13756
  return resolution;
13309
13757
  }
13310
13758
  if (tokens[0] === "make") {
13759
+ if (params.depth >= MAX_RESOLVE_DEPTH) {
13760
+ return { recipes: [], opaque: true, reason: "launcher_depth_exceeded" };
13761
+ }
13311
13762
  if (tokens.includes("-n") || tokens.includes("--dry-run")) {
13312
13763
  return null;
13313
13764
  }
@@ -13341,7 +13792,7 @@ function resolveLauncherRecipe(params) {
13341
13792
  }
13342
13793
 
13343
13794
  // src/core/verdict/parser.ts
13344
- import path38 from "node:path";
13795
+ import path40 from "node:path";
13345
13796
 
13346
13797
  // src/core/shell-substitution.ts
13347
13798
  function findStructuralCommandSubstitutions(command) {
@@ -13566,9 +14017,8 @@ function hasUnbalancedDollarParen(command) {
13566
14017
  // src/core/verdict/parser.ts
13567
14018
  var ENV_PREFIX_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*=(?:'[^']*'|"[^"]*"|\S+)$/;
13568
14019
  var MAX_WRAPPER_PEEL_DEPTH = 32;
13569
- var SHELL_INTERPRETERS = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "dash", "fish"]);
14020
+ var SHELL_INTERPRETERS2 = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "dash", "fish"]);
13570
14021
  var CODE_INTERPRETERS = /* @__PURE__ */ new Set(["python", "python3", "node", "ruby", "perl", "osascript"]);
13571
- var SCRIPT_FLAGS = /* @__PURE__ */ new Set(["-c", "-lc", "-e", "--eval"]);
13572
14022
  var INTERPRETER_SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([
13573
14023
  ".js",
13574
14024
  ".mjs",
@@ -13580,7 +14030,7 @@ var INTERPRETER_SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([
13580
14030
  ".sh"
13581
14031
  ]);
13582
14032
  function normalizeHead(token) {
13583
- const base = path38.basename(token);
14033
+ const base = path40.basename(token);
13584
14034
  if (base && base !== "." && base !== "..") {
13585
14035
  return base;
13586
14036
  }
@@ -13592,10 +14042,6 @@ function peelTransparentWrappers(tokens) {
13592
14042
  let encounteredXargs = false;
13593
14043
  let peelDepth = 0;
13594
14044
  while (current.length > 0) {
13595
- if (peelDepth >= MAX_WRAPPER_PEEL_DEPTH) {
13596
- return { tokens: current, xargsStdinOpaque: false, encounteredXargs, opaque: true };
13597
- }
13598
- peelDepth += 1;
13599
14045
  while (current.length > 0 && ENV_PREFIX_PATTERN.test(current[0] ?? "")) {
13600
14046
  current.shift();
13601
14047
  }
@@ -13605,6 +14051,10 @@ function peelTransparentWrappers(tokens) {
13605
14051
  const head = normalizeHead(current[0] ?? "");
13606
14052
  if (head === "xargs") {
13607
14053
  encounteredXargs = true;
14054
+ if (peelDepth >= MAX_WRAPPER_PEEL_DEPTH) {
14055
+ return { tokens: current, xargsStdinOpaque: false, encounteredXargs, opaque: true };
14056
+ }
14057
+ peelDepth += 1;
13608
14058
  const wrapper2 = peelXargsWrapper(current);
13609
14059
  if (wrapper2.kind === "opaque") {
13610
14060
  xargsStdinOpaque = current.length === 1;
@@ -13622,6 +14072,10 @@ function peelTransparentWrappers(tokens) {
13622
14072
  if (!wrapper) {
13623
14073
  break;
13624
14074
  }
14075
+ if (peelDepth >= MAX_WRAPPER_PEEL_DEPTH) {
14076
+ return { tokens: current, xargsStdinOpaque: false, encounteredXargs, opaque: true };
14077
+ }
14078
+ peelDepth += 1;
13625
14079
  if (wrapper.kind === "opaque") {
13626
14080
  return { tokens: current, xargsStdinOpaque: false, encounteredXargs, opaque: true };
13627
14081
  }
@@ -13849,50 +14303,16 @@ function extractRecursiveScript(tokens) {
13849
14303
  const body = filtered.slice(1).join(" ").trim();
13850
14304
  return body || null;
13851
14305
  }
13852
- if (SHELL_INTERPRETERS.has(head) || CODE_INTERPRETERS.has(head)) {
13853
- const flagIndex = filtered.findIndex((token) => SCRIPT_FLAGS.has(token));
13854
- if (flagIndex !== -1) {
13855
- const body = filtered[flagIndex + 1] ?? "";
13856
- return body || null;
13857
- }
13858
- }
13859
- return null;
13860
- }
13861
- function extractDockerComposeRunScript(tokens) {
13862
- const head = normalizeHead(tokens[0] ?? "");
13863
- const usesCompose = head === "docker-compose" || head === "docker" && (tokens[1] ?? "") === "compose";
13864
- if (!usesCompose) {
13865
- return null;
13866
- }
13867
- if (!tokens.includes("run")) {
13868
- return null;
13869
- }
13870
- const runIndex = tokens.indexOf("run");
13871
- const tail = tokens.slice(runIndex + 1);
13872
- for (let index = 0; index < tail.length; index += 1) {
13873
- const shellHead = normalizeHead(tail[index] ?? "");
13874
- if (!SHELL_INTERPRETERS.has(shellHead)) {
13875
- continue;
13876
- }
13877
- const flag = tail[index + 1] ?? "";
13878
- if (flag !== "-lc" && flag !== "-c") {
13879
- continue;
13880
- }
13881
- const body = tail[index + 2] ?? "";
13882
- return body || null;
13883
- }
13884
- return null;
14306
+ const invocation = decodeRecursiveInvocation(
14307
+ shellTokensFromValues(filtered, { detectExpansion: false })
14308
+ );
14309
+ return invocation.kind === "static" ? invocation.script || null : null;
13885
14310
  }
13886
- function isDynamicRecursiveEvaluation(tokens) {
13887
- const { tokens: filtered, opaque } = peelTransparentWrappers(tokens);
13888
- if (opaque) {
13889
- return false;
13890
- }
13891
- const head = normalizeHead(filtered[0] ?? "");
13892
- if (head === "eval") {
13893
- return true;
13894
- }
13895
- return (SHELL_INTERPRETERS.has(head) || CODE_INTERPRETERS.has(head)) && filtered.some((token) => SCRIPT_FLAGS.has(token));
14311
+ function decodeRecursiveInvocationTokens(tokens) {
14312
+ const values = tokens.map((token) => token.value);
14313
+ const { tokens: filtered, opaque } = peelTransparentWrappers(values);
14314
+ if (opaque) return { kind: "none" };
14315
+ return decodeRecursiveInvocation(tokens.slice(values.length - filtered.length));
13896
14316
  }
13897
14317
  function isCommandInspection(tokens) {
13898
14318
  return normalizeHead(tokens[0] ?? "") === "command" && peelCommandWrapper(tokens).kind === "preserve";
@@ -13906,11 +14326,10 @@ function isBareInterpreter(tokens) {
13906
14326
  return false;
13907
14327
  }
13908
14328
  const head = normalizeHead(peeled[0] ?? "");
13909
- if (!SHELL_INTERPRETERS.has(head) && !CODE_INTERPRETERS.has(head)) {
14329
+ if (!SHELL_INTERPRETERS2.has(head) && !CODE_INTERPRETERS.has(head)) {
13910
14330
  return false;
13911
14331
  }
13912
- const hasScriptFlag = peeled.some((token) => SCRIPT_FLAGS.has(token));
13913
- if (hasScriptFlag) {
14332
+ if (decodeRecursiveInvocation(shellTokensFromValues(peeled)).kind !== "none") {
13914
14333
  return false;
13915
14334
  }
13916
14335
  const args = peeled.slice(1);
@@ -13921,7 +14340,7 @@ function isBareInterpreter(tokens) {
13921
14340
  return false;
13922
14341
  }
13923
14342
  const scriptArg = args.find((token) => !token.startsWith("-"));
13924
- if (scriptArg && INTERPRETER_SCRIPT_EXTENSIONS.has(path38.extname(scriptArg))) {
14343
+ if (scriptArg && INTERPRETER_SCRIPT_EXTENSIONS.has(path40.extname(scriptArg))) {
13925
14344
  return false;
13926
14345
  }
13927
14346
  if (scriptArg) {
@@ -14213,11 +14632,11 @@ function lowerTopLevelSegments(command, context) {
14213
14632
  }
14214
14633
  function startsLocalPostgresService(command) {
14215
14634
  const tokens = tokenizeShell(command);
14216
- return path39.basename(tokens[0] ?? "") === "docker" && tokens[1] === "compose" && ["up", "start", "restart"].includes(tokens[2] ?? "") && tokens.includes("postgres");
14635
+ return path41.basename(tokens[0] ?? "") === "docker" && tokens[1] === "compose" && ["up", "start", "restart"].includes(tokens[2] ?? "") && tokens.includes("postgres");
14217
14636
  }
14218
14637
  function resolveCdTransition(command, currentCwd) {
14219
14638
  const tokens = tokenizeShell(command);
14220
- if (path39.basename(tokens[0] ?? "") !== "cd") {
14639
+ if (path41.basename(tokens[0] ?? "") !== "cd") {
14221
14640
  return null;
14222
14641
  }
14223
14642
  const target = tokens[1] ?? "~";
@@ -14238,7 +14657,8 @@ function joinNestedOpacity(outer, nested) {
14238
14657
  }
14239
14658
  function lowerSegment(command, context) {
14240
14659
  const commandRedacted = redactCommand(command);
14241
- const rawTokens = tokenizeShell(command);
14660
+ const lexed = lexShell(command);
14661
+ const rawTokens = lexed.tokens.map((token) => token.value);
14242
14662
  const environment = extractEnvironment(rawTokens, context.env);
14243
14663
  const env = environment.env;
14244
14664
  const parsed = parseSegment(command);
@@ -14246,10 +14666,23 @@ function lowerSegment(command, context) {
14246
14666
  const tokens = stripRedirects(
14247
14667
  parsedTokens.length === 0 && rawTokens.length > 0 && rawTokens.every((token) => ENV_PREFIX_PATTERN2.test(token)) ? rawTokens : parsedTokens
14248
14668
  ).map((token) => expandKnownVariables(token, env));
14249
- const head = path39.basename(tokens[0] ?? parsed.head);
14669
+ const decoderTokens = alignStructuredTokens(
14670
+ stripStructuredRedirects(lexed.tokens),
14671
+ stripRedirects(parsedTokens)
14672
+ );
14673
+ const head = path41.basename(tokens[0] ?? parsed.head);
14250
14674
  let opacity = segmentOpacity(command);
14251
14675
  const signals = /* @__PURE__ */ new Set();
14252
14676
  const requirements = [];
14677
+ if (!lexed.complete) {
14678
+ requirements.push(
14679
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
14680
+ "shell.grammar_incomplete"
14681
+ ])
14682
+ );
14683
+ signals.add("shell.grammar_incomplete");
14684
+ opacity = joinEffectOpacity(opacity, "unparseable");
14685
+ }
14253
14686
  addRedirectEffects(requirements, rawTokens, env, context, commandRedacted);
14254
14687
  addSubstitutionEffects(requirements, command, context, commandRedacted, signals);
14255
14688
  if (environment.malformed) {
@@ -14269,7 +14702,7 @@ function lowerSegment(command, context) {
14269
14702
  signals.add("shell.xargs_stdin_dynamic");
14270
14703
  opacity = joinEffectOpacity(opacity, "opaque");
14271
14704
  }
14272
- if (context.depth >= MAX_LOWER_DEPTH) {
14705
+ if (context.depth > MAX_LOWER_DEPTH) {
14273
14706
  requirements.push(
14274
14707
  requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
14275
14708
  "shell.lower_depth_exceeded"
@@ -14316,62 +14749,96 @@ function lowerSegment(command, context) {
14316
14749
  }
14317
14750
  return shellSegment(commandRedacted, head, requirements, opacity, signals);
14318
14751
  }
14319
- const recursiveScript = extractRecursiveScript(tokens);
14320
- if (recursiveScript && opacity !== "opaque" && opacity !== "unparseable") {
14321
- const dynamicEvaluation = isDynamicRecursiveEvaluation(tokens);
14752
+ const recursive = decodeRecursiveInvocationTokens(decoderTokens);
14753
+ if (recursive.kind === "static" && opacity !== "opaque" && opacity !== "unparseable") {
14322
14754
  requirements.push(
14323
- processRequirement(head || "sh", "spawn", commandRedacted, [
14755
+ processRequirement(recursive.interpreter, "spawn", commandRedacted, [
14324
14756
  "shell.recursive_wrapper",
14325
- ...dynamicEvaluation ? ["dynamic_shell_evaluation"] : []
14757
+ "dynamic_shell_evaluation"
14326
14758
  ])
14327
14759
  );
14328
- const nested = lowerTopLevelSegments(recursiveScript, {
14329
- ...context,
14330
- command: recursiveScript,
14331
- env,
14332
- depth: context.depth + 1
14333
- });
14334
- for (const nestedSegment of nested) {
14335
- requirements.push(
14336
- ...nestedSegment.requirements.map(
14337
- (entry) => withInnerProvenance(entry, recursiveScript, head, commandRedacted)
14338
- )
14339
- );
14340
- for (const signal of nestedSegment.signals) {
14341
- signals.add(signal);
14760
+ if (recursive.script !== "") {
14761
+ const nested = lowerTopLevelSegments(recursive.script, {
14762
+ ...context,
14763
+ command: recursive.script,
14764
+ env,
14765
+ depth: context.depth + 1
14766
+ });
14767
+ for (const nestedSegment of nested) {
14768
+ requirements.push(
14769
+ ...nestedSegment.requirements.map(
14770
+ (entry) => withInnerProvenance(entry, recursive.script, head, commandRedacted)
14771
+ )
14772
+ );
14773
+ for (const signal of nestedSegment.signals) signals.add(signal);
14342
14774
  }
14343
14775
  }
14344
14776
  signals.add("shell.recursive_wrapper");
14345
- if (dynamicEvaluation) {
14346
- signals.add("dynamic_shell_evaluation");
14347
- }
14777
+ signals.add("dynamic_shell_evaluation");
14348
14778
  return shellSegment(commandRedacted, head, requirements, "recursive", signals);
14349
14779
  }
14350
- const dockerComposeScript = extractDockerComposeRunScript(tokens);
14351
- if (dockerComposeScript && opacity !== "opaque" && opacity !== "unparseable") {
14780
+ if (recursive.kind === "dynamic" || recursive.kind === "indeterminate") {
14781
+ const recursiveSignals = [
14782
+ recursive.signal,
14783
+ ...recursive.kind === "dynamic" ? ["dynamic_shell_evaluation"] : []
14784
+ ];
14785
+ requirements.push(
14786
+ processRequirement(recursive.interpreter, "spawn", commandRedacted, recursiveSignals),
14787
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
14788
+ ...recursiveSignals
14789
+ ])
14790
+ );
14791
+ for (const signal of recursiveSignals) signals.add(signal);
14792
+ return shellSegment(
14793
+ commandRedacted,
14794
+ head,
14795
+ requirements,
14796
+ joinEffectOpacity(opacity, "opaque"),
14797
+ signals
14798
+ );
14799
+ }
14800
+ const compose = decodeDockerComposeRun(decoderTokens);
14801
+ if (compose.kind === "recursive" && opacity !== "opaque" && opacity !== "unparseable") {
14352
14802
  requirements.push(
14353
14803
  processRequirement(head, "spawn", commandRedacted, ["process.docker_compose_run"])
14354
14804
  );
14355
- const nested = lowerTopLevelSegments(dockerComposeScript, {
14356
- ...context,
14357
- command: dockerComposeScript,
14358
- env,
14359
- depth: context.depth + 1
14360
- });
14361
- for (const nestedSegment of nested) {
14362
- requirements.push(
14363
- ...nestedSegment.requirements.map(
14364
- (entry) => withInnerProvenance(entry, dockerComposeScript, head, commandRedacted)
14365
- )
14366
- );
14367
- for (const signal of nestedSegment.signals) {
14368
- signals.add(signal);
14805
+ if (compose.script !== "") {
14806
+ const nested = lowerTopLevelSegments(compose.script, {
14807
+ ...context,
14808
+ command: compose.script,
14809
+ env,
14810
+ depth: context.depth + 1
14811
+ });
14812
+ for (const nestedSegment of nested) {
14813
+ requirements.push(
14814
+ ...nestedSegment.requirements.map(
14815
+ (entry) => withInnerProvenance(entry, compose.script, head, commandRedacted)
14816
+ )
14817
+ );
14818
+ for (const signal of nestedSegment.signals) signals.add(signal);
14819
+ opacity = joinNestedOpacity(opacity, nestedSegment);
14369
14820
  }
14370
- opacity = joinNestedOpacity(opacity, nestedSegment);
14371
14821
  }
14372
14822
  signals.add("process.docker_compose_run");
14373
14823
  return shellSegment(commandRedacted, head, requirements, "recursive", signals);
14374
14824
  }
14825
+ if (compose.kind === "dynamic" || compose.kind === "indeterminate") {
14826
+ requirements.push(
14827
+ processRequirement(head, "spawn", commandRedacted, ["process.docker_compose_run"]),
14828
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
14829
+ compose.signal
14830
+ ])
14831
+ );
14832
+ signals.add("process.docker_compose_run");
14833
+ signals.add(compose.signal);
14834
+ return shellSegment(
14835
+ commandRedacted,
14836
+ head,
14837
+ requirements,
14838
+ joinEffectOpacity(opacity, "opaque"),
14839
+ signals
14840
+ );
14841
+ }
14375
14842
  const launcher = resolveLauncherRecipe({
14376
14843
  tokens,
14377
14844
  cwd: context.cwd,
@@ -14583,7 +15050,7 @@ function isMetadataOnlyArgv(argv) {
14583
15050
  return argv.length > 0 && argv.every((token) => METADATA_ONLY_FLAGS.has(token));
14584
15051
  }
14585
15052
  function executableBaseName(head) {
14586
- return path39.basename(head);
15053
+ return path41.basename(head);
14587
15054
  }
14588
15055
  var RAILS_READ_ONLY_SUBCOMMANDS = /* @__PURE__ */ new Set(["routes", "middleware", "stats", "about", "version"]);
14589
15056
  function railsReadOnlySubcommand(args) {
@@ -14594,7 +15061,7 @@ function railsReadOnlySubcommand(args) {
14594
15061
  return RAILS_READ_ONLY_SUBCOMMANDS.has(subcommand);
14595
15062
  }
14596
15063
  function isRubyTestScript(scriptPath) {
14597
- const base = path39.basename(scriptPath);
15064
+ const base = path41.basename(scriptPath);
14598
15065
  return base.endsWith("_test.rb") || base.endsWith("_spec.rb");
14599
15066
  }
14600
15067
  function parseRubyTestInvocation(args) {
@@ -14779,7 +15246,7 @@ function decodeShellControlBuiltin(head, args) {
14779
15246
  }
14780
15247
  return null;
14781
15248
  }
14782
- function decodeDockerComposeRun(head, args, segment) {
15249
+ function decodeDockerComposeRun2(head, args, segment) {
14783
15250
  let composeArgs = null;
14784
15251
  let command = head;
14785
15252
  if (head === "docker-compose") {
@@ -14873,7 +15340,7 @@ function decodeProcessOrFilesystem(params) {
14873
15340
  requirement2(
14874
15341
  "fs.read",
14875
15342
  "fs.read",
14876
- { kind: "path", path: path39.resolve(cwd, syntax) },
15343
+ { kind: "path", path: path41.resolve(cwd, syntax) },
14877
15344
  segment,
14878
15345
  ["shell.syntax_source_read"]
14879
15346
  )
@@ -14897,7 +15364,7 @@ function decodeProcessOrFilesystem(params) {
14897
15364
  return decoded;
14898
15365
  }
14899
15366
  }
14900
- const dockerCompose = decodeDockerComposeRun(head, args, segment);
15367
+ const dockerCompose = decodeDockerComposeRun2(head, args, segment);
14901
15368
  if (dockerCompose) {
14902
15369
  return dockerCompose;
14903
15370
  }
@@ -14923,7 +15390,7 @@ function decodeProcessOrFilesystem(params) {
14923
15390
  return [processRequirement(head, "inspect", segment, ["process.inspect.base64_stdin"])];
14924
15391
  }
14925
15392
  if (head === "node") {
14926
- return decodeNode(args, cwd, segment);
15393
+ return decodeNode2(args, cwd, segment);
14927
15394
  }
14928
15395
  if (head === "vite" || head === "vite-node") {
14929
15396
  return [processRequirement(head, "spawn", segment, ["process.local_dev_spawn"])];
@@ -15013,7 +15480,7 @@ function decodeBelay(args, repoRoot, segment) {
15013
15480
  requirement2(
15014
15481
  "control_plane.write",
15015
15482
  "control_plane.write",
15016
- { kind: "path", path: path39.join(repoRoot, ".belay-control-plane") },
15483
+ { kind: "path", path: path41.join(repoRoot, ".belay-control-plane") },
15017
15484
  segment,
15018
15485
  ["belay.config_non_judge_mutation"]
15019
15486
  )
@@ -15175,11 +15642,11 @@ function decodeRm(args, cwd, repoRoot, segment) {
15175
15642
  function canonicalRmOperand(targetPath, finalOperandIsSymlink) {
15176
15643
  try {
15177
15644
  if (finalOperandIsSymlink) {
15178
- return path39.join(realpathSync4.native(path39.dirname(targetPath)), path39.basename(targetPath));
15645
+ return path41.join(realpathSync4.native(path41.dirname(targetPath)), path41.basename(targetPath));
15179
15646
  }
15180
15647
  return realpathSync4.native(targetPath);
15181
15648
  } catch {
15182
- return path39.resolve(targetPath);
15649
+ return path41.resolve(targetPath);
15183
15650
  }
15184
15651
  }
15185
15652
  function isSymbolicLink(targetPath) {
@@ -15190,8 +15657,8 @@ function isSymbolicLink(targetPath) {
15190
15657
  }
15191
15658
  }
15192
15659
  function pathContains(ancestor, candidate) {
15193
- const relative = path39.relative(path39.resolve(ancestor), path39.resolve(candidate));
15194
- return relative === "" || !relative.startsWith("..") && !path39.isAbsolute(relative);
15660
+ const relative = path41.relative(path41.resolve(ancestor), path41.resolve(candidate));
15661
+ return relative === "" || !relative.startsWith("..") && !path41.isAbsolute(relative);
15195
15662
  }
15196
15663
  function decodeGo(args, segment) {
15197
15664
  if (["test", "list", "vet"].includes(args[0] ?? "")) {
@@ -15337,7 +15804,7 @@ function decodeSed(args, cwd, segment) {
15337
15804
  }
15338
15805
  return lowered;
15339
15806
  }
15340
- function decodeNode(args, cwd, segment) {
15807
+ function decodeNode2(args, cwd, segment) {
15341
15808
  if (args.length > 0 && args.every((arg) => ["--help", "--version", "-h", "-v"].includes(arg))) {
15342
15809
  return [processRequirement("node", "inspect", segment, ["process.inspect.node_metadata"])];
15343
15810
  }
@@ -15603,15 +16070,36 @@ function stripRedirects(tokens) {
15603
16070
  stripped.push(token);
15604
16071
  continue;
15605
16072
  }
15606
- if (token.includes(">") || token.includes("<")) {
15607
- const inline = token.replace(/^\d*(?:>>?|<<?|<>|>\|)/, "");
15608
- if (!inline) {
15609
- index += 1;
15610
- }
16073
+ index += 1;
16074
+ }
16075
+ return stripped;
16076
+ }
16077
+ function stripStructuredRedirects(tokens) {
16078
+ const stripped = [];
16079
+ for (let index = 0; index < tokens.length; index += 1) {
16080
+ const token = tokens[index];
16081
+ if (!token) continue;
16082
+ if (isFdDuplication(token.value)) {
16083
+ continue;
16084
+ }
16085
+ if (!isRedirectOperator(token.value)) {
16086
+ stripped.push(token);
16087
+ continue;
15611
16088
  }
16089
+ index += 1;
15612
16090
  }
15613
16091
  return stripped;
15614
16092
  }
16093
+ function alignStructuredTokens(tokens, values) {
16094
+ if (values.length === 0) return [];
16095
+ for (let start = tokens.length - values.length; start >= 0; start -= 1) {
16096
+ const candidate = tokens.slice(start);
16097
+ if (candidate.length === values.length && candidate.every((token, index) => token.value === values[index])) {
16098
+ return candidate;
16099
+ }
16100
+ }
16101
+ return [];
16102
+ }
15615
16103
  function shellSegment(commandRedacted, segmentHead, requirements, opacity, signals) {
15616
16104
  const normalizedRequirements = requirements.flatMap((entry) => {
15617
16105
  const dynamicSignal = dynamicResourceSignal(entry.resource);
@@ -16011,9 +16499,9 @@ function resolvePathOperand(operand, cwd) {
16011
16499
  return process.env.HOME ?? operand;
16012
16500
  }
16013
16501
  if (operand.startsWith("~/")) {
16014
- return path39.join(process.env.HOME ?? "~", operand.slice(2));
16502
+ return path41.join(process.env.HOME ?? "~", operand.slice(2));
16015
16503
  }
16016
- return path39.resolve(cwd, operand);
16504
+ return path41.resolve(cwd, operand);
16017
16505
  }
16018
16506
  function isShellHead(head) {
16019
16507
  return head === "bash" || head === "sh" || head === "zsh" || head === "dash" || head === "fish";
@@ -16537,7 +17025,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
16537
17025
  };
16538
17026
  }
16539
17027
  const signals = [];
16540
- const resolvedPath = path40.isAbsolute(filePath) ? filePath : path40.resolve(cwd, filePath);
17028
+ const resolvedPath = path42.isAbsolute(filePath) ? filePath : path42.resolve(cwd, filePath);
16541
17029
  const hitsProtectedRoot = protectedRoots.some((root) => pathWithinRoot(root, resolvedPath));
16542
17030
  if (hitsProtectedRoot) {
16543
17031
  signals.push("control_plane_path");
@@ -17139,7 +17627,7 @@ function hashDecisionConfig(config) {
17139
17627
  init_fingerprint2();
17140
17628
 
17141
17629
  // src/version.ts
17142
- var PACKAGE_VERSION = "0.9.2";
17630
+ var PACKAGE_VERSION = "0.9.3";
17143
17631
 
17144
17632
  // src/runtime-provenance.ts
17145
17633
  function resolveRuntimeArtifactHash(artifactHash) {
@@ -17214,7 +17702,7 @@ init_path_utils();
17214
17702
  import { randomUUID as randomUUID5 } from "node:crypto";
17215
17703
  import { existsSync as existsSync15 } from "node:fs";
17216
17704
  import { mkdir as mkdir11, readdir as readdir3, readFile as readFile12, rename as rename3, rm as rm6 } from "node:fs/promises";
17217
- import path45 from "node:path";
17705
+ import path47 from "node:path";
17218
17706
 
17219
17707
  // src/core/recovery/artifact-store.ts
17220
17708
  init_fingerprint2();
@@ -17222,7 +17710,7 @@ init_path_utils();
17222
17710
  import { randomUUID as randomUUID4 } from "node:crypto";
17223
17711
  import { existsSync as existsSync13 } from "node:fs";
17224
17712
  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";
17225
- import path42 from "node:path";
17713
+ import path44 from "node:path";
17226
17714
 
17227
17715
  // src/core/recovery/snapshot-node.ts
17228
17716
  init_fingerprint2();
@@ -17242,25 +17730,25 @@ import {
17242
17730
  symlink as symlink3,
17243
17731
  writeFile as writeFile6
17244
17732
  } from "node:fs/promises";
17245
- import path41 from "node:path";
17733
+ import path43 from "node:path";
17246
17734
  var RECOVERY_UNSUPPORTED_FILE_KIND = "recovery_unsupported_file_kind";
17247
17735
  function validRecoveryRelativePath(relativePath) {
17248
- if (!relativePath || relativePath.includes("\0") || path41.isAbsolute(relativePath)) return false;
17249
- const normalized = path41.normalize(relativePath);
17250
- return normalized !== "." && normalized !== ".." && !normalized.startsWith(`..${path41.sep}`);
17736
+ if (!relativePath || relativePath.includes("\0") || path43.isAbsolute(relativePath)) return false;
17737
+ const normalized = path43.normalize(relativePath);
17738
+ return normalized !== "." && normalized !== ".." && !normalized.startsWith(`..${path43.sep}`);
17251
17739
  }
17252
17740
  async function assertRecoverySafeTarget(resourceRoot, relativePath) {
17253
17741
  if (!validRecoveryRelativePath(relativePath)) throw new Error("recovery_path_escape");
17254
17742
  const root = canonicalPath(resourceRoot);
17255
- const target = path41.resolve(root, relativePath);
17256
- const relative = path41.relative(root, target);
17257
- if (relative === ".." || relative.startsWith(`..${path41.sep}`) || path41.isAbsolute(relative)) {
17743
+ const target = path43.resolve(root, relativePath);
17744
+ const relative = path43.relative(root, target);
17745
+ if (relative === ".." || relative.startsWith(`..${path43.sep}`) || path43.isAbsolute(relative)) {
17258
17746
  throw new Error("recovery_path_escape");
17259
17747
  }
17260
17748
  let current = root;
17261
- const parentParts = path41.relative(root, path41.dirname(target)).split(path41.sep).filter(Boolean);
17749
+ const parentParts = path43.relative(root, path43.dirname(target)).split(path43.sep).filter(Boolean);
17262
17750
  for (const part of parentParts) {
17263
- current = path41.join(current, part);
17751
+ current = path43.join(current, part);
17264
17752
  if (!existsSync12(current)) break;
17265
17753
  const info = await lstat6(current);
17266
17754
  if (info.isSymbolicLink()) throw new Error("recovery_symlink_escape");
@@ -17316,7 +17804,7 @@ async function captureRecoverySnapshot(filePath, options) {
17316
17804
  let blob;
17317
17805
  if (options?.blobDir) {
17318
17806
  await mkdir9(options.blobDir, { recursive: true, mode: 448 });
17319
- const blobPath = path41.join(options.blobDir, hash);
17807
+ const blobPath = path43.join(options.blobDir, hash);
17320
17808
  if (!existsSync12(blobPath)) {
17321
17809
  await writeFile6(blobPath, content, { mode: 384 });
17322
17810
  await fsyncPath(blobPath);
@@ -17371,7 +17859,7 @@ async function validateRecoverySnapshot(params) {
17371
17859
  if (record.blob !== `blobs/${record.hash}`) throw new Error(params.corruptReason);
17372
17860
  let content;
17373
17861
  try {
17374
- content = await readFile8(path41.join(params.artifactDir, record.blob));
17862
+ content = await readFile8(path43.join(params.artifactDir, record.blob));
17375
17863
  } catch {
17376
17864
  throw new Error(params.corruptReason);
17377
17865
  }
@@ -17399,13 +17887,13 @@ var RECOVERY_STATES = /* @__PURE__ */ new Set([
17399
17887
  ]);
17400
17888
  var STAGING_STALE_MS = 5 * 6e4;
17401
17889
  function checkpointsRoot(stateDir) {
17402
- return path42.join(stateDir, "recovery", "checkpoints");
17890
+ return path44.join(stateDir, "recovery", "checkpoints");
17403
17891
  }
17404
17892
  function checkpointDir(stateDir, checkpointId) {
17405
17893
  if (!/^cp_[a-f0-9]{24}$/.test(checkpointId)) {
17406
17894
  throw new Error("invalid_recovery_checkpoint_id");
17407
17895
  }
17408
- return path42.join(checkpointsRoot(stateDir), checkpointId);
17896
+ return path44.join(checkpointsRoot(stateDir), checkpointId);
17409
17897
  }
17410
17898
  async function fsyncPath2(filePath) {
17411
17899
  const handle = await open5(filePath, "r");
@@ -17416,13 +17904,13 @@ async function fsyncPath2(filePath) {
17416
17904
  }
17417
17905
  }
17418
17906
  async function atomicWriteJson(filePath, value) {
17419
- await mkdir10(path42.dirname(filePath), { recursive: true, mode: 448 });
17907
+ await mkdir10(path44.dirname(filePath), { recursive: true, mode: 448 });
17420
17908
  const temporary = `${filePath}.tmp-${randomUUID4()}`;
17421
17909
  await writeFile7(temporary, `${JSON.stringify(value, null, 2)}
17422
17910
  `, { mode: 384 });
17423
17911
  await fsyncPath2(temporary);
17424
17912
  await rename2(temporary, filePath);
17425
- await fsyncPath2(path42.dirname(filePath));
17913
+ await fsyncPath2(path44.dirname(filePath));
17426
17914
  }
17427
17915
  async function writeRecoveryState(artifactDir, state, manifestHash, detail) {
17428
17916
  const value = {
@@ -17432,13 +17920,13 @@ async function writeRecoveryState(artifactDir, state, manifestHash, detail) {
17432
17920
  manifestHash,
17433
17921
  ...detail ? { detail } : {}
17434
17922
  };
17435
- await atomicWriteJson(path42.join(artifactDir, "state.json"), value);
17923
+ await atomicWriteJson(path44.join(artifactDir, "state.json"), value);
17436
17924
  }
17437
17925
  async function directorySize(root) {
17438
17926
  if (!existsSync13(root)) return 0;
17439
17927
  let total = 0;
17440
17928
  for (const entry of await readdir2(root, { withFileTypes: true })) {
17441
- const entryPath = path42.join(root, entry.name);
17929
+ const entryPath = path44.join(root, entry.name);
17442
17930
  if (entry.isDirectory()) total += await directorySize(entryPath);
17443
17931
  else total += (await lstat7(entryPath)).size;
17444
17932
  }
@@ -17483,9 +17971,9 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
17483
17971
  let rawManifest;
17484
17972
  let state;
17485
17973
  try {
17486
- rawManifest = JSON.parse(await readFile9(path42.join(artifactDir, "manifest.json"), "utf8"));
17974
+ rawManifest = JSON.parse(await readFile9(path44.join(artifactDir, "manifest.json"), "utf8"));
17487
17975
  state = JSON.parse(
17488
- await readFile9(path42.join(artifactDir, "state.json"), "utf8")
17976
+ await readFile9(path44.join(artifactDir, "state.json"), "utf8")
17489
17977
  );
17490
17978
  } catch {
17491
17979
  throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
@@ -17501,10 +17989,10 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
17501
17989
  }
17502
17990
  const entryPaths = /* @__PURE__ */ new Set();
17503
17991
  for (const entry of manifest.entries) {
17504
- 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))) {
17992
+ 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))) {
17505
17993
  throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
17506
17994
  }
17507
- entryPaths.add(path42.normalize(entry.path));
17995
+ entryPaths.add(path44.normalize(entry.path));
17508
17996
  for (const [side, snapshot] of [
17509
17997
  ["before", entry.before],
17510
17998
  ["after", entry.after]
@@ -17518,7 +18006,7 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
17518
18006
  });
17519
18007
  }
17520
18008
  }
17521
- const receiptPath = path42.join(artifactDir, "receipt.json");
18009
+ const receiptPath = path44.join(artifactDir, "receipt.json");
17522
18010
  let receipt;
17523
18011
  if (["applied", "restoring", "restored", "conflict"].includes(state.state) || existsSync13(receiptPath)) {
17524
18012
  receipt = await readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash);
@@ -17528,7 +18016,7 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
17528
18016
  async function readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash) {
17529
18017
  let rawReceipt;
17530
18018
  try {
17531
- rawReceipt = JSON.parse(await readFile9(path42.join(artifactDir, "receipt.json"), "utf8"));
18019
+ rawReceipt = JSON.parse(await readFile9(path44.join(artifactDir, "receipt.json"), "utf8"));
17532
18020
  } catch {
17533
18021
  throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
17534
18022
  }
@@ -17551,7 +18039,7 @@ async function readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHas
17551
18039
  return receipt;
17552
18040
  }
17553
18041
  async function ensureRecoveryReceipt(artifactDir, manifest, manifestHash) {
17554
- const receiptPath = path42.join(artifactDir, "receipt.json");
18042
+ const receiptPath = path44.join(artifactDir, "receipt.json");
17555
18043
  if (existsSync13(receiptPath)) {
17556
18044
  return readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash);
17557
18045
  }
@@ -17576,7 +18064,7 @@ async function artifactRepoRoot(stateDir, checkpointId) {
17576
18064
  const artifactDir = checkpointDir(stateDir, checkpointId);
17577
18065
  try {
17578
18066
  const manifest = JSON.parse(
17579
- await readFile9(path42.join(artifactDir, "manifest.json"), "utf8")
18067
+ await readFile9(path44.join(artifactDir, "manifest.json"), "utf8")
17580
18068
  );
17581
18069
  if (typeof manifest.repoRoot === "string" && manifest.repoRoot) {
17582
18070
  return canonicalPath(manifest.repoRoot);
@@ -17584,7 +18072,7 @@ async function artifactRepoRoot(stateDir, checkpointId) {
17584
18072
  } catch {
17585
18073
  }
17586
18074
  try {
17587
- const owner = JSON.parse(await readFile9(path42.join(artifactDir, "owner.json"), "utf8"));
18075
+ const owner = JSON.parse(await readFile9(path44.join(artifactDir, "owner.json"), "utf8"));
17588
18076
  return typeof owner.repoRoot === "string" && owner.repoRoot ? canonicalPath(owner.repoRoot) : null;
17589
18077
  } catch {
17590
18078
  return null;
@@ -17604,10 +18092,10 @@ async function cleanupOrphanedStaging(stateDir) {
17604
18092
  const now = Date.now();
17605
18093
  for (const entry of await readdir2(root, { withFileTypes: true })) {
17606
18094
  if (!entry.isDirectory() || !/^\.tmp-cp_[a-f0-9]{24}$/.test(entry.name)) continue;
17607
- const stagingPath = path42.join(root, entry.name);
18095
+ const stagingPath = path44.join(root, entry.name);
17608
18096
  let stale = false;
17609
18097
  try {
17610
- const owner = JSON.parse(await readFile9(path42.join(stagingPath, "owner.json"), "utf8"));
18098
+ const owner = JSON.parse(await readFile9(path44.join(stagingPath, "owner.json"), "utf8"));
17611
18099
  const pid = typeof owner.pid === "number" ? owner.pid : Number.NaN;
17612
18100
  const createdAt = typeof owner.createdAt === "string" ? Date.parse(owner.createdAt) : NaN;
17613
18101
  let alive = false;
@@ -17649,7 +18137,7 @@ async function markRecoveryCheckpointApplied(stateDir, checkpoint) {
17649
18137
  init_fingerprint2();
17650
18138
  import { existsSync as existsSync14 } from "node:fs";
17651
18139
  import { readFile as readFile10 } from "node:fs/promises";
17652
- import path43 from "node:path";
18140
+ import path45 from "node:path";
17653
18141
  async function matchRecoverySide(resourceRoot, entries, side) {
17654
18142
  for (const entry of entries) {
17655
18143
  const target = await assertRecoverySafeTarget(resourceRoot, entry.path);
@@ -17664,7 +18152,7 @@ async function reconcileRecoveryCheckpoint(stateDir, checkpointId) {
17664
18152
  } catch {
17665
18153
  const artifactDir = checkpointDir(stateDir, checkpointId);
17666
18154
  if (existsSync14(artifactDir)) {
17667
- const manifestPath = path43.join(artifactDir, "manifest.json");
18155
+ const manifestPath = path45.join(artifactDir, "manifest.json");
17668
18156
  const hash = existsSync14(manifestPath) ? hashValue(await readFile10(manifestPath, "utf8")) : "unavailable";
17669
18157
  await writeRecoveryState(artifactDir, "corrupt", hash, RECOVERY_CHECKPOINT_CORRUPT);
17670
18158
  }
@@ -17701,7 +18189,7 @@ async function reconcileRecoveryCheckpoint(stateDir, checkpointId) {
17701
18189
  // src/core/recovery/resource-identity.ts
17702
18190
  init_fingerprint2();
17703
18191
  import { lstat as lstat8, readFile as readFile11, realpath as realpath3 } from "node:fs/promises";
17704
- import path44 from "node:path";
18192
+ import path46 from "node:path";
17705
18193
  async function currentRecoveryResourceIdentity(resourceRoot, resourceKind) {
17706
18194
  const resolvedRoot = await realpath3(resourceRoot);
17707
18195
  if (resourceKind === "directory") {
@@ -17709,13 +18197,13 @@ async function currentRecoveryResourceIdentity(resourceRoot, resourceKind) {
17709
18197
  if (!rootInfo.isDirectory()) throw new Error("recovery_repo_identity_unavailable");
17710
18198
  return hashValue(`${resolvedRoot}\0${rootInfo.dev}:${rootInfo.ino}:${rootInfo.birthtimeMs}`);
17711
18199
  }
17712
- const dotGit = path44.join(resolvedRoot, ".git");
18200
+ const dotGit = path46.join(resolvedRoot, ".git");
17713
18201
  const gitInfo = await lstat8(dotGit);
17714
18202
  let gitMetadataPath = dotGit;
17715
18203
  if (gitInfo.isFile()) {
17716
18204
  const marker = (await readFile11(dotGit, "utf8")).trim();
17717
18205
  if (!marker.startsWith("gitdir:")) throw new Error("recovery_repo_identity_unavailable");
17718
- gitMetadataPath = path44.resolve(resolvedRoot, marker.slice("gitdir:".length).trim());
18206
+ gitMetadataPath = path46.resolve(resolvedRoot, marker.slice("gitdir:".length).trim());
17719
18207
  } else if (!gitInfo.isDirectory()) {
17720
18208
  throw new Error("recovery_repo_identity_unavailable");
17721
18209
  }
@@ -17788,16 +18276,16 @@ async function prepareRecoveryCheckpoint(params) {
17788
18276
  throw new Error(RECOVERY_CHECKPOINT_QUOTA);
17789
18277
  }
17790
18278
  const checkpointId = `cp_${randomUUID5().replaceAll("-", "").slice(0, 24)}`;
17791
- const temporary = path45.join(checkpointsRoot(params.stateDir), `.tmp-${checkpointId}`);
18279
+ const temporary = path47.join(checkpointsRoot(params.stateDir), `.tmp-${checkpointId}`);
17792
18280
  const finalDir = checkpointDir(params.stateDir, checkpointId);
17793
18281
  await mkdir11(temporary, { recursive: true, mode: 448 });
17794
- await atomicWriteJson(path45.join(temporary, "owner.json"), {
18282
+ await atomicWriteJson(path47.join(temporary, "owner.json"), {
17795
18283
  version: 1,
17796
18284
  pid: process.pid,
17797
18285
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
17798
18286
  repoRoot: canonicalPath(params.repoRoot)
17799
18287
  });
17800
- await mkdir11(path45.join(temporary, "blobs"), { recursive: true, mode: 448 });
18288
+ await mkdir11(path47.join(temporary, "blobs"), { recursive: true, mode: 448 });
17801
18289
  try {
17802
18290
  const entries = [];
17803
18291
  const protectedRoots = (params.protectedRoots ?? []).map(canonicalPath);
@@ -17806,8 +18294,8 @@ async function prepareRecoveryCheckpoint(params) {
17806
18294
  )) {
17807
18295
  const target = await assertRecoverySafeTarget(params.repoRoot, change.relativePath);
17808
18296
  if (protectedRoots.some((root) => {
17809
- const relative = path45.relative(root, target);
17810
- return relative === "" || relative !== ".." && !relative.startsWith(`..${path45.sep}`) && !path45.isAbsolute(relative);
18297
+ const relative = path47.relative(root, target);
18298
+ return relative === "" || relative !== ".." && !relative.startsWith(`..${path47.sep}`) && !path47.isAbsolute(relative);
17811
18299
  })) {
17812
18300
  throw new Error("recovery_protected_path");
17813
18301
  }
@@ -17819,7 +18307,7 @@ async function prepareRecoveryCheckpoint(params) {
17819
18307
  entries.push({
17820
18308
  path: change.relativePath,
17821
18309
  before: await captureRecoverySnapshot(baseline, {
17822
- blobDir: path45.join(temporary, "blobs")
18310
+ blobDir: path47.join(temporary, "blobs")
17823
18311
  }),
17824
18312
  after: withoutRecoveryBlob(await captureRecoverySnapshot(source))
17825
18313
  });
@@ -17856,7 +18344,7 @@ async function prepareRecoveryCheckpoint(params) {
17856
18344
  entries
17857
18345
  };
17858
18346
  const manifestHash = hashValue(canonicalStringify(manifest));
17859
- await atomicWriteJson(path45.join(temporary, "manifest.json"), manifest);
18347
+ await atomicWriteJson(path47.join(temporary, "manifest.json"), manifest);
17860
18348
  await writeRecoveryState(temporary, "prepared", manifestHash);
17861
18349
  await fsyncPath2(temporary);
17862
18350
  const projectedBytes = await recoveryCheckpointStorageBytes(params.stateDir, params.repoRoot);
@@ -17899,7 +18387,7 @@ async function listRecoveryCheckpoints(stateDir, repoRoot) {
17899
18387
  } catch {
17900
18388
  try {
17901
18389
  const raw = JSON.parse(
17902
- await readFile12(path45.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
18390
+ await readFile12(path47.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
17903
18391
  );
17904
18392
  rootFromArtifact = typeof raw.repoRoot === "string" && raw.repoRoot ? raw.repoRoot : void 0;
17905
18393
  } catch {
@@ -17933,7 +18421,7 @@ async function listRecoveryCheckpoints(stateDir, repoRoot) {
17933
18421
  } catch {
17934
18422
  try {
17935
18423
  const manifest = JSON.parse(
17936
- await readFile12(path45.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
18424
+ await readFile12(path47.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
17937
18425
  );
17938
18426
  if (manifest.checkpointId !== id || ![1, 2].includes(manifest.version)) continue;
17939
18427
  if (repoRoot && canonicalPath(manifest.repoRoot) !== canonicalPath(repoRoot)) continue;
@@ -17963,7 +18451,7 @@ async function recoveryCheckpointStorageBytes(stateDir, repoRoot) {
17963
18451
  let total = 0;
17964
18452
  for (const entry of await readdir3(root, { withFileTypes: true })) {
17965
18453
  if (!entry.isDirectory()) continue;
17966
- const entryPath = path45.join(root, entry.name);
18454
+ const entryPath = path47.join(root, entry.name);
17967
18455
  if (/^cp_[a-f0-9]{24}$/.test(entry.name)) {
17968
18456
  if (await artifactRepoRoot(stateDir, entry.name) === expected) {
17969
18457
  total += await directorySize(entryPath);
@@ -17972,7 +18460,7 @@ async function recoveryCheckpointStorageBytes(stateDir, repoRoot) {
17972
18460
  }
17973
18461
  if (/^\.tmp-cp_[a-f0-9]{24}$/.test(entry.name)) {
17974
18462
  try {
17975
- const owner = JSON.parse(await readFile12(path45.join(entryPath, "owner.json"), "utf8"));
18463
+ const owner = JSON.parse(await readFile12(path47.join(entryPath, "owner.json"), "utf8"));
17976
18464
  if (typeof owner.repoRoot === "string" && canonicalPath(owner.repoRoot) === expected) {
17977
18465
  total += await directorySize(entryPath);
17978
18466
  }
@@ -18016,14 +18504,14 @@ init_scrub();
18016
18504
  // src/core/transactional/file-checkpoint-backend.ts
18017
18505
  import { cp, lstat as lstat11, mkdir as mkdir13, mkdtemp as mkdtemp5, readdir as readdir6, rm as rm9, writeFile as writeFile10 } from "node:fs/promises";
18018
18506
  import os5 from "node:os";
18019
- import path49 from "node:path";
18507
+ import path51 from "node:path";
18020
18508
 
18021
18509
  // src/core/transactional/file-checkpoint-git.ts
18022
18510
  init_path_utils();
18023
18511
  import { spawn as spawn8 } from "node:child_process";
18024
18512
  import { createHash as createHash13 } from "node:crypto";
18025
18513
  import { copyFile as copyFile3, lstat as lstat9, readdir as readdir4, readFile as readFile13 } from "node:fs/promises";
18026
- import path46 from "node:path";
18514
+ import path48 from "node:path";
18027
18515
  var FILE_CHECKPOINT_GIT_METADATA_CHANGED = "file_checkpoint_git_metadata_changed";
18028
18516
  var FILE_CHECKPOINT_SOURCE_CHANGED = "file_checkpoint_source_changed";
18029
18517
  var FILE_CHECKPOINT_CWD_OUTSIDE_ROOT = "file_checkpoint_cwd_outside_root";
@@ -18057,7 +18545,7 @@ function rethrowStableFileCheckpointError(error) {
18057
18545
  }
18058
18546
  async function rootGitMetadataPresent(repoRoot) {
18059
18547
  try {
18060
- await lstat9(path46.join(repoRoot, ".git"));
18548
+ await lstat9(path48.join(repoRoot, ".git"));
18061
18549
  return true;
18062
18550
  } catch {
18063
18551
  return false;
@@ -18106,10 +18594,10 @@ function execGit2(repoRoot, args) {
18106
18594
  }
18107
18595
  async function resolveGitPath(repoRoot, gitPath) {
18108
18596
  const trimmed = gitPath.trim();
18109
- if (path46.isAbsolute(trimmed)) {
18597
+ if (path48.isAbsolute(trimmed)) {
18110
18598
  return trimmed;
18111
18599
  }
18112
- return path46.join(repoRoot, trimmed);
18600
+ return path48.join(repoRoot, trimmed);
18113
18601
  }
18114
18602
  async function cloneBareWorktreeCopy(sourceRoot, destinationRoot) {
18115
18603
  await execGit2(sourceRoot, [
@@ -18151,7 +18639,7 @@ async function copyGitIndexState(sourceRoot, destinationRoot) {
18151
18639
  destinationRoot,
18152
18640
  await execGit2(destinationRoot, ["rev-parse", "--git-dir"])
18153
18641
  );
18154
- const destinationShared = path46.join(destinationGitDir, path46.basename(sourceShared));
18642
+ const destinationShared = path48.join(destinationGitDir, path48.basename(sourceShared));
18155
18643
  try {
18156
18644
  await copyFile3(sourceShared, destinationShared);
18157
18645
  } catch (error) {
@@ -18160,7 +18648,7 @@ async function copyGitIndexState(sourceRoot, destinationRoot) {
18160
18648
  }
18161
18649
  async function readGitFile(gitDir, relativePath) {
18162
18650
  try {
18163
- return await readFile13(path46.join(gitDir, relativePath));
18651
+ return await readFile13(path48.join(gitDir, relativePath));
18164
18652
  } catch {
18165
18653
  return null;
18166
18654
  }
@@ -18184,8 +18672,8 @@ async function hashResolvedGitPath(repoRoot, gitDir, gitPath, hash) {
18184
18672
  repoRoot,
18185
18673
  await execGit2(repoRoot, ["rev-parse", "--git-path", gitPath])
18186
18674
  );
18187
- const relative = path46.resolve(resolved).startsWith(path46.resolve(gitDir)) ? path46.relative(gitDir, resolved) : resolved;
18188
- const content = typeof relative === "string" && !relative.startsWith("..") && !path46.isAbsolute(relative) ? await readGitFile(gitDir, relative) : await readAbsoluteGitFile(resolved);
18675
+ const relative = path48.resolve(resolved).startsWith(path48.resolve(gitDir)) ? path48.relative(gitDir, resolved) : resolved;
18676
+ const content = typeof relative === "string" && !relative.startsWith("..") && !path48.isAbsolute(relative) ? await readGitFile(gitDir, relative) : await readAbsoluteGitFile(resolved);
18189
18677
  if (content !== null) {
18190
18678
  hashGitFileContent(hash, gitPath, content);
18191
18679
  }
@@ -18195,13 +18683,13 @@ async function hashResolvedGitPath(repoRoot, gitDir, gitPath, hash) {
18195
18683
  async function hashGitTree(gitDir, relativeDir, hash) {
18196
18684
  let names;
18197
18685
  try {
18198
- names = await readdir4(path46.join(gitDir, relativeDir));
18686
+ names = await readdir4(path48.join(gitDir, relativeDir));
18199
18687
  } catch {
18200
18688
  return;
18201
18689
  }
18202
18690
  for (const name of names.sort()) {
18203
- const relativePath = relativeDir ? path46.join(relativeDir, name) : name;
18204
- const absolutePath = path46.join(gitDir, relativePath);
18691
+ const relativePath = relativeDir ? path48.join(relativeDir, name) : name;
18692
+ const absolutePath = path48.join(gitDir, relativePath);
18205
18693
  let childNames = null;
18206
18694
  try {
18207
18695
  childNames = await readdir4(absolutePath);
@@ -18223,7 +18711,7 @@ async function hashGitTree(gitDir, relativeDir, hash) {
18223
18711
  }
18224
18712
  async function computeGitMetadataFingerprint(repoRoot) {
18225
18713
  const gitDirRel = (await execGit2(repoRoot, ["rev-parse", "--git-dir"])).trim();
18226
- const gitDir = path46.isAbsolute(gitDirRel) ? gitDirRel : path46.join(repoRoot, gitDirRel);
18714
+ const gitDir = path48.isAbsolute(gitDirRel) ? gitDirRel : path48.join(repoRoot, gitDirRel);
18227
18715
  const hash = createHash13("sha256");
18228
18716
  for (const file of [
18229
18717
  "HEAD",
@@ -18246,8 +18734,8 @@ async function computeGitMetadataFingerprint(repoRoot) {
18246
18734
  const sharedIndex = (await execGit2(repoRoot, ["rev-parse", "--shared-index-path"])).trim();
18247
18735
  if (sharedIndex) {
18248
18736
  const resolved = await resolveGitPath(repoRoot, sharedIndex);
18249
- const relative = path46.relative(gitDir, resolved);
18250
- const content = relative && !relative.startsWith("..") && !path46.isAbsolute(relative) ? await readGitFile(gitDir, relative) : await readAbsoluteGitFile(resolved);
18737
+ const relative = path48.relative(gitDir, resolved);
18738
+ const content = relative && !relative.startsWith("..") && !path48.isAbsolute(relative) ? await readGitFile(gitDir, relative) : await readAbsoluteGitFile(resolved);
18251
18739
  if (content !== null) {
18252
18740
  hashGitFileContent(hash, "shared-index", content);
18253
18741
  }
@@ -18258,7 +18746,7 @@ async function computeGitMetadataFingerprint(repoRoot) {
18258
18746
  await hashResolvedGitPath(repoRoot, gitDir, gitPath, hash);
18259
18747
  }
18260
18748
  try {
18261
- const rootGitPath = path46.join(repoRoot, ".git");
18749
+ const rootGitPath = path48.join(repoRoot, ".git");
18262
18750
  const rootGitInfo = await lstat9(rootGitPath);
18263
18751
  if (rootGitInfo.isFile()) {
18264
18752
  const content = await readAbsoluteGitFile(rootGitPath);
@@ -18274,14 +18762,14 @@ async function computeGitMetadataFingerprint(repoRoot) {
18274
18762
  function resolveExecutionCwdRelative(resourceRoot, cwd) {
18275
18763
  const resolvedCwd = canonicalPath(cwd);
18276
18764
  const resourceCanonical = canonicalPath(resourceRoot);
18277
- const relative = path46.relative(resourceCanonical, resolvedCwd);
18765
+ const relative = path48.relative(resourceCanonical, resolvedCwd);
18278
18766
  if (relative === "" || relative === ".") {
18279
18767
  return "";
18280
18768
  }
18281
- if (relative.startsWith("..") || path46.isAbsolute(relative)) {
18769
+ if (relative.startsWith("..") || path48.isAbsolute(relative)) {
18282
18770
  throw new Error(FILE_CHECKPOINT_CWD_OUTSIDE_ROOT);
18283
18771
  }
18284
- return relative.split(path46.sep).join("/");
18772
+ return relative.split(path48.sep).join("/");
18285
18773
  }
18286
18774
 
18287
18775
  // src/core/transactional/file-checkpoint-isolation.ts
@@ -18303,7 +18791,7 @@ function fileCheckpointIsolationReason(context) {
18303
18791
 
18304
18792
  // src/core/transactional/file-checkpoint-staging.ts
18305
18793
  import { readdir as readdir5, readFile as readFile14, rm as rm7, writeFile as writeFile8 } from "node:fs/promises";
18306
- import path47 from "node:path";
18794
+ import path49 from "node:path";
18307
18795
  function isOwnerProcessAlive(pid) {
18308
18796
  try {
18309
18797
  process.kill(pid, 0);
@@ -18313,12 +18801,12 @@ function isOwnerProcessAlive(pid) {
18313
18801
  }
18314
18802
  }
18315
18803
  async function writeOwnerMarker(stagingRoot, marker) {
18316
- await writeFile8(path47.join(stagingRoot, "owner.json"), `${JSON.stringify(marker)}
18804
+ await writeFile8(path49.join(stagingRoot, "owner.json"), `${JSON.stringify(marker)}
18317
18805
  `, "utf8");
18318
18806
  }
18319
18807
  async function readOwnerMarker(stagingRoot) {
18320
18808
  try {
18321
- const raw = await readFile14(path47.join(stagingRoot, "owner.json"), "utf8");
18809
+ const raw = await readFile14(path49.join(stagingRoot, "owner.json"), "utf8");
18322
18810
  return JSON.parse(raw.trim());
18323
18811
  } catch {
18324
18812
  return null;
@@ -18336,7 +18824,7 @@ async function collectDeadOwnerStaging(parentDir) {
18336
18824
  if (!name.startsWith("belay-file-checkpoint-")) {
18337
18825
  continue;
18338
18826
  }
18339
- const stagingRoot = path47.join(parentDir, name);
18827
+ const stagingRoot = path49.join(parentDir, name);
18340
18828
  const marker = await readOwnerMarker(stagingRoot);
18341
18829
  if (!marker) {
18342
18830
  dead.push(stagingRoot);
@@ -18368,7 +18856,7 @@ import {
18368
18856
  writeFile as writeFile9
18369
18857
  } from "node:fs/promises";
18370
18858
  import os4 from "node:os";
18371
- import path48 from "node:path";
18859
+ import path50 from "node:path";
18372
18860
  var FILE_CHECKPOINT_COPY_FAILED = "file_checkpoint_copy_failed";
18373
18861
  async function chmodSafe2(target, mode) {
18374
18862
  try {
@@ -18378,7 +18866,7 @@ async function chmodSafe2(target, mode) {
18378
18866
  }
18379
18867
  }
18380
18868
  async function copyRegularFile(sourcePath, destinationPath, mode, strategy) {
18381
- await mkdir12(path48.dirname(destinationPath), { recursive: true });
18869
+ await mkdir12(path50.dirname(destinationPath), { recursive: true });
18382
18870
  if (strategy === "clonefile" && fsConstants2.COPYFILE_FICLONE !== void 0) {
18383
18871
  try {
18384
18872
  await copyFile4(sourcePath, destinationPath, fsConstants2.COPYFILE_FICLONE);
@@ -18404,7 +18892,7 @@ async function copyNode(sourceRoot, destinationRoot, relativePath, strategy) {
18404
18892
  return strategy;
18405
18893
  }
18406
18894
  if (info.isSymbolicLink()) {
18407
- await mkdir12(path48.dirname(destinationPath), { recursive: true });
18895
+ await mkdir12(path50.dirname(destinationPath), { recursive: true });
18408
18896
  await symlink4(await readlink5(sourcePath), destinationPath);
18409
18897
  return strategy;
18410
18898
  }
@@ -18460,9 +18948,9 @@ async function mapWithConcurrency(items, concurrency, worker) {
18460
18948
  async function probeFileCloneStrategy() {
18461
18949
  let tempDir = null;
18462
18950
  try {
18463
- tempDir = await mkdtemp4(path48.join(os4.tmpdir(), "belay-clone-probe-"));
18464
- const source = path48.join(tempDir, "source.txt");
18465
- const destination = path48.join(tempDir, "dest.txt");
18951
+ tempDir = await mkdtemp4(path50.join(os4.tmpdir(), "belay-clone-probe-"));
18952
+ const source = path50.join(tempDir, "source.txt");
18953
+ const destination = path50.join(tempDir, "dest.txt");
18466
18954
  await writeFile9(source, "probe\n");
18467
18955
  if (fsConstants2.COPYFILE_FICLONE_FORCE !== void 0) {
18468
18956
  try {
@@ -18615,11 +19103,11 @@ async function protectedRootState(root) {
18615
19103
  return `directory:${node.hash}:${index.treeHash}`;
18616
19104
  }
18617
19105
  function executionProtectedRoot(resourceRoot, executionRoot, protectedRoot) {
18618
- const relative = path49.relative(path49.resolve(resourceRoot), path49.resolve(protectedRoot));
18619
- if (relative === "" || relative.startsWith("..") || path49.isAbsolute(relative)) {
19106
+ const relative = path51.relative(path51.resolve(resourceRoot), path51.resolve(protectedRoot));
19107
+ if (relative === "" || relative.startsWith("..") || path51.isAbsolute(relative)) {
18620
19108
  return null;
18621
19109
  }
18622
- return path49.join(executionRoot, relative);
19110
+ return path51.join(executionRoot, relative);
18623
19111
  }
18624
19112
  async function captureProtectedRootStates(resourceRoot, executionRoot, protectedRoots) {
18625
19113
  const states = /* @__PURE__ */ new Map();
@@ -18644,15 +19132,15 @@ async function directoryByteSize(root, deadlineMs) {
18644
19132
  }
18645
19133
  let total = 0;
18646
19134
  for (const name of await readdir6(root)) {
18647
- total += await directoryByteSize(path49.join(root, name), deadlineMs);
19135
+ total += await directoryByteSize(path51.join(root, name), deadlineMs);
18648
19136
  }
18649
19137
  return total;
18650
19138
  }
18651
19139
  async function copyGitMetadataDirectory(sourceRoot, destinationRoot) {
18652
19140
  const gitDirRel = (await execGit2(sourceRoot, ["rev-parse", "--git-dir"])).trim();
18653
- const sourceGitDir = path49.isAbsolute(gitDirRel) ? gitDirRel : path49.join(sourceRoot, gitDirRel);
18654
- const relativeGitDir = path49.relative(path49.resolve(sourceRoot), path49.resolve(sourceGitDir));
18655
- const destinationGitDir = relativeGitDir && !relativeGitDir.startsWith("..") ? path49.join(destinationRoot, relativeGitDir) : path49.join(destinationRoot, ".git");
19141
+ const sourceGitDir = path51.isAbsolute(gitDirRel) ? gitDirRel : path51.join(sourceRoot, gitDirRel);
19142
+ const relativeGitDir = path51.relative(path51.resolve(sourceRoot), path51.resolve(sourceGitDir));
19143
+ const destinationGitDir = relativeGitDir && !relativeGitDir.startsWith("..") ? path51.join(destinationRoot, relativeGitDir) : path51.join(destinationRoot, ".git");
18656
19144
  await cp(sourceGitDir, destinationGitDir, { recursive: true, force: true });
18657
19145
  }
18658
19146
  async function prepareDirtyGitSnapshot(context) {
@@ -18661,7 +19149,7 @@ async function prepareDirtyGitSnapshot(context) {
18661
19149
  const quotas = context.fileCheckpoint;
18662
19150
  const deadlineMs = Date.now() + quotas.prepareTimeoutMs;
18663
19151
  await removeDeadOwnerStaging(os5.tmpdir());
18664
- const stagingRoot = await mkdtemp5(path49.join(os5.tmpdir(), "belay-file-checkpoint-"));
19152
+ const stagingRoot = await mkdtemp5(path51.join(os5.tmpdir(), "belay-file-checkpoint-"));
18665
19153
  await writeOwnerMarker(stagingRoot, {
18666
19154
  version: 1,
18667
19155
  pid: process.pid,
@@ -18669,8 +19157,8 @@ async function prepareDirtyGitSnapshot(context) {
18669
19157
  resourceRoot: context.repoRoot,
18670
19158
  backend: "file_checkpoint"
18671
19159
  });
18672
- const baselineRoot = path49.join(stagingRoot, "baseline");
18673
- const executionRoot = path49.join(stagingRoot, "execution");
19160
+ const baselineRoot = path51.join(stagingRoot, "baseline");
19161
+ const executionRoot = path51.join(stagingRoot, "execution");
18674
19162
  try {
18675
19163
  resolveExecutionCwdRelative(context.repoRoot, context.cwd);
18676
19164
  const sourceGitMetadataFingerprint = await computeGitMetadataFingerprint(context.repoRoot);
@@ -18704,7 +19192,7 @@ async function prepareDirtyGitSnapshot(context) {
18704
19192
  throw new Error(FILE_CHECKPOINT_SOURCE_CHANGED);
18705
19193
  }
18706
19194
  await writeFile10(
18707
- path49.join(stagingRoot, "baseline-index.json"),
19195
+ path51.join(stagingRoot, "baseline-index.json"),
18708
19196
  `${JSON.stringify(baselineIndex)}
18709
19197
  `,
18710
19198
  "utf8"
@@ -18754,7 +19242,7 @@ async function prepareNonGitSnapshot(context) {
18754
19242
  const quotas = context.fileCheckpoint;
18755
19243
  const deadlineMs = Date.now() + quotas.prepareTimeoutMs;
18756
19244
  await removeDeadOwnerStaging(os5.tmpdir());
18757
- const stagingRoot = await mkdtemp5(path49.join(os5.tmpdir(), "belay-file-checkpoint-"));
19245
+ const stagingRoot = await mkdtemp5(path51.join(os5.tmpdir(), "belay-file-checkpoint-"));
18758
19246
  await writeOwnerMarker(stagingRoot, {
18759
19247
  version: 1,
18760
19248
  pid: process.pid,
@@ -18762,8 +19250,8 @@ async function prepareNonGitSnapshot(context) {
18762
19250
  resourceRoot: context.repoRoot,
18763
19251
  backend: "file_checkpoint"
18764
19252
  });
18765
- const baselineRoot = path49.join(stagingRoot, "baseline");
18766
- const executionRoot = path49.join(stagingRoot, "execution");
19253
+ const baselineRoot = path51.join(stagingRoot, "baseline");
19254
+ const executionRoot = path51.join(stagingRoot, "execution");
18767
19255
  try {
18768
19256
  resolveExecutionCwdRelative(context.repoRoot, context.cwd);
18769
19257
  const resourceIdentity = await currentRecoveryResourceIdentity(context.repoRoot, "directory");
@@ -18792,7 +19280,7 @@ async function prepareNonGitSnapshot(context) {
18792
19280
  throw new Error(FILE_CHECKPOINT_SOURCE_CHANGED);
18793
19281
  }
18794
19282
  await writeFile10(
18795
- path49.join(stagingRoot, "baseline-index.json"),
19283
+ path51.join(stagingRoot, "baseline-index.json"),
18796
19284
  `${JSON.stringify(baselineIndex)}
18797
19285
  `,
18798
19286
  "utf8"
@@ -19145,10 +19633,10 @@ async function selectTransactionalBackend(context) {
19145
19633
  }
19146
19634
 
19147
19635
  // src/core/transactional/diff-evaluator.ts
19148
- import path50 from "node:path";
19636
+ import path52 from "node:path";
19149
19637
  init_path_utils();
19150
19638
  function categorizeChange(change, ctx) {
19151
- const absolutePath = canonicalPath(path50.join(ctx.repoRoot, change.relativePath));
19639
+ const absolutePath = canonicalPath(path52.join(ctx.repoRoot, change.relativePath));
19152
19640
  if (!pathWithinRoot(ctx.repoRoot, absolutePath)) {
19153
19641
  return "repo_outside";
19154
19642
  }
@@ -19742,7 +20230,7 @@ async function notifyDeny(config, event) {
19742
20230
  init_path_utils();
19743
20231
 
19744
20232
  // src/adapters/layouts/protected-paths.ts
19745
- import path51 from "node:path";
20233
+ import path53 from "node:path";
19746
20234
  function protectedArtifactRoots(layout, repoRoot, controlPlaneDir) {
19747
20235
  const roots = [
19748
20236
  layout.configPath(repoRoot),
@@ -19754,7 +20242,7 @@ function protectedArtifactRoots(layout, repoRoot, controlPlaneDir) {
19754
20242
  if (controlPlaneDir) {
19755
20243
  roots.push(controlPlaneDir);
19756
20244
  }
19757
- return roots.map((entry) => path51.resolve(entry));
20245
+ return roots.map((entry) => path53.resolve(entry));
19758
20246
  }
19759
20247
 
19760
20248
  // src/adapters/shared/gate-runtime.ts
@@ -19807,8 +20295,8 @@ function createDefaultGateRuntimeDeps() {
19807
20295
  return loadJsonFile(configPath, {});
19808
20296
  },
19809
20297
  async appendAudit(ctx, event) {
19810
- const auditPath = path52.join(ctx.repoRoot, ctx.config.audit.logPath);
19811
- await mkdir14(path52.dirname(auditPath), { recursive: true });
20298
+ const auditPath = path54.join(ctx.repoRoot, ctx.config.audit.logPath);
20299
+ await mkdir14(path54.dirname(auditPath), { recursive: true });
19812
20300
  const provenance = auditProvenance(ctx.config);
19813
20301
  const record = {
19814
20302
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -19841,7 +20329,7 @@ function createDefaultGateRuntimeDeps() {
19841
20329
  };
19842
20330
  },
19843
20331
  async writeApprovals(filePath, state) {
19844
- await mkdir14(path52.dirname(filePath), { recursive: true });
20332
+ await mkdir14(path54.dirname(filePath), { recursive: true });
19845
20333
  await writeFile11(filePath, `${JSON.stringify(compactApprovals(state), null, 2)}
19846
20334
  `, "utf8");
19847
20335
  },
@@ -20003,7 +20491,7 @@ function deriveWorkspaceRootScopeHint(params) {
20003
20491
  if (!targetPath) {
20004
20492
  return void 0;
20005
20493
  }
20006
- const candidateRoot = canonicalPath(path52.dirname(targetPath));
20494
+ const candidateRoot = canonicalPath(path54.dirname(targetPath));
20007
20495
  const validation = validateTrustedWorkspaceRootCandidate({
20008
20496
  candidatePath: candidateRoot,
20009
20497
  repoRoot: action.repoRoot,
@@ -20337,6 +20825,7 @@ async function evaluateGatedAction(ctx, deps, params) {
20337
20825
  event: resolveGateAuditEvent(sourceEvent, params.kind),
20338
20826
  sourceEvent,
20339
20827
  kind: params.kind,
20828
+ ...typeof params.payload?.tool_use_id === "string" ? { toolInvocationCorrelationId: toolInvocationCorrelationId(params.payload.tool_use_id) } : {},
20340
20829
  fingerprint: verdict2.fingerprint,
20341
20830
  verdict: verdict2.verdict,
20342
20831
  reason: verdict2.reason,
@@ -20494,6 +20983,7 @@ async function evaluateGatedAction(ctx, deps, params) {
20494
20983
  const scrubbedPayload = fingerprintReplayPayload(params.kind, params.payload, scrubOpts);
20495
20984
  return gateDecisionToVerdict(ctx, deps, params.kind, result, {
20496
20985
  sourceEvent: params.sourceEvent,
20986
+ toolInvocationCorrelationId: typeof params.payload?.tool_use_id === "string" ? toolInvocationCorrelationId(params.payload.tool_use_id) : void 0,
20497
20987
  predictedAssessment,
20498
20988
  observedAssessment: observedAssessment2,
20499
20989
  transactionalLayer,
@@ -20611,6 +21101,7 @@ async function gateDecisionToVerdict(ctx, deps, kind, result, auditExtras = {})
20611
21101
  event: auditEvent,
20612
21102
  sourceEvent,
20613
21103
  kind,
21104
+ ...auditExtras.toolInvocationCorrelationId ? { toolInvocationCorrelationId: auditExtras.toolInvocationCorrelationId } : {},
20614
21105
  fingerprint: result.fingerprint,
20615
21106
  summary: result.normalizedCommand ?? result.summary ?? "",
20616
21107
  assessment: result.assessment,
@@ -21061,29 +21552,56 @@ function gateVerdictToCodexUserPromptResponse(verdict2) {
21061
21552
  };
21062
21553
  }
21063
21554
  async function appendObservedAudit(ctx, deps, eventName, payload) {
21555
+ const rawToolUseId = typeof payload.tool_use_id === "string" ? payload.tool_use_id : void 0;
21556
+ const summaryPayload = redactToolInvocationId(payload, rawToolUseId);
21064
21557
  await deps.appendAudit(ctx, {
21065
21558
  event: eventName,
21066
21559
  kind: "audit",
21067
21560
  verdict: "allow",
21068
21561
  reason: "observed",
21069
- summary: canonicalStringify(payload)
21562
+ ...rawToolUseId ? { toolInvocationCorrelationId: toolInvocationCorrelationId(rawToolUseId) } : {},
21563
+ ...typeof summaryPayload.tool_name === "string" ? { toolName: summaryPayload.tool_name } : {},
21564
+ ...typeof summaryPayload.failure_type === "string" ? { failureType: summaryPayload.failure_type } : {},
21565
+ ...typeof summaryPayload.error_message === "string" ? { errorMessage: summaryPayload.error_message } : {},
21566
+ ...typeof payload.duration === "number" ? { durationMs: payload.duration } : {},
21567
+ ...typeof payload.is_interrupt === "boolean" ? { isInterrupt: payload.is_interrupt } : {},
21568
+ summary: canonicalStringify(summaryPayload)
21070
21569
  });
21071
21570
  }
21072
21571
 
21073
21572
  // src/adapters/shared/repo-root.ts
21074
21573
  import { existsSync as existsSync17 } from "node:fs";
21075
- import path53 from "node:path";
21574
+ import path55 from "node:path";
21575
+ function belayConfigPath(current, adapterName) {
21576
+ if (adapterName === "cursor") {
21577
+ return path55.join(current, ".cursor", "belay.config.json");
21578
+ }
21579
+ if (adapterName === "claude") {
21580
+ return path55.join(current, ".claude", "belay.config.json");
21581
+ }
21582
+ return path55.join(current, ".codex", "belay.config.json");
21583
+ }
21584
+ function markerMatches(current, marker, layout) {
21585
+ const markerPath = path55.join(current, marker);
21586
+ if (!existsSync17(markerPath)) {
21587
+ return false;
21588
+ }
21589
+ if (marker === ".cursor" || marker === ".claude" || marker === ".codex") {
21590
+ return existsSync17(belayConfigPath(current, layout.name));
21591
+ }
21592
+ return true;
21593
+ }
21076
21594
  function findRepoRoot(startPath, layout) {
21077
- let current = path53.resolve(startPath);
21595
+ let current = path55.resolve(startPath);
21078
21596
  while (true) {
21079
21597
  for (const marker of layout.repoRootMarkers) {
21080
- if (existsSync17(path53.join(current, marker))) {
21598
+ if (markerMatches(current, marker, layout)) {
21081
21599
  return current;
21082
21600
  }
21083
21601
  }
21084
- const parent = path53.dirname(current);
21602
+ const parent = path55.dirname(current);
21085
21603
  if (parent === current) {
21086
- return path53.resolve(startPath);
21604
+ return path55.resolve(startPath);
21087
21605
  }
21088
21606
  current = parent;
21089
21607
  }
@@ -21109,6 +21627,16 @@ function jsonResponse(value) {
21109
21627
  process2.stdout.write(`${JSON.stringify(value)}
21110
21628
  `);
21111
21629
  }
21630
+ function nonEmptyPathString(value) {
21631
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
21632
+ }
21633
+ function resolveCodexActionCwd(payload, fallbackCwd = process2.cwd(), options = {}) {
21634
+ const toolInput = payload.tool_input !== null && typeof payload.tool_input === "object" ? payload.tool_input : void 0;
21635
+ const nestedCwd = options.includeToolInputCwd ? nonEmptyPathString(toolInput?.working_directory) ?? nonEmptyPathString(toolInput?.cwd) : void 0;
21636
+ const payloadCwd = nonEmptyPathString(payload.cwd);
21637
+ const workspaceRoot = Array.isArray(payload.workspace_roots) ? payload.workspace_roots.map(nonEmptyPathString).find(Boolean) : void 0;
21638
+ return path56.resolve(nestedCwd ?? payloadCwd ?? workspaceRoot ?? fallbackCwd);
21639
+ }
21112
21640
  async function loadRuntimeContext(cwd) {
21113
21641
  const repoRoot = findRepoRoot(cwd, codexLayout);
21114
21642
  const configPath = codexLayout.configPath(repoRoot);
@@ -21201,7 +21729,8 @@ async function runBeforeSubmitPromptHook() {
21201
21729
  try {
21202
21730
  const payload = await readStdinJson();
21203
21731
  const prompt = String(payload.prompt ?? payload.user_message ?? "");
21204
- const ctx = await loadRuntimeContext(process2.cwd());
21732
+ const cwd = resolveCodexActionCwd(payload);
21733
+ const ctx = await loadRuntimeContext(cwd);
21205
21734
  const deps = createDefaultGateRuntimeDeps();
21206
21735
  const result = await processApprovalPrompt(ctx, deps, prompt);
21207
21736
  jsonResponse(gateVerdictToCodexUserPromptResponse(result));
@@ -21215,9 +21744,10 @@ async function runBeforeSubmitPromptHook() {
21215
21744
  async function runToolGateHook(eventName) {
21216
21745
  try {
21217
21746
  const payload = await readStdinJson();
21218
- const cwd = process2.cwd();
21219
21747
  const toolName = String(payload.tool_name ?? payload.toolName ?? "");
21220
21748
  const kind = resolveCodexGateKind(eventName, toolName);
21749
+ const includeToolInputCwd = (eventName === "PreToolUse" || eventName === "preToolUse") && kind === "shell";
21750
+ const cwd = resolveCodexActionCwd(payload, process2.cwd(), { includeToolInputCwd });
21221
21751
  const ctx = await loadRuntimeContext(cwd);
21222
21752
  const deps = createDefaultGateRuntimeDeps();
21223
21753
  if (!kind) {
@@ -21255,7 +21785,7 @@ async function runShellGateHook() {
21255
21785
  try {
21256
21786
  const payload = await readStdinJson();
21257
21787
  const command = extractString(payload.tool_input, "command") || String(payload.command ?? "");
21258
- const cwd = process2.cwd();
21788
+ const cwd = resolveCodexActionCwd(payload, process2.cwd(), { includeToolInputCwd: true });
21259
21789
  const ctx = await loadRuntimeContext(cwd);
21260
21790
  const deps = createDefaultGateRuntimeDeps();
21261
21791
  const verdict2 = await evaluateGatedAction(ctx, deps, {
@@ -21280,7 +21810,11 @@ async function runShellGateHook() {
21280
21810
  async function runAuditHook(eventName) {
21281
21811
  try {
21282
21812
  const payload = await readStdinJson();
21283
- const ctx = await loadRuntimeContext(process2.cwd());
21813
+ const toolName = String(payload.tool_name ?? payload.toolName ?? "");
21814
+ const kind = resolveCodexGateKind(eventName, toolName);
21815
+ const includeToolInputCwd = (eventName === "PreToolUse" || eventName === "preToolUse") && kind === "shell";
21816
+ const cwd = resolveCodexActionCwd(payload, process2.cwd(), { includeToolInputCwd });
21817
+ const ctx = await loadRuntimeContext(cwd);
21284
21818
  const deps = createDefaultGateRuntimeDeps();
21285
21819
  await appendObservedAudit(ctx, deps, eventName, payload);
21286
21820
  jsonResponse({});
@@ -21293,6 +21827,7 @@ async function runAuditHook(eventName) {
21293
21827
  }
21294
21828
  }
21295
21829
  export {
21830
+ resolveCodexActionCwd,
21296
21831
  runAuditHook,
21297
21832
  runBeforeSubmitPromptHook,
21298
21833
  runShellGateHook,