@guilz-dev/belay 0.9.1 → 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +1 -1
  2. package/dist/adapters/codex/runtime-entry.d.ts +3 -0
  3. package/dist/adapters/codex/runtime-entry.js +27 -4
  4. package/dist/adapters/cursor/cwd-resolution.d.ts +10 -0
  5. package/dist/adapters/cursor/cwd-resolution.js +58 -0
  6. package/dist/adapters/cursor/hooks.d.ts +5 -3
  7. package/dist/adapters/cursor/hooks.js +29 -20
  8. package/dist/adapters/cursor/runtime-entry.d.ts +1 -0
  9. package/dist/adapters/cursor/runtime-entry.js +107 -6
  10. package/dist/adapters/shared/gate-runtime.js +26 -3
  11. package/dist/adapters/shared/repo-root.js +20 -1
  12. package/dist/bundle/claude-runtime.mjs +1225 -287
  13. package/dist/bundle/codex-runtime.mjs +1247 -291
  14. package/dist/bundle/cursor-runtime.mjs +4320 -3154
  15. package/dist/cli.js +33 -3
  16. package/dist/commands/doctor.js +38 -9
  17. package/dist/commands/health-snapshot.d.ts +3 -0
  18. package/dist/commands/health-snapshot.js +56 -0
  19. package/dist/commands/report.js +14 -0
  20. package/dist/commands/status.js +15 -0
  21. package/dist/commands/where.d.ts +4 -0
  22. package/dist/commands/where.js +52 -0
  23. package/dist/core/approval-repo-lookup.d.ts +16 -0
  24. package/dist/core/approval-repo-lookup.js +48 -0
  25. package/dist/core/audit-io.d.ts +1 -1
  26. package/dist/core/audit-io.js +1 -1
  27. package/dist/core/audit-legacy-archive.d.ts +1 -0
  28. package/dist/core/audit-legacy-archive.js +5 -0
  29. package/dist/core/audit-query.d.ts +1 -0
  30. package/dist/core/audit-query.js +7 -0
  31. package/dist/core/audit-serialize.d.ts +3 -0
  32. package/dist/core/audit-serialize.js +39 -3
  33. package/dist/core/audit-summary.d.ts +9 -0
  34. package/dist/core/audit-summary.js +58 -1
  35. package/dist/core/audit-types.d.ts +4 -0
  36. package/dist/core/effect-ir/shell-lower.js +283 -27
  37. package/dist/core/replay-scrub.d.ts +1 -0
  38. package/dist/core/replay-scrub.js +22 -3
  39. package/dist/core/shell-tokenizer.d.ts +28 -0
  40. package/dist/core/shell-tokenizer.js +111 -29
  41. package/dist/core/verdict/docker-compose-run.d.ts +18 -0
  42. package/dist/core/verdict/docker-compose-run.js +136 -0
  43. package/dist/core/verdict/launcher-resolve.js +66 -25
  44. package/dist/core/verdict/makefile-expand.d.ts +4 -0
  45. package/dist/core/verdict/makefile-expand.js +151 -0
  46. package/dist/core/verdict/parser.d.ts +4 -0
  47. package/dist/core/verdict/parser.js +25 -30
  48. package/dist/core/verdict/recursive-invocation.d.ts +20 -0
  49. package/dist/core/verdict/recursive-invocation.js +224 -0
  50. package/dist/corpus/benign-probe-cores.d.ts +1 -1
  51. package/dist/corpus/benign-probe-cores.js +2 -0
  52. package/dist/defaults.js +16 -0
  53. package/dist/installer/scope-config.d.ts +2 -2
  54. package/dist/installer.d.ts +10 -1
  55. package/dist/installer.js +43 -2
  56. package/dist/types.d.ts +34 -1
  57. package/dist/version.d.ts +1 -1
  58. package/dist/version.js +1 -1
  59. package/package.json +5 -2
  60. package/skills/belay/SKILL.md +5 -0
  61. package/skills/belay/belay-report.md +4 -1
@@ -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();
@@ -11120,12 +11245,349 @@ function worstEffectDecision(decisions) {
11120
11245
 
11121
11246
  // src/core/effect-ir/shell-lower.ts
11122
11247
  init_git_resource_identity();
11248
+ init_path_utils();
11123
11249
  init_shell_tokenizer();
11124
11250
  import { lstatSync as lstatSync2, realpathSync as realpathSync4 } from "node:fs";
11125
- import path39 from "node:path";
11251
+ import path41 from "node:path";
11126
11252
 
11127
- // 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
11128
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";
11129
11591
  var CURL_EFFECT_NEUTRAL_FLAGS = /* @__PURE__ */ new Set([
11130
11592
  "-f",
11131
11593
  "-L",
@@ -11160,7 +11622,7 @@ var GH_READ_COMMANDS = /* @__PURE__ */ new Set([
11160
11622
  "workflow view"
11161
11623
  ]);
11162
11624
  function decodeEgressEffects(params) {
11163
- const head = path35.basename(params.tokens[0] ?? "");
11625
+ const head = path37.basename(params.tokens[0] ?? "");
11164
11626
  if (head !== "curl" && head !== "wget" && head !== "gh") {
11165
11627
  return null;
11166
11628
  }
@@ -11168,7 +11630,7 @@ function decodeEgressEffects(params) {
11168
11630
  const provenance = { segment: params.segment };
11169
11631
  const requirements = [];
11170
11632
  for (const file of decoded.files) {
11171
- const resolved = path35.resolve(params.cwd, expandHome2(file));
11633
+ const resolved = path37.resolve(params.cwd, expandHome2(file));
11172
11634
  requirements.push(
11173
11635
  requirement("fs.read", "fs.read", { kind: "path", path: resolved }, params.segment, [
11174
11636
  "egress.explicit_file_read"
@@ -11190,7 +11652,7 @@ function decodeEgressEffects(params) {
11190
11652
  if (file === "-") {
11191
11653
  continue;
11192
11654
  }
11193
- const resolved = path35.resolve(params.cwd, expandHome2(file));
11655
+ const resolved = path37.resolve(params.cwd, expandHome2(file));
11194
11656
  if (resolved === "/dev/null") {
11195
11657
  continue;
11196
11658
  }
@@ -11668,13 +12130,13 @@ function decodeSingleCurlWgetGrammar(head, tokens) {
11668
12130
  if (head === "wget" && !explicitOutput) {
11669
12131
  outputFiles.push(
11670
12132
  ...endpointOutputNames.map(
11671
- (name) => outputDirectory ? path35.join(outputDirectory, name) : name
12133
+ (name) => outputDirectory ? path37.join(outputDirectory, name) : name
11672
12134
  )
11673
12135
  );
11674
12136
  } else if (head === "curl" && remoteNameOutput) {
11675
12137
  outputFiles.push(
11676
12138
  ...endpointOutputNames.map(
11677
- (name) => outputDirectory ? path35.join(outputDirectory, name) : name
12139
+ (name) => outputDirectory ? path37.join(outputDirectory, name) : name
11678
12140
  )
11679
12141
  );
11680
12142
  }
@@ -11688,7 +12150,7 @@ function decodeSingleCurlWgetGrammar(head, tokens) {
11688
12150
  outputFiles: [
11689
12151
  ...new Set(
11690
12152
  outputFiles.map(
11691
- (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
11692
12154
  )
11693
12155
  )
11694
12156
  ],
@@ -11703,7 +12165,7 @@ function remoteOutputName(spec) {
11703
12165
  } catch {
11704
12166
  pathname = spec.split(/[?#]/, 1)[0] ?? "";
11705
12167
  }
11706
- const name = path35.posix.basename(pathname);
12168
+ const name = path37.posix.basename(pathname);
11707
12169
  return name && name !== "/" ? name : "index.html";
11708
12170
  }
11709
12171
  function decodeGhGrammar(tokens) {
@@ -11868,7 +12330,7 @@ function expandHome2(value) {
11868
12330
  return process.env.HOME ?? value;
11869
12331
  }
11870
12332
  if (value.startsWith("~/")) {
11871
- return path35.join(process.env.HOME ?? "~", value.slice(2));
12333
+ return path37.join(process.env.HOME ?? "~", value.slice(2));
11872
12334
  }
11873
12335
  return value;
11874
12336
  }
@@ -11887,7 +12349,7 @@ function requirement(tag, action, resource, segment, signals) {
11887
12349
  }
11888
12350
 
11889
12351
  // src/core/verdict/git-classifier.ts
11890
- import path36 from "node:path";
12352
+ import path38 from "node:path";
11891
12353
  init_shell_tokenizer();
11892
12354
  var GIT_BRANCH_MUTATION_FLAGS = /* @__PURE__ */ new Set([
11893
12355
  "--copy",
@@ -11983,7 +12445,7 @@ var FILE_OPERAND_SUBCOMMANDS = /* @__PURE__ */ new Set([
11983
12445
  var COMPOUND_SUBCOMMAND_HEADS = /* @__PURE__ */ new Set(["worktree", "stash", "tag"]);
11984
12446
  var REF_ONLY_WITHOUT_TERMINATOR = /* @__PURE__ */ new Set(["checkout", "show", "log"]);
11985
12447
  function isGitExecutable(token) {
11986
- return path36.basename(token) === "git";
12448
+ return path38.basename(token) === "git";
11987
12449
  }
11988
12450
  function takesValue(flag) {
11989
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=");
@@ -12011,7 +12473,7 @@ function peelGlobalOptions(tokens, baseCwd) {
12011
12473
  if (token === "-C" || token === "--work-tree" || token === "--git-dir" || token === "-c") {
12012
12474
  const value = tokens[index + 1];
12013
12475
  if (token === "-C" && value) {
12014
- effectiveCwd = path36.resolve(baseCwd, value);
12476
+ effectiveCwd = path38.resolve(baseCwd, value);
12015
12477
  } else if (token === "--work-tree" && value) {
12016
12478
  workTree = value;
12017
12479
  } else if (token === "--git-dir" && value) {
@@ -12021,7 +12483,7 @@ function peelGlobalOptions(tokens, baseCwd) {
12021
12483
  continue;
12022
12484
  }
12023
12485
  if (token.startsWith("-C") && token.length > 2) {
12024
- effectiveCwd = path36.resolve(baseCwd, token.slice(2));
12486
+ effectiveCwd = path38.resolve(baseCwd, token.slice(2));
12025
12487
  index += 1;
12026
12488
  continue;
12027
12489
  }
@@ -12164,7 +12626,7 @@ function looksLikeDiffPathOperand(token) {
12164
12626
  if (!looksLikeFileOperand(token)) {
12165
12627
  return false;
12166
12628
  }
12167
- if (token.startsWith(".") || path36.isAbsolute(token)) {
12629
+ if (token.startsWith(".") || path38.isAbsolute(token)) {
12168
12630
  return true;
12169
12631
  }
12170
12632
  return token.includes(".");
@@ -12172,12 +12634,12 @@ function looksLikeDiffPathOperand(token) {
12172
12634
  function resolveGitWorkTree(baseCwd, effectiveCwd, workTree, gitDir) {
12173
12635
  const resolveBase = effectiveCwd ?? baseCwd;
12174
12636
  if (workTree) {
12175
- return path36.resolve(resolveBase, workTree);
12637
+ return path38.resolve(resolveBase, workTree);
12176
12638
  }
12177
12639
  if (gitDir) {
12178
- const resolvedGitDir = path36.resolve(resolveBase, gitDir);
12179
- if (path36.basename(resolvedGitDir) === ".git") {
12180
- return path36.dirname(resolvedGitDir);
12640
+ const resolvedGitDir = path38.resolve(resolveBase, gitDir);
12641
+ if (path38.basename(resolvedGitDir) === ".git") {
12642
+ return path38.dirname(resolvedGitDir);
12181
12643
  }
12182
12644
  }
12183
12645
  return void 0;
@@ -12258,7 +12720,7 @@ function classifyGitCommand(tokens, baseCwd) {
12258
12720
  const { subcommand, args, effectiveCwd, gitDir, workTree } = normalized;
12259
12721
  const normalizedKey = `git ${subcommand}`;
12260
12722
  const gitWorkTree = resolveGitWorkTree(baseCwd, effectiveCwd, workTree, gitDir);
12261
- const effectiveGitDir = gitDir ? path36.resolve(effectiveCwd ?? baseCwd, gitDir) : void 0;
12723
+ const effectiveGitDir = gitDir ? path38.resolve(effectiveCwd ?? baseCwd, gitDir) : void 0;
12262
12724
  const scopeTargets = [effectiveCwd, gitWorkTree, effectiveGitDir].filter(
12263
12725
  (target, index, targets) => Boolean(target) && targets.indexOf(target) === index
12264
12726
  );
@@ -12410,9 +12872,9 @@ function decodeGitEffects(params) {
12410
12872
  ...subcommand === "push" ? ["tier0_external"] : []
12411
12873
  ];
12412
12874
  const effectiveCwd = normalized.effectiveCwd ?? params.cwd;
12413
- const workTreeRoot = normalized.workTree ? path36.resolve(normalized.effectiveCwd ?? params.cwd, normalized.workTree) : normalized.effectiveCwd ?? params.repoRoot;
12414
- const gitRefRoot = normalized.gitDir ? path36.resolve(effectiveCwd, normalized.gitDir) : workTreeRoot;
12415
- 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");
12416
12878
  const requirements = [];
12417
12879
  if (subcommand === "fetch" || subcommand === "pull") {
12418
12880
  const positionals = gitRemotePositionals(args);
@@ -12571,7 +13033,7 @@ function decodeGitEffects(params) {
12571
13033
  gitRequirement(
12572
13034
  "control_plane.write",
12573
13035
  "control_plane.write",
12574
- { kind: "path", path: path36.join(gitControlRoot, "logs") },
13036
+ { kind: "path", path: path38.join(gitControlRoot, "logs") },
12575
13037
  params.segment,
12576
13038
  [...signals, "git_history_destructive", "git.reflog.mutate"]
12577
13039
  )
@@ -12629,7 +13091,7 @@ function decodeGitEffects(params) {
12629
13091
  gitRequirement(
12630
13092
  "fs.read",
12631
13093
  "fs.read",
12632
- { kind: "path", path: path36.resolve(workTreeRoot, operand) },
13094
+ { kind: "path", path: path38.resolve(workTreeRoot, operand) },
12633
13095
  params.segment,
12634
13096
  [...signals, "git.path.read"]
12635
13097
  )
@@ -12664,7 +13126,7 @@ function decodeGitEffects(params) {
12664
13126
  gitRequirement(
12665
13127
  "fs.write",
12666
13128
  "fs.write",
12667
- { kind: "path", path: path36.resolve(workTreeRoot, operand) },
13129
+ { kind: "path", path: path38.resolve(workTreeRoot, operand) },
12668
13130
  params.segment,
12669
13131
  [...signals, "git.path.write"]
12670
13132
  )
@@ -12849,7 +13311,144 @@ function gitRequirement(tag, action, resource, segment, signals) {
12849
13311
 
12850
13312
  // src/core/verdict/launcher-resolve.ts
12851
13313
  import { existsSync as existsSync11, readFileSync as readFileSync5 } from "node:fs";
12852
- import path37 from "node:path";
13314
+ import path39 from "node:path";
13315
+
13316
+ // src/core/verdict/makefile-expand.ts
13317
+ var MAX_EXPAND_DEPTH = 16;
13318
+ function parseMakefileVariables(content) {
13319
+ const variables = /* @__PURE__ */ new Map();
13320
+ for (const line of content.split("\n")) {
13321
+ const trimmed = line.trim();
13322
+ if (!trimmed || trimmed.startsWith("#")) {
13323
+ continue;
13324
+ }
13325
+ const match = /^([A-Za-z_][A-Za-z0-9_]*)\s*[:?]?=\s*(.+)$/.exec(trimmed);
13326
+ if (!match) {
13327
+ continue;
13328
+ }
13329
+ variables.set(match[1] ?? "", (match[2] ?? "").trim());
13330
+ }
13331
+ return variables;
13332
+ }
13333
+ function normalizeMakeRecipeLine(line) {
13334
+ let normalized = line.trim();
13335
+ while (normalized.startsWith("@") || normalized.startsWith("-") || normalized.startsWith("+")) {
13336
+ normalized = normalized.slice(1).trimStart();
13337
+ }
13338
+ return normalized;
13339
+ }
13340
+ function expandMakeExpression(expression, cliVars, makefileVars) {
13341
+ if (/\$\(\s*shell\b/i.test(expression) || expression.includes("$$")) {
13342
+ return null;
13343
+ }
13344
+ try {
13345
+ const expanded = expandMakeValue(expression, cliVars, makefileVars, 0);
13346
+ if (expanded === null || /\$\(/.test(expanded) || /\$\{/.test(expanded)) {
13347
+ return null;
13348
+ }
13349
+ return expanded;
13350
+ } catch {
13351
+ return null;
13352
+ }
13353
+ }
13354
+ function expandMakeValue(expression, cliVars, makefileVars, depth) {
13355
+ if (depth > MAX_EXPAND_DEPTH) {
13356
+ return null;
13357
+ }
13358
+ let value = expression.trim();
13359
+ let changed = true;
13360
+ let iterations = 0;
13361
+ while (changed && iterations < MAX_EXPAND_DEPTH) {
13362
+ changed = false;
13363
+ iterations += 1;
13364
+ const orMatch = value.match(/\$\(\s*or\s+([^()]*(?:\([^)]*\)[^()]*)*)\)/);
13365
+ if (orMatch) {
13366
+ const [fullMatch, inner] = orMatch;
13367
+ const parts = splitMakeFunctionArgs(inner ?? "");
13368
+ let selected = null;
13369
+ for (const part of parts) {
13370
+ const expanded = expandMakeValue(part.trim(), cliVars, makefileVars, depth + 1);
13371
+ if (expanded !== null && expanded.trim() !== "") {
13372
+ selected = expanded;
13373
+ break;
13374
+ }
13375
+ }
13376
+ if (selected === null) {
13377
+ const fallback = parts.at(-1)?.trim();
13378
+ selected = fallback === void 0 ? "" : expandMakeValue(fallback, cliVars, makefileVars, depth + 1);
13379
+ }
13380
+ if (selected === null) {
13381
+ return null;
13382
+ }
13383
+ value = value.replace(fullMatch, selected);
13384
+ changed = true;
13385
+ continue;
13386
+ }
13387
+ const varMatch = value.match(/\$\(([A-Za-z_][A-Za-z0-9_]*)\)/);
13388
+ if (varMatch) {
13389
+ const [fullMatch, name] = varMatch;
13390
+ const resolved = resolveMakeVariable(name ?? "", cliVars, makefileVars, depth + 1);
13391
+ if (resolved === null) {
13392
+ return null;
13393
+ }
13394
+ value = value.replace(fullMatch, resolved);
13395
+ changed = true;
13396
+ continue;
13397
+ }
13398
+ const bracedMatch = value.match(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/);
13399
+ if (bracedMatch) {
13400
+ const [fullMatch, name] = bracedMatch;
13401
+ const resolved = name === "PWD" ? "." : resolveMakeVariable(name ?? "", cliVars, makefileVars, depth + 1);
13402
+ if (resolved === null) {
13403
+ return null;
13404
+ }
13405
+ value = value.replace(fullMatch, resolved);
13406
+ changed = true;
13407
+ continue;
13408
+ }
13409
+ break;
13410
+ }
13411
+ return value;
13412
+ }
13413
+ function resolveMakeVariable(name, cliVars, makefileVars, depth) {
13414
+ if (Object.hasOwn(cliVars, name)) {
13415
+ return cliVars[name] ?? "";
13416
+ }
13417
+ const definition = makefileVars.get(name);
13418
+ if (definition === void 0) {
13419
+ return null;
13420
+ }
13421
+ return expandMakeValue(definition, cliVars, makefileVars, depth);
13422
+ }
13423
+ function splitMakeFunctionArgs(input) {
13424
+ const parts = [];
13425
+ let current = "";
13426
+ let depth = 0;
13427
+ for (const char of input) {
13428
+ if (char === "(") {
13429
+ depth += 1;
13430
+ current += char;
13431
+ continue;
13432
+ }
13433
+ if (char === ")") {
13434
+ depth -= 1;
13435
+ current += char;
13436
+ continue;
13437
+ }
13438
+ if (char === "," && depth === 0) {
13439
+ parts.push(current.trim());
13440
+ current = "";
13441
+ continue;
13442
+ }
13443
+ current += char;
13444
+ }
13445
+ if (current.trim()) {
13446
+ parts.push(current.trim());
13447
+ }
13448
+ return parts;
13449
+ }
13450
+
13451
+ // src/core/verdict/launcher-resolve.ts
12853
13452
  var MAX_RESOLVE_DEPTH = 8;
12854
13453
  var PNPM_BUILTIN_COMMANDS = /* @__PURE__ */ new Set([
12855
13454
  "add",
@@ -12885,7 +13484,7 @@ var PNPM_BUILTIN_COMMANDS = /* @__PURE__ */ new Set([
12885
13484
  "why"
12886
13485
  ]);
12887
13486
  function readPackageJson(dir) {
12888
- const packagePath = path37.join(dir, "package.json");
13487
+ const packagePath = path39.join(dir, "package.json");
12889
13488
  if (!existsSync11(packagePath)) {
12890
13489
  return null;
12891
13490
  }
@@ -12896,17 +13495,17 @@ function readPackageJson(dir) {
12896
13495
  }
12897
13496
  }
12898
13497
  function findPackageJson(startDir, stopDir) {
12899
- let current = path37.resolve(startDir);
12900
- const stop = path37.resolve(stopDir);
13498
+ let current = path39.resolve(startDir);
13499
+ const stop = path39.resolve(stopDir);
12901
13500
  while (true) {
12902
- const packagePath = path37.join(current, "package.json");
13501
+ const packagePath = path39.join(current, "package.json");
12903
13502
  if (existsSync11(packagePath)) {
12904
13503
  return packagePath;
12905
13504
  }
12906
- if (current === stop || current === path37.dirname(current)) {
13505
+ if (current === stop || current === path39.dirname(current)) {
12907
13506
  return existsSync11(packagePath) ? packagePath : null;
12908
13507
  }
12909
- const parent = path37.dirname(current);
13508
+ const parent = path39.dirname(current);
12910
13509
  if (!parent.startsWith(stop) && parent !== current) {
12911
13510
  }
12912
13511
  if (parent === current) {
@@ -12963,7 +13562,7 @@ function resolveNpmRecipe(cwd, repoRoot, scriptName, extraArgs) {
12963
13562
  }
12964
13563
  return { recipes: [], opaque: true, reason: "package_json_missing" };
12965
13564
  }
12966
- const pkg = readPackageJson(path37.dirname(packagePath));
13565
+ const pkg = readPackageJson(path39.dirname(packagePath));
12967
13566
  const scripts = pkg?.scripts;
12968
13567
  if (!scripts || typeof scripts !== "object") {
12969
13568
  return { recipes: [], opaque: true, reason: "package_scripts_missing" };
@@ -12990,10 +13589,9 @@ function resolveNpmRecipe(cwd, repoRoot, scriptName, extraArgs) {
12990
13589
  reason: "npm_script_resolved"
12991
13590
  };
12992
13591
  }
12993
- function parseMakefileRecipes(makefilePath) {
13592
+ function parseMakefileRecipeContent(content) {
12994
13593
  const targets = /* @__PURE__ */ new Map();
12995
13594
  try {
12996
- const content = readFileSync5(makefilePath, "utf8");
12997
13595
  const lines = content.split("\n");
12998
13596
  let currentTarget = null;
12999
13597
  let recipeLines = [];
@@ -13052,78 +13650,96 @@ function parseMakefileRecipes(makefilePath) {
13052
13650
  }
13053
13651
  return targets;
13054
13652
  }
13055
- function resolveMakeRecipe(cwd, repoRoot, target) {
13653
+ function resolveMakeRecipe(cwd, repoRoot, target, cliVars = {}) {
13056
13654
  const candidates = ["Makefile", "makefile", "GNUmakefile"];
13057
13655
  let makefilePath = null;
13058
- let searchDir = path37.resolve(cwd);
13059
- const stop = path37.resolve(repoRoot);
13656
+ let searchDir = path39.resolve(cwd);
13657
+ const stop = path39.resolve(repoRoot);
13060
13658
  while (true) {
13061
13659
  for (const name of candidates) {
13062
- const candidate = path37.join(searchDir, name);
13660
+ const candidate = path39.join(searchDir, name);
13063
13661
  if (existsSync11(candidate)) {
13064
13662
  makefilePath = candidate;
13065
13663
  break;
13066
13664
  }
13067
13665
  }
13068
- if (makefilePath || searchDir === stop || searchDir === path37.dirname(searchDir)) {
13666
+ if (makefilePath || searchDir === stop || searchDir === path39.dirname(searchDir)) {
13069
13667
  break;
13070
13668
  }
13071
- searchDir = path37.dirname(searchDir);
13669
+ searchDir = path39.dirname(searchDir);
13072
13670
  }
13073
13671
  if (!makefilePath) {
13074
13672
  return { recipes: [], opaque: true, reason: "unknown_local_effect" };
13075
13673
  }
13076
- const targets = parseMakefileRecipes(makefilePath);
13674
+ const makefileContent = readFileSync5(makefilePath, "utf8");
13675
+ const makefileVars = parseMakefileVariables(makefileContent);
13676
+ const targets = parseMakefileRecipeContent(makefileContent);
13077
13677
  if (!targets.has(target)) {
13078
13678
  return { recipes: [], opaque: true, reason: "make_target_undefined" };
13079
13679
  }
13080
13680
  const recipeLines = [];
13081
13681
  const visiting = /* @__PURE__ */ new Set();
13082
13682
  const visited = /* @__PURE__ */ new Set();
13083
- let opaquePrerequisites = false;
13683
+ let hasDynamicPrerequisite = false;
13684
+ let hasUndefinedPrerequisite = false;
13685
+ let hasDependencyCycle = false;
13084
13686
  const collect = (name) => {
13085
13687
  if (visited.has(name)) {
13086
- return true;
13688
+ return;
13087
13689
  }
13088
13690
  if (visiting.has(name)) {
13089
- return false;
13691
+ hasDependencyCycle = true;
13692
+ return;
13090
13693
  }
13091
13694
  const entry = targets.get(name);
13092
13695
  if (!entry) {
13093
- return true;
13696
+ if (!existsSync11(path39.resolve(path39.dirname(makefilePath), name))) {
13697
+ hasUndefinedPrerequisite = true;
13698
+ }
13699
+ return;
13094
13700
  }
13095
13701
  visiting.add(name);
13096
- opaquePrerequisites ||= entry.opaquePrerequisites;
13702
+ hasDynamicPrerequisite ||= entry.opaquePrerequisites;
13097
13703
  for (const prerequisite of entry.prerequisites) {
13098
- if (targets.has(prerequisite) && !collect(prerequisite)) {
13099
- return false;
13100
- }
13704
+ collect(prerequisite);
13101
13705
  }
13102
13706
  recipeLines.push(...entry.recipes);
13103
13707
  visiting.delete(name);
13104
13708
  visited.add(name);
13105
- return true;
13106
13709
  };
13107
- if (!collect(target)) {
13108
- return { recipes: recipeLines, opaque: true, reason: "make_dependency_cycle" };
13109
- }
13710
+ collect(target);
13711
+ const expandedRecipes = [];
13110
13712
  for (const line of recipeLines) {
13111
- if (/\$\(/.test(line) || /\$\{/.test(line)) {
13713
+ const normalized = normalizeMakeRecipeLine(line);
13714
+ const expanded = expandMakeExpression(normalized, cliVars, makefileVars);
13715
+ if (expanded === null) {
13112
13716
  return { recipes: recipeLines, opaque: true, reason: "make_recipe_dynamic" };
13113
13717
  }
13718
+ expandedRecipes.push(expanded);
13719
+ }
13720
+ for (const line of expandedRecipes) {
13721
+ if (/\$\(/.test(line) || /\$\{/.test(line)) {
13722
+ return { recipes: expandedRecipes, opaque: true, reason: "make_recipe_dynamic" };
13723
+ }
13724
+ }
13725
+ if (hasDependencyCycle) {
13726
+ return { recipes: expandedRecipes, opaque: true, reason: "make_dependency_cycle" };
13114
13727
  }
13115
- if (opaquePrerequisites) {
13116
- return { recipes: recipeLines, opaque: true, reason: "make_prerequisite_dynamic" };
13728
+ if (hasDynamicPrerequisite) {
13729
+ return { recipes: expandedRecipes, opaque: true, reason: "make_prerequisite_dynamic" };
13117
13730
  }
13118
- return { recipes: recipeLines, opaque: false, reason: "make_recipe_resolved" };
13731
+ if (hasUndefinedPrerequisite) {
13732
+ return { recipes: expandedRecipes, opaque: true, reason: "make_prerequisite_undefined" };
13733
+ }
13734
+ return { recipes: expandedRecipes, opaque: false, reason: "make_recipe_resolved" };
13119
13735
  }
13120
13736
  function resolveLauncherRecipe(params) {
13121
- if (params.depth >= MAX_RESOLVE_DEPTH) {
13122
- return { recipes: [], opaque: true, reason: "launcher_depth_exceeded" };
13123
- }
13124
13737
  const tokens = params.tokens;
13125
13738
  const scriptName = npmScriptName(tokens);
13126
13739
  if (scriptName) {
13740
+ if (params.depth >= MAX_RESOLVE_DEPTH) {
13741
+ return { recipes: [], opaque: true, reason: "launcher_depth_exceeded" };
13742
+ }
13127
13743
  const resolution = resolveNpmRecipe(
13128
13744
  params.cwd,
13129
13745
  params.repoRoot,
@@ -13139,8 +13755,31 @@ function resolveLauncherRecipe(params) {
13139
13755
  }
13140
13756
  return resolution;
13141
13757
  }
13142
- if (tokens[0] === "make" && tokens[1] && !tokens[1].startsWith("-")) {
13143
- return resolveMakeRecipe(params.cwd, params.repoRoot, tokens[1]);
13758
+ if (tokens[0] === "make") {
13759
+ if (params.depth >= MAX_RESOLVE_DEPTH) {
13760
+ return { recipes: [], opaque: true, reason: "launcher_depth_exceeded" };
13761
+ }
13762
+ if (tokens.includes("-n") || tokens.includes("--dry-run")) {
13763
+ return null;
13764
+ }
13765
+ let target = null;
13766
+ const cliVars = {};
13767
+ for (const token of tokens.slice(1)) {
13768
+ if (token.startsWith("-")) {
13769
+ continue;
13770
+ }
13771
+ const assignment = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/.exec(token);
13772
+ if (assignment) {
13773
+ cliVars[assignment[1] ?? ""] = assignment[2] ?? "";
13774
+ continue;
13775
+ }
13776
+ if (!target) {
13777
+ target = token;
13778
+ }
13779
+ }
13780
+ if (target) {
13781
+ return resolveMakeRecipe(params.cwd, params.repoRoot, target, cliVars);
13782
+ }
13144
13783
  }
13145
13784
  if (tokens[0] === "pnpm" && tokens[1] === "exec" && tokens[2]) {
13146
13785
  return {
@@ -13153,7 +13792,7 @@ function resolveLauncherRecipe(params) {
13153
13792
  }
13154
13793
 
13155
13794
  // src/core/verdict/parser.ts
13156
- import path38 from "node:path";
13795
+ import path40 from "node:path";
13157
13796
 
13158
13797
  // src/core/shell-substitution.ts
13159
13798
  function findStructuralCommandSubstitutions(command) {
@@ -13378,9 +14017,8 @@ function hasUnbalancedDollarParen(command) {
13378
14017
  // src/core/verdict/parser.ts
13379
14018
  var ENV_PREFIX_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*=(?:'[^']*'|"[^"]*"|\S+)$/;
13380
14019
  var MAX_WRAPPER_PEEL_DEPTH = 32;
13381
- var SHELL_INTERPRETERS = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "dash", "fish"]);
14020
+ var SHELL_INTERPRETERS2 = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "dash", "fish"]);
13382
14021
  var CODE_INTERPRETERS = /* @__PURE__ */ new Set(["python", "python3", "node", "ruby", "perl", "osascript"]);
13383
- var SCRIPT_FLAGS = /* @__PURE__ */ new Set(["-c", "-lc", "-e", "--eval"]);
13384
14022
  var INTERPRETER_SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([
13385
14023
  ".js",
13386
14024
  ".mjs",
@@ -13392,7 +14030,7 @@ var INTERPRETER_SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([
13392
14030
  ".sh"
13393
14031
  ]);
13394
14032
  function normalizeHead(token) {
13395
- const base = path38.basename(token);
14033
+ const base = path40.basename(token);
13396
14034
  if (base && base !== "." && base !== "..") {
13397
14035
  return base;
13398
14036
  }
@@ -13404,10 +14042,6 @@ function peelTransparentWrappers(tokens) {
13404
14042
  let encounteredXargs = false;
13405
14043
  let peelDepth = 0;
13406
14044
  while (current.length > 0) {
13407
- if (peelDepth >= MAX_WRAPPER_PEEL_DEPTH) {
13408
- return { tokens: current, xargsStdinOpaque: false, encounteredXargs, opaque: true };
13409
- }
13410
- peelDepth += 1;
13411
14045
  while (current.length > 0 && ENV_PREFIX_PATTERN.test(current[0] ?? "")) {
13412
14046
  current.shift();
13413
14047
  }
@@ -13417,6 +14051,10 @@ function peelTransparentWrappers(tokens) {
13417
14051
  const head = normalizeHead(current[0] ?? "");
13418
14052
  if (head === "xargs") {
13419
14053
  encounteredXargs = true;
14054
+ if (peelDepth >= MAX_WRAPPER_PEEL_DEPTH) {
14055
+ return { tokens: current, xargsStdinOpaque: false, encounteredXargs, opaque: true };
14056
+ }
14057
+ peelDepth += 1;
13420
14058
  const wrapper2 = peelXargsWrapper(current);
13421
14059
  if (wrapper2.kind === "opaque") {
13422
14060
  xargsStdinOpaque = current.length === 1;
@@ -13434,6 +14072,10 @@ function peelTransparentWrappers(tokens) {
13434
14072
  if (!wrapper) {
13435
14073
  break;
13436
14074
  }
14075
+ if (peelDepth >= MAX_WRAPPER_PEEL_DEPTH) {
14076
+ return { tokens: current, xargsStdinOpaque: false, encounteredXargs, opaque: true };
14077
+ }
14078
+ peelDepth += 1;
13437
14079
  if (wrapper.kind === "opaque") {
13438
14080
  return { tokens: current, xargsStdinOpaque: false, encounteredXargs, opaque: true };
13439
14081
  }
@@ -13657,34 +14299,20 @@ function extractRecursiveScript(tokens) {
13657
14299
  return null;
13658
14300
  }
13659
14301
  const head = normalizeHead(filtered[0] ?? "");
13660
- const second = filtered[1] ?? "";
13661
14302
  if (head === "eval") {
13662
14303
  const body = filtered.slice(1).join(" ").trim();
13663
14304
  return body || null;
13664
14305
  }
13665
- if (SHELL_INTERPRETERS.has(head) || CODE_INTERPRETERS.has(head)) {
13666
- const flagIndex = filtered.findIndex((token) => SCRIPT_FLAGS.has(token));
13667
- if (flagIndex !== -1) {
13668
- const body = filtered.slice(flagIndex + 1).join(" ").replace(/^['"]|['"]$/g, "").trim();
13669
- return body || null;
13670
- }
13671
- }
13672
- if (head === "bash" && (second === "-lc" || second === "-c")) {
13673
- const body = filtered.slice(2).join(" ").replace(/^['"]|['"]$/g, "").trim();
13674
- return body || null;
13675
- }
13676
- return null;
14306
+ const invocation = decodeRecursiveInvocation(
14307
+ shellTokensFromValues(filtered, { detectExpansion: false })
14308
+ );
14309
+ return invocation.kind === "static" ? invocation.script || null : null;
13677
14310
  }
13678
- function isDynamicRecursiveEvaluation(tokens) {
13679
- const { tokens: filtered, opaque } = peelTransparentWrappers(tokens);
13680
- if (opaque) {
13681
- return false;
13682
- }
13683
- const head = normalizeHead(filtered[0] ?? "");
13684
- if (head === "eval") {
13685
- return true;
13686
- }
13687
- 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));
13688
14316
  }
13689
14317
  function isCommandInspection(tokens) {
13690
14318
  return normalizeHead(tokens[0] ?? "") === "command" && peelCommandWrapper(tokens).kind === "preserve";
@@ -13698,11 +14326,10 @@ function isBareInterpreter(tokens) {
13698
14326
  return false;
13699
14327
  }
13700
14328
  const head = normalizeHead(peeled[0] ?? "");
13701
- if (!SHELL_INTERPRETERS.has(head) && !CODE_INTERPRETERS.has(head)) {
14329
+ if (!SHELL_INTERPRETERS2.has(head) && !CODE_INTERPRETERS.has(head)) {
13702
14330
  return false;
13703
14331
  }
13704
- const hasScriptFlag = peeled.some((token) => SCRIPT_FLAGS.has(token));
13705
- if (hasScriptFlag) {
14332
+ if (decodeRecursiveInvocation(shellTokensFromValues(peeled)).kind !== "none") {
13706
14333
  return false;
13707
14334
  }
13708
14335
  const args = peeled.slice(1);
@@ -13713,7 +14340,7 @@ function isBareInterpreter(tokens) {
13713
14340
  return false;
13714
14341
  }
13715
14342
  const scriptArg = args.find((token) => !token.startsWith("-"));
13716
- if (scriptArg && INTERPRETER_SCRIPT_EXTENSIONS.has(path38.extname(scriptArg))) {
14343
+ if (scriptArg && INTERPRETER_SCRIPT_EXTENSIONS.has(path40.extname(scriptArg))) {
13717
14344
  return false;
13718
14345
  }
13719
14346
  if (scriptArg) {
@@ -14005,11 +14632,11 @@ function lowerTopLevelSegments(command, context) {
14005
14632
  }
14006
14633
  function startsLocalPostgresService(command) {
14007
14634
  const tokens = tokenizeShell(command);
14008
- 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");
14009
14636
  }
14010
14637
  function resolveCdTransition(command, currentCwd) {
14011
14638
  const tokens = tokenizeShell(command);
14012
- if (path39.basename(tokens[0] ?? "") !== "cd") {
14639
+ if (path41.basename(tokens[0] ?? "") !== "cd") {
14013
14640
  return null;
14014
14641
  }
14015
14642
  const target = tokens[1] ?? "~";
@@ -14030,17 +14657,32 @@ function joinNestedOpacity(outer, nested) {
14030
14657
  }
14031
14658
  function lowerSegment(command, context) {
14032
14659
  const commandRedacted = redactCommand(command);
14033
- const rawTokens = tokenizeShell(command);
14660
+ const lexed = lexShell(command);
14661
+ const rawTokens = lexed.tokens.map((token) => token.value);
14034
14662
  const environment = extractEnvironment(rawTokens, context.env);
14035
14663
  const env = environment.env;
14036
14664
  const parsed = parseSegment(command);
14037
- const tokens = stripRedirects(environment.commandTokens ?? parsed.tokens).map(
14038
- (token) => expandKnownVariables(token, env)
14665
+ const parsedTokens = environment.commandTokens ?? parsed.tokens;
14666
+ const tokens = stripRedirects(
14667
+ parsedTokens.length === 0 && rawTokens.length > 0 && rawTokens.every((token) => ENV_PREFIX_PATTERN2.test(token)) ? rawTokens : parsedTokens
14668
+ ).map((token) => expandKnownVariables(token, env));
14669
+ const decoderTokens = alignStructuredTokens(
14670
+ stripStructuredRedirects(lexed.tokens),
14671
+ stripRedirects(parsedTokens)
14039
14672
  );
14040
- const head = path39.basename(tokens[0] ?? parsed.head);
14673
+ const head = path41.basename(tokens[0] ?? parsed.head);
14041
14674
  let opacity = segmentOpacity(command);
14042
14675
  const signals = /* @__PURE__ */ new Set();
14043
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
+ }
14044
14686
  addRedirectEffects(requirements, rawTokens, env, context, commandRedacted);
14045
14687
  addSubstitutionEffects(requirements, command, context, commandRedacted, signals);
14046
14688
  if (environment.malformed) {
@@ -14060,7 +14702,7 @@ function lowerSegment(command, context) {
14060
14702
  signals.add("shell.xargs_stdin_dynamic");
14061
14703
  opacity = joinEffectOpacity(opacity, "opaque");
14062
14704
  }
14063
- if (context.depth >= MAX_LOWER_DEPTH) {
14705
+ if (context.depth > MAX_LOWER_DEPTH) {
14064
14706
  requirements.push(
14065
14707
  requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
14066
14708
  "shell.lower_depth_exceeded"
@@ -14107,37 +14749,96 @@ function lowerSegment(command, context) {
14107
14749
  }
14108
14750
  return shellSegment(commandRedacted, head, requirements, opacity, signals);
14109
14751
  }
14110
- const recursiveScript = extractRecursiveScript(tokens);
14111
- if (recursiveScript && opacity !== "opaque" && opacity !== "unparseable") {
14112
- const dynamicEvaluation = isDynamicRecursiveEvaluation(tokens);
14752
+ const recursive = decodeRecursiveInvocationTokens(decoderTokens);
14753
+ if (recursive.kind === "static" && opacity !== "opaque" && opacity !== "unparseable") {
14113
14754
  requirements.push(
14114
- processRequirement(head || "sh", "spawn", commandRedacted, [
14755
+ processRequirement(recursive.interpreter, "spawn", commandRedacted, [
14115
14756
  "shell.recursive_wrapper",
14116
- ...dynamicEvaluation ? ["dynamic_shell_evaluation"] : []
14757
+ "dynamic_shell_evaluation"
14117
14758
  ])
14118
14759
  );
14119
- const nested = lowerTopLevelSegments(recursiveScript, {
14120
- ...context,
14121
- command: recursiveScript,
14122
- env,
14123
- depth: context.depth + 1
14124
- });
14125
- for (const nestedSegment of nested) {
14126
- requirements.push(
14127
- ...nestedSegment.requirements.map(
14128
- (entry) => withInnerProvenance(entry, recursiveScript, head, commandRedacted)
14129
- )
14130
- );
14131
- for (const signal of nestedSegment.signals) {
14132
- 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);
14133
14774
  }
14134
14775
  }
14135
14776
  signals.add("shell.recursive_wrapper");
14136
- if (dynamicEvaluation) {
14137
- signals.add("dynamic_shell_evaluation");
14777
+ signals.add("dynamic_shell_evaluation");
14778
+ return shellSegment(commandRedacted, head, requirements, "recursive", signals);
14779
+ }
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") {
14802
+ requirements.push(
14803
+ processRequirement(head, "spawn", commandRedacted, ["process.docker_compose_run"])
14804
+ );
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);
14820
+ }
14138
14821
  }
14822
+ signals.add("process.docker_compose_run");
14139
14823
  return shellSegment(commandRedacted, head, requirements, "recursive", signals);
14140
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
+ }
14141
14842
  const launcher = resolveLauncherRecipe({
14142
14843
  tokens,
14143
14844
  cwd: context.cwd,
@@ -14349,7 +15050,7 @@ function isMetadataOnlyArgv(argv) {
14349
15050
  return argv.length > 0 && argv.every((token) => METADATA_ONLY_FLAGS.has(token));
14350
15051
  }
14351
15052
  function executableBaseName(head) {
14352
- return path39.basename(head);
15053
+ return path41.basename(head);
14353
15054
  }
14354
15055
  var RAILS_READ_ONLY_SUBCOMMANDS = /* @__PURE__ */ new Set(["routes", "middleware", "stats", "about", "version"]);
14355
15056
  function railsReadOnlySubcommand(args) {
@@ -14359,6 +15060,111 @@ function railsReadOnlySubcommand(args) {
14359
15060
  }
14360
15061
  return RAILS_READ_ONLY_SUBCOMMANDS.has(subcommand);
14361
15062
  }
15063
+ function isRubyTestScript(scriptPath) {
15064
+ const base = path41.basename(scriptPath);
15065
+ return base.endsWith("_test.rb") || base.endsWith("_spec.rb");
15066
+ }
15067
+ function parseRubyTestInvocation(args) {
15068
+ const includePaths = [];
15069
+ for (let index = 0; index < args.length; index += 1) {
15070
+ const arg = args[index] ?? "";
15071
+ if (arg === "-e" || arg === "-r") {
15072
+ return null;
15073
+ }
15074
+ if (arg === "-I") {
15075
+ const includePath = args[index + 1];
15076
+ if (!includePath) {
15077
+ return null;
15078
+ }
15079
+ includePaths.push(includePath);
15080
+ index += 1;
15081
+ continue;
15082
+ }
15083
+ if (arg.startsWith("-I") && arg.length > 2) {
15084
+ includePaths.push(arg.slice(2));
15085
+ continue;
15086
+ }
15087
+ if (arg.startsWith("-")) {
15088
+ if (arg === "-n") {
15089
+ if (!args[index + 1]) {
15090
+ return null;
15091
+ }
15092
+ index += 1;
15093
+ continue;
15094
+ }
15095
+ if (arg.startsWith("-n")) {
15096
+ continue;
15097
+ }
15098
+ return null;
15099
+ }
15100
+ if (isRubyTestScript(arg)) {
15101
+ return { includePaths, scriptPath: arg };
15102
+ }
15103
+ return null;
15104
+ }
15105
+ return null;
15106
+ }
15107
+ function isRubocopMutating(args) {
15108
+ return args.some(
15109
+ (arg) => arg === "-A" || arg === "-a" || arg === "--auto-correct" || arg === "--autocorrect" || arg.startsWith("--auto-correct-all") || arg.startsWith("--autocorrect-all")
15110
+ );
15111
+ }
15112
+ function decodeBundleExecInner(innerHead, innerArgs, segment) {
15113
+ const innerBase = executableBaseName(innerHead);
15114
+ if (innerBase === "rubocop") {
15115
+ const mutating = isRubocopMutating(innerArgs);
15116
+ return [
15117
+ processRequirement(
15118
+ innerHead,
15119
+ mutating ? "spawn" : "inspect",
15120
+ segment,
15121
+ mutating ? ["process.linter.mutating"] : ["process.inspect.linter"]
15122
+ )
15123
+ ];
15124
+ }
15125
+ if (innerBase === "rspec") {
15126
+ const targetArgs = innerArgs.filter((arg) => !arg.startsWith("-"));
15127
+ if (targetArgs.length === 0) {
15128
+ return null;
15129
+ }
15130
+ return [processRequirement(innerHead, "spawn", segment, ["process.test_runner.rspec"])];
15131
+ }
15132
+ return null;
15133
+ }
15134
+ function decodeRuby(args, cwd, repoRoot, segment) {
15135
+ const parsed = parseRubyTestInvocation(args);
15136
+ if (!parsed) {
15137
+ return unsupportedProcess("ruby", segment, "process.ruby_grammar_incomplete");
15138
+ }
15139
+ const scriptPath = resolvePathOperand(parsed.scriptPath, cwd);
15140
+ if (!pathWithinRoot(canonicalPath(repoRoot), canonicalPath(scriptPath))) {
15141
+ return unsupportedProcess("ruby", segment, "process.ruby_outside_repo");
15142
+ }
15143
+ for (const includePath of parsed.includePaths) {
15144
+ const resolvedInclude = resolvePathOperand(includePath, cwd);
15145
+ if (!pathWithinRoot(canonicalPath(repoRoot), canonicalPath(resolvedInclude))) {
15146
+ return unsupportedProcess("ruby", segment, "process.ruby_outside_repo");
15147
+ }
15148
+ }
15149
+ const lowered = [
15150
+ processRequirement("ruby", "spawn", segment, ["process.test_runner.minitest"]),
15151
+ requirement2("fs.read", "fs.read", { kind: "path", path: scriptPath }, segment, [
15152
+ "ruby.minitest_script_read"
15153
+ ])
15154
+ ];
15155
+ for (const includePath of parsed.includePaths) {
15156
+ lowered.push(
15157
+ requirement2(
15158
+ "fs.read",
15159
+ "fs.read",
15160
+ { kind: "path", path: resolvePathOperand(includePath, cwd) },
15161
+ segment,
15162
+ ["ruby.minitest_load_path_read"]
15163
+ )
15164
+ );
15165
+ }
15166
+ return lowered;
15167
+ }
14362
15168
  function decodeRuntimeMetadataProcess(head, args, segment) {
14363
15169
  if (head === "bundle") {
14364
15170
  if (args.length === 1 && isMetadataOnlyArgv(args)) {
@@ -14380,6 +15186,10 @@ function decodeRuntimeMetadataProcess(head, args, segment) {
14380
15186
  ])
14381
15187
  ];
14382
15188
  }
15189
+ const bundleExecInner = decodeBundleExecInner(innerHead, innerArgs, segment);
15190
+ if (bundleExecInner) {
15191
+ return bundleExecInner;
15192
+ }
14383
15193
  }
14384
15194
  return null;
14385
15195
  }
@@ -14395,9 +15205,74 @@ function decodeRuntimeMetadataProcess(head, args, segment) {
14395
15205
  }
14396
15206
  return null;
14397
15207
  }
15208
+ function decodeSetBuiltin(args) {
15209
+ let index = 0;
15210
+ while (index < args.length) {
15211
+ const arg = args[index] ?? "";
15212
+ if (arg === "--") {
15213
+ index += 1;
15214
+ continue;
15215
+ }
15216
+ if (arg === "-o" || arg === "+o") {
15217
+ if (!args[index + 1]) {
15218
+ return false;
15219
+ }
15220
+ index += 2;
15221
+ continue;
15222
+ }
15223
+ if (/^[-+][A-Za-z0-9]+$/.test(arg)) {
15224
+ index += 1;
15225
+ continue;
15226
+ }
15227
+ return false;
15228
+ }
15229
+ return true;
15230
+ }
15231
+ function decodeShellControlBuiltin(head, args) {
15232
+ if (head === "set") {
15233
+ return decodeSetBuiltin(args) ? [] : null;
15234
+ }
15235
+ if (head === "wait") {
15236
+ if (args.length === 0 || args.every((arg) => /^\d+$/.test(arg))) {
15237
+ return [];
15238
+ }
15239
+ return null;
15240
+ }
15241
+ if (head === "exit") {
15242
+ if (args.length === 0 || args.length === 1 && /^-?\d+$/.test(args[0] ?? "")) {
15243
+ return [];
15244
+ }
15245
+ return null;
15246
+ }
15247
+ return null;
15248
+ }
15249
+ function decodeDockerComposeRun2(head, args, segment) {
15250
+ let composeArgs = null;
15251
+ let command = head;
15252
+ if (head === "docker-compose") {
15253
+ composeArgs = args;
15254
+ } else if (head === "docker" && args[0] === "compose") {
15255
+ composeArgs = args.slice(1);
15256
+ command = "docker";
15257
+ }
15258
+ if (!composeArgs) {
15259
+ return null;
15260
+ }
15261
+ if (composeArgs.includes("run")) {
15262
+ return [processRequirement(command, "spawn", segment, ["process.docker_compose_run"])];
15263
+ }
15264
+ return unsupportedProcess(command, segment, "process.docker_compose_grammar_incomplete");
15265
+ }
14398
15266
  function decodeProcessOrFilesystem(params) {
14399
15267
  const { tokens, head, env, cwd, repoRoot, segment } = params;
14400
15268
  const args = tokens.slice(1);
15269
+ if (tokens.length > 0 && tokens.every((token) => ENV_PREFIX_PATTERN2.test(token))) {
15270
+ return [];
15271
+ }
15272
+ const shellControl = decodeShellControlBuiltin(head, args);
15273
+ if (shellControl) {
15274
+ return shellControl;
15275
+ }
14401
15276
  if (isCommandInspection(tokens)) {
14402
15277
  return [processRequirement(head, "inspect", segment, ["process.inspect.command_lookup"])];
14403
15278
  }
@@ -14465,7 +15340,7 @@ function decodeProcessOrFilesystem(params) {
14465
15340
  requirement2(
14466
15341
  "fs.read",
14467
15342
  "fs.read",
14468
- { kind: "path", path: path39.resolve(cwd, syntax) },
15343
+ { kind: "path", path: path41.resolve(cwd, syntax) },
14469
15344
  segment,
14470
15345
  ["shell.syntax_source_read"]
14471
15346
  )
@@ -14480,6 +15355,19 @@ function decodeProcessOrFilesystem(params) {
14480
15355
  if (runtimeMetadata) {
14481
15356
  return runtimeMetadata;
14482
15357
  }
15358
+ if (head === "ruby") {
15359
+ return decodeRuby(args, cwd, repoRoot, segment);
15360
+ }
15361
+ if (head === "rubocop" || head === "rspec") {
15362
+ const decoded = decodeBundleExecInner(head, args, segment);
15363
+ if (decoded) {
15364
+ return decoded;
15365
+ }
15366
+ }
15367
+ const dockerCompose = decodeDockerComposeRun2(head, args, segment);
15368
+ if (dockerCompose) {
15369
+ return dockerCompose;
15370
+ }
14483
15371
  if ((head === "npm" || head === "pnpm") && args.length === 1 && isMetadataOnlyArgv(args)) {
14484
15372
  return [processRequirement(head, "inspect", segment, ["process.inspect.package_manager"])];
14485
15373
  }
@@ -14502,7 +15390,7 @@ function decodeProcessOrFilesystem(params) {
14502
15390
  return [processRequirement(head, "inspect", segment, ["process.inspect.base64_stdin"])];
14503
15391
  }
14504
15392
  if (head === "node") {
14505
- return decodeNode(args, cwd, segment);
15393
+ return decodeNode2(args, cwd, segment);
14506
15394
  }
14507
15395
  if (head === "vite" || head === "vite-node") {
14508
15396
  return [processRequirement(head, "spawn", segment, ["process.local_dev_spawn"])];
@@ -14592,7 +15480,7 @@ function decodeBelay(args, repoRoot, segment) {
14592
15480
  requirement2(
14593
15481
  "control_plane.write",
14594
15482
  "control_plane.write",
14595
- { kind: "path", path: path39.join(repoRoot, ".belay-control-plane") },
15483
+ { kind: "path", path: path41.join(repoRoot, ".belay-control-plane") },
14596
15484
  segment,
14597
15485
  ["belay.config_non_judge_mutation"]
14598
15486
  )
@@ -14754,11 +15642,11 @@ function decodeRm(args, cwd, repoRoot, segment) {
14754
15642
  function canonicalRmOperand(targetPath, finalOperandIsSymlink) {
14755
15643
  try {
14756
15644
  if (finalOperandIsSymlink) {
14757
- return path39.join(realpathSync4.native(path39.dirname(targetPath)), path39.basename(targetPath));
15645
+ return path41.join(realpathSync4.native(path41.dirname(targetPath)), path41.basename(targetPath));
14758
15646
  }
14759
15647
  return realpathSync4.native(targetPath);
14760
15648
  } catch {
14761
- return path39.resolve(targetPath);
15649
+ return path41.resolve(targetPath);
14762
15650
  }
14763
15651
  }
14764
15652
  function isSymbolicLink(targetPath) {
@@ -14769,8 +15657,8 @@ function isSymbolicLink(targetPath) {
14769
15657
  }
14770
15658
  }
14771
15659
  function pathContains(ancestor, candidate) {
14772
- const relative = path39.relative(path39.resolve(ancestor), path39.resolve(candidate));
14773
- 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);
14774
15662
  }
14775
15663
  function decodeGo(args, segment) {
14776
15664
  if (["test", "list", "vet"].includes(args[0] ?? "")) {
@@ -14916,7 +15804,7 @@ function decodeSed(args, cwd, segment) {
14916
15804
  }
14917
15805
  return lowered;
14918
15806
  }
14919
- function decodeNode(args, cwd, segment) {
15807
+ function decodeNode2(args, cwd, segment) {
14920
15808
  if (args.length > 0 && args.every((arg) => ["--help", "--version", "-h", "-v"].includes(arg))) {
14921
15809
  return [processRequirement("node", "inspect", segment, ["process.inspect.node_metadata"])];
14922
15810
  }
@@ -15182,15 +16070,36 @@ function stripRedirects(tokens) {
15182
16070
  stripped.push(token);
15183
16071
  continue;
15184
16072
  }
15185
- if (token.includes(">") || token.includes("<")) {
15186
- const inline = token.replace(/^\d*(?:>>?|<<?|<>|>\|)/, "");
15187
- if (!inline) {
15188
- index += 1;
15189
- }
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;
15190
16088
  }
16089
+ index += 1;
15191
16090
  }
15192
16091
  return stripped;
15193
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
+ }
15194
16103
  function shellSegment(commandRedacted, segmentHead, requirements, opacity, signals) {
15195
16104
  const normalizedRequirements = requirements.flatMap((entry) => {
15196
16105
  const dynamicSignal = dynamicResourceSignal(entry.resource);
@@ -15590,9 +16499,9 @@ function resolvePathOperand(operand, cwd) {
15590
16499
  return process.env.HOME ?? operand;
15591
16500
  }
15592
16501
  if (operand.startsWith("~/")) {
15593
- return path39.join(process.env.HOME ?? "~", operand.slice(2));
16502
+ return path41.join(process.env.HOME ?? "~", operand.slice(2));
15594
16503
  }
15595
- return path39.resolve(cwd, operand);
16504
+ return path41.resolve(cwd, operand);
15596
16505
  }
15597
16506
  function isShellHead(head) {
15598
16507
  return head === "bash" || head === "sh" || head === "zsh" || head === "dash" || head === "fish";
@@ -16116,7 +17025,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
16116
17025
  };
16117
17026
  }
16118
17027
  const signals = [];
16119
- const resolvedPath = path40.isAbsolute(filePath) ? filePath : path40.resolve(cwd, filePath);
17028
+ const resolvedPath = path42.isAbsolute(filePath) ? filePath : path42.resolve(cwd, filePath);
16120
17029
  const hitsProtectedRoot = protectedRoots.some((root) => pathWithinRoot(root, resolvedPath));
16121
17030
  if (hitsProtectedRoot) {
16122
17031
  signals.push("control_plane_path");
@@ -16718,7 +17627,7 @@ function hashDecisionConfig(config) {
16718
17627
  init_fingerprint2();
16719
17628
 
16720
17629
  // src/version.ts
16721
- var PACKAGE_VERSION = "0.9.1";
17630
+ var PACKAGE_VERSION = "0.9.3";
16722
17631
 
16723
17632
  // src/runtime-provenance.ts
16724
17633
  function resolveRuntimeArtifactHash(artifactHash) {
@@ -16793,7 +17702,7 @@ init_path_utils();
16793
17702
  import { randomUUID as randomUUID5 } from "node:crypto";
16794
17703
  import { existsSync as existsSync15 } from "node:fs";
16795
17704
  import { mkdir as mkdir11, readdir as readdir3, readFile as readFile12, rename as rename3, rm as rm6 } from "node:fs/promises";
16796
- import path45 from "node:path";
17705
+ import path47 from "node:path";
16797
17706
 
16798
17707
  // src/core/recovery/artifact-store.ts
16799
17708
  init_fingerprint2();
@@ -16801,7 +17710,7 @@ init_path_utils();
16801
17710
  import { randomUUID as randomUUID4 } from "node:crypto";
16802
17711
  import { existsSync as existsSync13 } from "node:fs";
16803
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";
16804
- import path42 from "node:path";
17713
+ import path44 from "node:path";
16805
17714
 
16806
17715
  // src/core/recovery/snapshot-node.ts
16807
17716
  init_fingerprint2();
@@ -16821,25 +17730,25 @@ import {
16821
17730
  symlink as symlink3,
16822
17731
  writeFile as writeFile6
16823
17732
  } from "node:fs/promises";
16824
- import path41 from "node:path";
17733
+ import path43 from "node:path";
16825
17734
  var RECOVERY_UNSUPPORTED_FILE_KIND = "recovery_unsupported_file_kind";
16826
17735
  function validRecoveryRelativePath(relativePath) {
16827
- if (!relativePath || relativePath.includes("\0") || path41.isAbsolute(relativePath)) return false;
16828
- const normalized = path41.normalize(relativePath);
16829
- 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}`);
16830
17739
  }
16831
17740
  async function assertRecoverySafeTarget(resourceRoot, relativePath) {
16832
17741
  if (!validRecoveryRelativePath(relativePath)) throw new Error("recovery_path_escape");
16833
17742
  const root = canonicalPath(resourceRoot);
16834
- const target = path41.resolve(root, relativePath);
16835
- const relative = path41.relative(root, target);
16836
- 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)) {
16837
17746
  throw new Error("recovery_path_escape");
16838
17747
  }
16839
17748
  let current = root;
16840
- 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);
16841
17750
  for (const part of parentParts) {
16842
- current = path41.join(current, part);
17751
+ current = path43.join(current, part);
16843
17752
  if (!existsSync12(current)) break;
16844
17753
  const info = await lstat6(current);
16845
17754
  if (info.isSymbolicLink()) throw new Error("recovery_symlink_escape");
@@ -16895,7 +17804,7 @@ async function captureRecoverySnapshot(filePath, options) {
16895
17804
  let blob;
16896
17805
  if (options?.blobDir) {
16897
17806
  await mkdir9(options.blobDir, { recursive: true, mode: 448 });
16898
- const blobPath = path41.join(options.blobDir, hash);
17807
+ const blobPath = path43.join(options.blobDir, hash);
16899
17808
  if (!existsSync12(blobPath)) {
16900
17809
  await writeFile6(blobPath, content, { mode: 384 });
16901
17810
  await fsyncPath(blobPath);
@@ -16950,7 +17859,7 @@ async function validateRecoverySnapshot(params) {
16950
17859
  if (record.blob !== `blobs/${record.hash}`) throw new Error(params.corruptReason);
16951
17860
  let content;
16952
17861
  try {
16953
- content = await readFile8(path41.join(params.artifactDir, record.blob));
17862
+ content = await readFile8(path43.join(params.artifactDir, record.blob));
16954
17863
  } catch {
16955
17864
  throw new Error(params.corruptReason);
16956
17865
  }
@@ -16978,13 +17887,13 @@ var RECOVERY_STATES = /* @__PURE__ */ new Set([
16978
17887
  ]);
16979
17888
  var STAGING_STALE_MS = 5 * 6e4;
16980
17889
  function checkpointsRoot(stateDir) {
16981
- return path42.join(stateDir, "recovery", "checkpoints");
17890
+ return path44.join(stateDir, "recovery", "checkpoints");
16982
17891
  }
16983
17892
  function checkpointDir(stateDir, checkpointId) {
16984
17893
  if (!/^cp_[a-f0-9]{24}$/.test(checkpointId)) {
16985
17894
  throw new Error("invalid_recovery_checkpoint_id");
16986
17895
  }
16987
- return path42.join(checkpointsRoot(stateDir), checkpointId);
17896
+ return path44.join(checkpointsRoot(stateDir), checkpointId);
16988
17897
  }
16989
17898
  async function fsyncPath2(filePath) {
16990
17899
  const handle = await open5(filePath, "r");
@@ -16995,13 +17904,13 @@ async function fsyncPath2(filePath) {
16995
17904
  }
16996
17905
  }
16997
17906
  async function atomicWriteJson(filePath, value) {
16998
- await mkdir10(path42.dirname(filePath), { recursive: true, mode: 448 });
17907
+ await mkdir10(path44.dirname(filePath), { recursive: true, mode: 448 });
16999
17908
  const temporary = `${filePath}.tmp-${randomUUID4()}`;
17000
17909
  await writeFile7(temporary, `${JSON.stringify(value, null, 2)}
17001
17910
  `, { mode: 384 });
17002
17911
  await fsyncPath2(temporary);
17003
17912
  await rename2(temporary, filePath);
17004
- await fsyncPath2(path42.dirname(filePath));
17913
+ await fsyncPath2(path44.dirname(filePath));
17005
17914
  }
17006
17915
  async function writeRecoveryState(artifactDir, state, manifestHash, detail) {
17007
17916
  const value = {
@@ -17011,13 +17920,13 @@ async function writeRecoveryState(artifactDir, state, manifestHash, detail) {
17011
17920
  manifestHash,
17012
17921
  ...detail ? { detail } : {}
17013
17922
  };
17014
- await atomicWriteJson(path42.join(artifactDir, "state.json"), value);
17923
+ await atomicWriteJson(path44.join(artifactDir, "state.json"), value);
17015
17924
  }
17016
17925
  async function directorySize(root) {
17017
17926
  if (!existsSync13(root)) return 0;
17018
17927
  let total = 0;
17019
17928
  for (const entry of await readdir2(root, { withFileTypes: true })) {
17020
- const entryPath = path42.join(root, entry.name);
17929
+ const entryPath = path44.join(root, entry.name);
17021
17930
  if (entry.isDirectory()) total += await directorySize(entryPath);
17022
17931
  else total += (await lstat7(entryPath)).size;
17023
17932
  }
@@ -17062,9 +17971,9 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
17062
17971
  let rawManifest;
17063
17972
  let state;
17064
17973
  try {
17065
- rawManifest = JSON.parse(await readFile9(path42.join(artifactDir, "manifest.json"), "utf8"));
17974
+ rawManifest = JSON.parse(await readFile9(path44.join(artifactDir, "manifest.json"), "utf8"));
17066
17975
  state = JSON.parse(
17067
- await readFile9(path42.join(artifactDir, "state.json"), "utf8")
17976
+ await readFile9(path44.join(artifactDir, "state.json"), "utf8")
17068
17977
  );
17069
17978
  } catch {
17070
17979
  throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
@@ -17080,10 +17989,10 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
17080
17989
  }
17081
17990
  const entryPaths = /* @__PURE__ */ new Set();
17082
17991
  for (const entry of manifest.entries) {
17083
- 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))) {
17084
17993
  throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
17085
17994
  }
17086
- entryPaths.add(path42.normalize(entry.path));
17995
+ entryPaths.add(path44.normalize(entry.path));
17087
17996
  for (const [side, snapshot] of [
17088
17997
  ["before", entry.before],
17089
17998
  ["after", entry.after]
@@ -17097,7 +18006,7 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
17097
18006
  });
17098
18007
  }
17099
18008
  }
17100
- const receiptPath = path42.join(artifactDir, "receipt.json");
18009
+ const receiptPath = path44.join(artifactDir, "receipt.json");
17101
18010
  let receipt;
17102
18011
  if (["applied", "restoring", "restored", "conflict"].includes(state.state) || existsSync13(receiptPath)) {
17103
18012
  receipt = await readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash);
@@ -17107,7 +18016,7 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
17107
18016
  async function readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash) {
17108
18017
  let rawReceipt;
17109
18018
  try {
17110
- rawReceipt = JSON.parse(await readFile9(path42.join(artifactDir, "receipt.json"), "utf8"));
18019
+ rawReceipt = JSON.parse(await readFile9(path44.join(artifactDir, "receipt.json"), "utf8"));
17111
18020
  } catch {
17112
18021
  throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
17113
18022
  }
@@ -17130,7 +18039,7 @@ async function readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHas
17130
18039
  return receipt;
17131
18040
  }
17132
18041
  async function ensureRecoveryReceipt(artifactDir, manifest, manifestHash) {
17133
- const receiptPath = path42.join(artifactDir, "receipt.json");
18042
+ const receiptPath = path44.join(artifactDir, "receipt.json");
17134
18043
  if (existsSync13(receiptPath)) {
17135
18044
  return readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash);
17136
18045
  }
@@ -17155,7 +18064,7 @@ async function artifactRepoRoot(stateDir, checkpointId) {
17155
18064
  const artifactDir = checkpointDir(stateDir, checkpointId);
17156
18065
  try {
17157
18066
  const manifest = JSON.parse(
17158
- await readFile9(path42.join(artifactDir, "manifest.json"), "utf8")
18067
+ await readFile9(path44.join(artifactDir, "manifest.json"), "utf8")
17159
18068
  );
17160
18069
  if (typeof manifest.repoRoot === "string" && manifest.repoRoot) {
17161
18070
  return canonicalPath(manifest.repoRoot);
@@ -17163,7 +18072,7 @@ async function artifactRepoRoot(stateDir, checkpointId) {
17163
18072
  } catch {
17164
18073
  }
17165
18074
  try {
17166
- 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"));
17167
18076
  return typeof owner.repoRoot === "string" && owner.repoRoot ? canonicalPath(owner.repoRoot) : null;
17168
18077
  } catch {
17169
18078
  return null;
@@ -17183,10 +18092,10 @@ async function cleanupOrphanedStaging(stateDir) {
17183
18092
  const now = Date.now();
17184
18093
  for (const entry of await readdir2(root, { withFileTypes: true })) {
17185
18094
  if (!entry.isDirectory() || !/^\.tmp-cp_[a-f0-9]{24}$/.test(entry.name)) continue;
17186
- const stagingPath = path42.join(root, entry.name);
18095
+ const stagingPath = path44.join(root, entry.name);
17187
18096
  let stale = false;
17188
18097
  try {
17189
- 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"));
17190
18099
  const pid = typeof owner.pid === "number" ? owner.pid : Number.NaN;
17191
18100
  const createdAt = typeof owner.createdAt === "string" ? Date.parse(owner.createdAt) : NaN;
17192
18101
  let alive = false;
@@ -17228,7 +18137,7 @@ async function markRecoveryCheckpointApplied(stateDir, checkpoint) {
17228
18137
  init_fingerprint2();
17229
18138
  import { existsSync as existsSync14 } from "node:fs";
17230
18139
  import { readFile as readFile10 } from "node:fs/promises";
17231
- import path43 from "node:path";
18140
+ import path45 from "node:path";
17232
18141
  async function matchRecoverySide(resourceRoot, entries, side) {
17233
18142
  for (const entry of entries) {
17234
18143
  const target = await assertRecoverySafeTarget(resourceRoot, entry.path);
@@ -17243,7 +18152,7 @@ async function reconcileRecoveryCheckpoint(stateDir, checkpointId) {
17243
18152
  } catch {
17244
18153
  const artifactDir = checkpointDir(stateDir, checkpointId);
17245
18154
  if (existsSync14(artifactDir)) {
17246
- const manifestPath = path43.join(artifactDir, "manifest.json");
18155
+ const manifestPath = path45.join(artifactDir, "manifest.json");
17247
18156
  const hash = existsSync14(manifestPath) ? hashValue(await readFile10(manifestPath, "utf8")) : "unavailable";
17248
18157
  await writeRecoveryState(artifactDir, "corrupt", hash, RECOVERY_CHECKPOINT_CORRUPT);
17249
18158
  }
@@ -17280,7 +18189,7 @@ async function reconcileRecoveryCheckpoint(stateDir, checkpointId) {
17280
18189
  // src/core/recovery/resource-identity.ts
17281
18190
  init_fingerprint2();
17282
18191
  import { lstat as lstat8, readFile as readFile11, realpath as realpath3 } from "node:fs/promises";
17283
- import path44 from "node:path";
18192
+ import path46 from "node:path";
17284
18193
  async function currentRecoveryResourceIdentity(resourceRoot, resourceKind) {
17285
18194
  const resolvedRoot = await realpath3(resourceRoot);
17286
18195
  if (resourceKind === "directory") {
@@ -17288,13 +18197,13 @@ async function currentRecoveryResourceIdentity(resourceRoot, resourceKind) {
17288
18197
  if (!rootInfo.isDirectory()) throw new Error("recovery_repo_identity_unavailable");
17289
18198
  return hashValue(`${resolvedRoot}\0${rootInfo.dev}:${rootInfo.ino}:${rootInfo.birthtimeMs}`);
17290
18199
  }
17291
- const dotGit = path44.join(resolvedRoot, ".git");
18200
+ const dotGit = path46.join(resolvedRoot, ".git");
17292
18201
  const gitInfo = await lstat8(dotGit);
17293
18202
  let gitMetadataPath = dotGit;
17294
18203
  if (gitInfo.isFile()) {
17295
18204
  const marker = (await readFile11(dotGit, "utf8")).trim();
17296
18205
  if (!marker.startsWith("gitdir:")) throw new Error("recovery_repo_identity_unavailable");
17297
- gitMetadataPath = path44.resolve(resolvedRoot, marker.slice("gitdir:".length).trim());
18206
+ gitMetadataPath = path46.resolve(resolvedRoot, marker.slice("gitdir:".length).trim());
17298
18207
  } else if (!gitInfo.isDirectory()) {
17299
18208
  throw new Error("recovery_repo_identity_unavailable");
17300
18209
  }
@@ -17367,16 +18276,16 @@ async function prepareRecoveryCheckpoint(params) {
17367
18276
  throw new Error(RECOVERY_CHECKPOINT_QUOTA);
17368
18277
  }
17369
18278
  const checkpointId = `cp_${randomUUID5().replaceAll("-", "").slice(0, 24)}`;
17370
- const temporary = path45.join(checkpointsRoot(params.stateDir), `.tmp-${checkpointId}`);
18279
+ const temporary = path47.join(checkpointsRoot(params.stateDir), `.tmp-${checkpointId}`);
17371
18280
  const finalDir = checkpointDir(params.stateDir, checkpointId);
17372
18281
  await mkdir11(temporary, { recursive: true, mode: 448 });
17373
- await atomicWriteJson(path45.join(temporary, "owner.json"), {
18282
+ await atomicWriteJson(path47.join(temporary, "owner.json"), {
17374
18283
  version: 1,
17375
18284
  pid: process.pid,
17376
18285
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
17377
18286
  repoRoot: canonicalPath(params.repoRoot)
17378
18287
  });
17379
- await mkdir11(path45.join(temporary, "blobs"), { recursive: true, mode: 448 });
18288
+ await mkdir11(path47.join(temporary, "blobs"), { recursive: true, mode: 448 });
17380
18289
  try {
17381
18290
  const entries = [];
17382
18291
  const protectedRoots = (params.protectedRoots ?? []).map(canonicalPath);
@@ -17385,8 +18294,8 @@ async function prepareRecoveryCheckpoint(params) {
17385
18294
  )) {
17386
18295
  const target = await assertRecoverySafeTarget(params.repoRoot, change.relativePath);
17387
18296
  if (protectedRoots.some((root) => {
17388
- const relative = path45.relative(root, target);
17389
- 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);
17390
18299
  })) {
17391
18300
  throw new Error("recovery_protected_path");
17392
18301
  }
@@ -17398,7 +18307,7 @@ async function prepareRecoveryCheckpoint(params) {
17398
18307
  entries.push({
17399
18308
  path: change.relativePath,
17400
18309
  before: await captureRecoverySnapshot(baseline, {
17401
- blobDir: path45.join(temporary, "blobs")
18310
+ blobDir: path47.join(temporary, "blobs")
17402
18311
  }),
17403
18312
  after: withoutRecoveryBlob(await captureRecoverySnapshot(source))
17404
18313
  });
@@ -17435,7 +18344,7 @@ async function prepareRecoveryCheckpoint(params) {
17435
18344
  entries
17436
18345
  };
17437
18346
  const manifestHash = hashValue(canonicalStringify(manifest));
17438
- await atomicWriteJson(path45.join(temporary, "manifest.json"), manifest);
18347
+ await atomicWriteJson(path47.join(temporary, "manifest.json"), manifest);
17439
18348
  await writeRecoveryState(temporary, "prepared", manifestHash);
17440
18349
  await fsyncPath2(temporary);
17441
18350
  const projectedBytes = await recoveryCheckpointStorageBytes(params.stateDir, params.repoRoot);
@@ -17478,7 +18387,7 @@ async function listRecoveryCheckpoints(stateDir, repoRoot) {
17478
18387
  } catch {
17479
18388
  try {
17480
18389
  const raw = JSON.parse(
17481
- await readFile12(path45.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
18390
+ await readFile12(path47.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
17482
18391
  );
17483
18392
  rootFromArtifact = typeof raw.repoRoot === "string" && raw.repoRoot ? raw.repoRoot : void 0;
17484
18393
  } catch {
@@ -17512,7 +18421,7 @@ async function listRecoveryCheckpoints(stateDir, repoRoot) {
17512
18421
  } catch {
17513
18422
  try {
17514
18423
  const manifest = JSON.parse(
17515
- await readFile12(path45.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
18424
+ await readFile12(path47.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
17516
18425
  );
17517
18426
  if (manifest.checkpointId !== id || ![1, 2].includes(manifest.version)) continue;
17518
18427
  if (repoRoot && canonicalPath(manifest.repoRoot) !== canonicalPath(repoRoot)) continue;
@@ -17542,7 +18451,7 @@ async function recoveryCheckpointStorageBytes(stateDir, repoRoot) {
17542
18451
  let total = 0;
17543
18452
  for (const entry of await readdir3(root, { withFileTypes: true })) {
17544
18453
  if (!entry.isDirectory()) continue;
17545
- const entryPath = path45.join(root, entry.name);
18454
+ const entryPath = path47.join(root, entry.name);
17546
18455
  if (/^cp_[a-f0-9]{24}$/.test(entry.name)) {
17547
18456
  if (await artifactRepoRoot(stateDir, entry.name) === expected) {
17548
18457
  total += await directorySize(entryPath);
@@ -17551,7 +18460,7 @@ async function recoveryCheckpointStorageBytes(stateDir, repoRoot) {
17551
18460
  }
17552
18461
  if (/^\.tmp-cp_[a-f0-9]{24}$/.test(entry.name)) {
17553
18462
  try {
17554
- 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"));
17555
18464
  if (typeof owner.repoRoot === "string" && canonicalPath(owner.repoRoot) === expected) {
17556
18465
  total += await directorySize(entryPath);
17557
18466
  }
@@ -17595,14 +18504,14 @@ init_scrub();
17595
18504
  // src/core/transactional/file-checkpoint-backend.ts
17596
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";
17597
18506
  import os5 from "node:os";
17598
- import path49 from "node:path";
18507
+ import path51 from "node:path";
17599
18508
 
17600
18509
  // src/core/transactional/file-checkpoint-git.ts
17601
18510
  init_path_utils();
17602
18511
  import { spawn as spawn8 } from "node:child_process";
17603
18512
  import { createHash as createHash13 } from "node:crypto";
17604
18513
  import { copyFile as copyFile3, lstat as lstat9, readdir as readdir4, readFile as readFile13 } from "node:fs/promises";
17605
- import path46 from "node:path";
18514
+ import path48 from "node:path";
17606
18515
  var FILE_CHECKPOINT_GIT_METADATA_CHANGED = "file_checkpoint_git_metadata_changed";
17607
18516
  var FILE_CHECKPOINT_SOURCE_CHANGED = "file_checkpoint_source_changed";
17608
18517
  var FILE_CHECKPOINT_CWD_OUTSIDE_ROOT = "file_checkpoint_cwd_outside_root";
@@ -17636,7 +18545,7 @@ function rethrowStableFileCheckpointError(error) {
17636
18545
  }
17637
18546
  async function rootGitMetadataPresent(repoRoot) {
17638
18547
  try {
17639
- await lstat9(path46.join(repoRoot, ".git"));
18548
+ await lstat9(path48.join(repoRoot, ".git"));
17640
18549
  return true;
17641
18550
  } catch {
17642
18551
  return false;
@@ -17685,10 +18594,10 @@ function execGit2(repoRoot, args) {
17685
18594
  }
17686
18595
  async function resolveGitPath(repoRoot, gitPath) {
17687
18596
  const trimmed = gitPath.trim();
17688
- if (path46.isAbsolute(trimmed)) {
18597
+ if (path48.isAbsolute(trimmed)) {
17689
18598
  return trimmed;
17690
18599
  }
17691
- return path46.join(repoRoot, trimmed);
18600
+ return path48.join(repoRoot, trimmed);
17692
18601
  }
17693
18602
  async function cloneBareWorktreeCopy(sourceRoot, destinationRoot) {
17694
18603
  await execGit2(sourceRoot, [
@@ -17730,7 +18639,7 @@ async function copyGitIndexState(sourceRoot, destinationRoot) {
17730
18639
  destinationRoot,
17731
18640
  await execGit2(destinationRoot, ["rev-parse", "--git-dir"])
17732
18641
  );
17733
- const destinationShared = path46.join(destinationGitDir, path46.basename(sourceShared));
18642
+ const destinationShared = path48.join(destinationGitDir, path48.basename(sourceShared));
17734
18643
  try {
17735
18644
  await copyFile3(sourceShared, destinationShared);
17736
18645
  } catch (error) {
@@ -17739,7 +18648,7 @@ async function copyGitIndexState(sourceRoot, destinationRoot) {
17739
18648
  }
17740
18649
  async function readGitFile(gitDir, relativePath) {
17741
18650
  try {
17742
- return await readFile13(path46.join(gitDir, relativePath));
18651
+ return await readFile13(path48.join(gitDir, relativePath));
17743
18652
  } catch {
17744
18653
  return null;
17745
18654
  }
@@ -17763,8 +18672,8 @@ async function hashResolvedGitPath(repoRoot, gitDir, gitPath, hash) {
17763
18672
  repoRoot,
17764
18673
  await execGit2(repoRoot, ["rev-parse", "--git-path", gitPath])
17765
18674
  );
17766
- const relative = path46.resolve(resolved).startsWith(path46.resolve(gitDir)) ? path46.relative(gitDir, resolved) : resolved;
17767
- 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);
17768
18677
  if (content !== null) {
17769
18678
  hashGitFileContent(hash, gitPath, content);
17770
18679
  }
@@ -17774,13 +18683,13 @@ async function hashResolvedGitPath(repoRoot, gitDir, gitPath, hash) {
17774
18683
  async function hashGitTree(gitDir, relativeDir, hash) {
17775
18684
  let names;
17776
18685
  try {
17777
- names = await readdir4(path46.join(gitDir, relativeDir));
18686
+ names = await readdir4(path48.join(gitDir, relativeDir));
17778
18687
  } catch {
17779
18688
  return;
17780
18689
  }
17781
18690
  for (const name of names.sort()) {
17782
- const relativePath = relativeDir ? path46.join(relativeDir, name) : name;
17783
- const absolutePath = path46.join(gitDir, relativePath);
18691
+ const relativePath = relativeDir ? path48.join(relativeDir, name) : name;
18692
+ const absolutePath = path48.join(gitDir, relativePath);
17784
18693
  let childNames = null;
17785
18694
  try {
17786
18695
  childNames = await readdir4(absolutePath);
@@ -17802,7 +18711,7 @@ async function hashGitTree(gitDir, relativeDir, hash) {
17802
18711
  }
17803
18712
  async function computeGitMetadataFingerprint(repoRoot) {
17804
18713
  const gitDirRel = (await execGit2(repoRoot, ["rev-parse", "--git-dir"])).trim();
17805
- const gitDir = path46.isAbsolute(gitDirRel) ? gitDirRel : path46.join(repoRoot, gitDirRel);
18714
+ const gitDir = path48.isAbsolute(gitDirRel) ? gitDirRel : path48.join(repoRoot, gitDirRel);
17806
18715
  const hash = createHash13("sha256");
17807
18716
  for (const file of [
17808
18717
  "HEAD",
@@ -17825,8 +18734,8 @@ async function computeGitMetadataFingerprint(repoRoot) {
17825
18734
  const sharedIndex = (await execGit2(repoRoot, ["rev-parse", "--shared-index-path"])).trim();
17826
18735
  if (sharedIndex) {
17827
18736
  const resolved = await resolveGitPath(repoRoot, sharedIndex);
17828
- const relative = path46.relative(gitDir, resolved);
17829
- 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);
17830
18739
  if (content !== null) {
17831
18740
  hashGitFileContent(hash, "shared-index", content);
17832
18741
  }
@@ -17837,7 +18746,7 @@ async function computeGitMetadataFingerprint(repoRoot) {
17837
18746
  await hashResolvedGitPath(repoRoot, gitDir, gitPath, hash);
17838
18747
  }
17839
18748
  try {
17840
- const rootGitPath = path46.join(repoRoot, ".git");
18749
+ const rootGitPath = path48.join(repoRoot, ".git");
17841
18750
  const rootGitInfo = await lstat9(rootGitPath);
17842
18751
  if (rootGitInfo.isFile()) {
17843
18752
  const content = await readAbsoluteGitFile(rootGitPath);
@@ -17853,14 +18762,14 @@ async function computeGitMetadataFingerprint(repoRoot) {
17853
18762
  function resolveExecutionCwdRelative(resourceRoot, cwd) {
17854
18763
  const resolvedCwd = canonicalPath(cwd);
17855
18764
  const resourceCanonical = canonicalPath(resourceRoot);
17856
- const relative = path46.relative(resourceCanonical, resolvedCwd);
18765
+ const relative = path48.relative(resourceCanonical, resolvedCwd);
17857
18766
  if (relative === "" || relative === ".") {
17858
18767
  return "";
17859
18768
  }
17860
- if (relative.startsWith("..") || path46.isAbsolute(relative)) {
18769
+ if (relative.startsWith("..") || path48.isAbsolute(relative)) {
17861
18770
  throw new Error(FILE_CHECKPOINT_CWD_OUTSIDE_ROOT);
17862
18771
  }
17863
- return relative.split(path46.sep).join("/");
18772
+ return relative.split(path48.sep).join("/");
17864
18773
  }
17865
18774
 
17866
18775
  // src/core/transactional/file-checkpoint-isolation.ts
@@ -17882,7 +18791,7 @@ function fileCheckpointIsolationReason(context) {
17882
18791
 
17883
18792
  // src/core/transactional/file-checkpoint-staging.ts
17884
18793
  import { readdir as readdir5, readFile as readFile14, rm as rm7, writeFile as writeFile8 } from "node:fs/promises";
17885
- import path47 from "node:path";
18794
+ import path49 from "node:path";
17886
18795
  function isOwnerProcessAlive(pid) {
17887
18796
  try {
17888
18797
  process.kill(pid, 0);
@@ -17892,12 +18801,12 @@ function isOwnerProcessAlive(pid) {
17892
18801
  }
17893
18802
  }
17894
18803
  async function writeOwnerMarker(stagingRoot, marker) {
17895
- await writeFile8(path47.join(stagingRoot, "owner.json"), `${JSON.stringify(marker)}
18804
+ await writeFile8(path49.join(stagingRoot, "owner.json"), `${JSON.stringify(marker)}
17896
18805
  `, "utf8");
17897
18806
  }
17898
18807
  async function readOwnerMarker(stagingRoot) {
17899
18808
  try {
17900
- const raw = await readFile14(path47.join(stagingRoot, "owner.json"), "utf8");
18809
+ const raw = await readFile14(path49.join(stagingRoot, "owner.json"), "utf8");
17901
18810
  return JSON.parse(raw.trim());
17902
18811
  } catch {
17903
18812
  return null;
@@ -17915,7 +18824,7 @@ async function collectDeadOwnerStaging(parentDir) {
17915
18824
  if (!name.startsWith("belay-file-checkpoint-")) {
17916
18825
  continue;
17917
18826
  }
17918
- const stagingRoot = path47.join(parentDir, name);
18827
+ const stagingRoot = path49.join(parentDir, name);
17919
18828
  const marker = await readOwnerMarker(stagingRoot);
17920
18829
  if (!marker) {
17921
18830
  dead.push(stagingRoot);
@@ -17947,7 +18856,7 @@ import {
17947
18856
  writeFile as writeFile9
17948
18857
  } from "node:fs/promises";
17949
18858
  import os4 from "node:os";
17950
- import path48 from "node:path";
18859
+ import path50 from "node:path";
17951
18860
  var FILE_CHECKPOINT_COPY_FAILED = "file_checkpoint_copy_failed";
17952
18861
  async function chmodSafe2(target, mode) {
17953
18862
  try {
@@ -17957,7 +18866,7 @@ async function chmodSafe2(target, mode) {
17957
18866
  }
17958
18867
  }
17959
18868
  async function copyRegularFile(sourcePath, destinationPath, mode, strategy) {
17960
- await mkdir12(path48.dirname(destinationPath), { recursive: true });
18869
+ await mkdir12(path50.dirname(destinationPath), { recursive: true });
17961
18870
  if (strategy === "clonefile" && fsConstants2.COPYFILE_FICLONE !== void 0) {
17962
18871
  try {
17963
18872
  await copyFile4(sourcePath, destinationPath, fsConstants2.COPYFILE_FICLONE);
@@ -17983,7 +18892,7 @@ async function copyNode(sourceRoot, destinationRoot, relativePath, strategy) {
17983
18892
  return strategy;
17984
18893
  }
17985
18894
  if (info.isSymbolicLink()) {
17986
- await mkdir12(path48.dirname(destinationPath), { recursive: true });
18895
+ await mkdir12(path50.dirname(destinationPath), { recursive: true });
17987
18896
  await symlink4(await readlink5(sourcePath), destinationPath);
17988
18897
  return strategy;
17989
18898
  }
@@ -18039,9 +18948,9 @@ async function mapWithConcurrency(items, concurrency, worker) {
18039
18948
  async function probeFileCloneStrategy() {
18040
18949
  let tempDir = null;
18041
18950
  try {
18042
- tempDir = await mkdtemp4(path48.join(os4.tmpdir(), "belay-clone-probe-"));
18043
- const source = path48.join(tempDir, "source.txt");
18044
- 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");
18045
18954
  await writeFile9(source, "probe\n");
18046
18955
  if (fsConstants2.COPYFILE_FICLONE_FORCE !== void 0) {
18047
18956
  try {
@@ -18194,11 +19103,11 @@ async function protectedRootState(root) {
18194
19103
  return `directory:${node.hash}:${index.treeHash}`;
18195
19104
  }
18196
19105
  function executionProtectedRoot(resourceRoot, executionRoot, protectedRoot) {
18197
- const relative = path49.relative(path49.resolve(resourceRoot), path49.resolve(protectedRoot));
18198
- 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)) {
18199
19108
  return null;
18200
19109
  }
18201
- return path49.join(executionRoot, relative);
19110
+ return path51.join(executionRoot, relative);
18202
19111
  }
18203
19112
  async function captureProtectedRootStates(resourceRoot, executionRoot, protectedRoots) {
18204
19113
  const states = /* @__PURE__ */ new Map();
@@ -18223,15 +19132,15 @@ async function directoryByteSize(root, deadlineMs) {
18223
19132
  }
18224
19133
  let total = 0;
18225
19134
  for (const name of await readdir6(root)) {
18226
- total += await directoryByteSize(path49.join(root, name), deadlineMs);
19135
+ total += await directoryByteSize(path51.join(root, name), deadlineMs);
18227
19136
  }
18228
19137
  return total;
18229
19138
  }
18230
19139
  async function copyGitMetadataDirectory(sourceRoot, destinationRoot) {
18231
19140
  const gitDirRel = (await execGit2(sourceRoot, ["rev-parse", "--git-dir"])).trim();
18232
- const sourceGitDir = path49.isAbsolute(gitDirRel) ? gitDirRel : path49.join(sourceRoot, gitDirRel);
18233
- const relativeGitDir = path49.relative(path49.resolve(sourceRoot), path49.resolve(sourceGitDir));
18234
- 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");
18235
19144
  await cp(sourceGitDir, destinationGitDir, { recursive: true, force: true });
18236
19145
  }
18237
19146
  async function prepareDirtyGitSnapshot(context) {
@@ -18240,7 +19149,7 @@ async function prepareDirtyGitSnapshot(context) {
18240
19149
  const quotas = context.fileCheckpoint;
18241
19150
  const deadlineMs = Date.now() + quotas.prepareTimeoutMs;
18242
19151
  await removeDeadOwnerStaging(os5.tmpdir());
18243
- const stagingRoot = await mkdtemp5(path49.join(os5.tmpdir(), "belay-file-checkpoint-"));
19152
+ const stagingRoot = await mkdtemp5(path51.join(os5.tmpdir(), "belay-file-checkpoint-"));
18244
19153
  await writeOwnerMarker(stagingRoot, {
18245
19154
  version: 1,
18246
19155
  pid: process.pid,
@@ -18248,8 +19157,8 @@ async function prepareDirtyGitSnapshot(context) {
18248
19157
  resourceRoot: context.repoRoot,
18249
19158
  backend: "file_checkpoint"
18250
19159
  });
18251
- const baselineRoot = path49.join(stagingRoot, "baseline");
18252
- const executionRoot = path49.join(stagingRoot, "execution");
19160
+ const baselineRoot = path51.join(stagingRoot, "baseline");
19161
+ const executionRoot = path51.join(stagingRoot, "execution");
18253
19162
  try {
18254
19163
  resolveExecutionCwdRelative(context.repoRoot, context.cwd);
18255
19164
  const sourceGitMetadataFingerprint = await computeGitMetadataFingerprint(context.repoRoot);
@@ -18283,7 +19192,7 @@ async function prepareDirtyGitSnapshot(context) {
18283
19192
  throw new Error(FILE_CHECKPOINT_SOURCE_CHANGED);
18284
19193
  }
18285
19194
  await writeFile10(
18286
- path49.join(stagingRoot, "baseline-index.json"),
19195
+ path51.join(stagingRoot, "baseline-index.json"),
18287
19196
  `${JSON.stringify(baselineIndex)}
18288
19197
  `,
18289
19198
  "utf8"
@@ -18333,7 +19242,7 @@ async function prepareNonGitSnapshot(context) {
18333
19242
  const quotas = context.fileCheckpoint;
18334
19243
  const deadlineMs = Date.now() + quotas.prepareTimeoutMs;
18335
19244
  await removeDeadOwnerStaging(os5.tmpdir());
18336
- const stagingRoot = await mkdtemp5(path49.join(os5.tmpdir(), "belay-file-checkpoint-"));
19245
+ const stagingRoot = await mkdtemp5(path51.join(os5.tmpdir(), "belay-file-checkpoint-"));
18337
19246
  await writeOwnerMarker(stagingRoot, {
18338
19247
  version: 1,
18339
19248
  pid: process.pid,
@@ -18341,8 +19250,8 @@ async function prepareNonGitSnapshot(context) {
18341
19250
  resourceRoot: context.repoRoot,
18342
19251
  backend: "file_checkpoint"
18343
19252
  });
18344
- const baselineRoot = path49.join(stagingRoot, "baseline");
18345
- const executionRoot = path49.join(stagingRoot, "execution");
19253
+ const baselineRoot = path51.join(stagingRoot, "baseline");
19254
+ const executionRoot = path51.join(stagingRoot, "execution");
18346
19255
  try {
18347
19256
  resolveExecutionCwdRelative(context.repoRoot, context.cwd);
18348
19257
  const resourceIdentity = await currentRecoveryResourceIdentity(context.repoRoot, "directory");
@@ -18371,7 +19280,7 @@ async function prepareNonGitSnapshot(context) {
18371
19280
  throw new Error(FILE_CHECKPOINT_SOURCE_CHANGED);
18372
19281
  }
18373
19282
  await writeFile10(
18374
- path49.join(stagingRoot, "baseline-index.json"),
19283
+ path51.join(stagingRoot, "baseline-index.json"),
18375
19284
  `${JSON.stringify(baselineIndex)}
18376
19285
  `,
18377
19286
  "utf8"
@@ -18724,10 +19633,10 @@ async function selectTransactionalBackend(context) {
18724
19633
  }
18725
19634
 
18726
19635
  // src/core/transactional/diff-evaluator.ts
18727
- import path50 from "node:path";
19636
+ import path52 from "node:path";
18728
19637
  init_path_utils();
18729
19638
  function categorizeChange(change, ctx) {
18730
- const absolutePath = canonicalPath(path50.join(ctx.repoRoot, change.relativePath));
19639
+ const absolutePath = canonicalPath(path52.join(ctx.repoRoot, change.relativePath));
18731
19640
  if (!pathWithinRoot(ctx.repoRoot, absolutePath)) {
18732
19641
  return "repo_outside";
18733
19642
  }
@@ -19321,7 +20230,7 @@ async function notifyDeny(config, event) {
19321
20230
  init_path_utils();
19322
20231
 
19323
20232
  // src/adapters/layouts/protected-paths.ts
19324
- import path51 from "node:path";
20233
+ import path53 from "node:path";
19325
20234
  function protectedArtifactRoots(layout, repoRoot, controlPlaneDir) {
19326
20235
  const roots = [
19327
20236
  layout.configPath(repoRoot),
@@ -19333,7 +20242,7 @@ function protectedArtifactRoots(layout, repoRoot, controlPlaneDir) {
19333
20242
  if (controlPlaneDir) {
19334
20243
  roots.push(controlPlaneDir);
19335
20244
  }
19336
- return roots.map((entry) => path51.resolve(entry));
20245
+ return roots.map((entry) => path53.resolve(entry));
19337
20246
  }
19338
20247
 
19339
20248
  // src/adapters/shared/gate-runtime.ts
@@ -19386,8 +20295,8 @@ function createDefaultGateRuntimeDeps() {
19386
20295
  return loadJsonFile(configPath, {});
19387
20296
  },
19388
20297
  async appendAudit(ctx, event) {
19389
- const auditPath = path52.join(ctx.repoRoot, ctx.config.audit.logPath);
19390
- 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 });
19391
20300
  const provenance = auditProvenance(ctx.config);
19392
20301
  const record = {
19393
20302
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -19420,7 +20329,7 @@ function createDefaultGateRuntimeDeps() {
19420
20329
  };
19421
20330
  },
19422
20331
  async writeApprovals(filePath, state) {
19423
- await mkdir14(path52.dirname(filePath), { recursive: true });
20332
+ await mkdir14(path54.dirname(filePath), { recursive: true });
19424
20333
  await writeFile11(filePath, `${JSON.stringify(compactApprovals(state), null, 2)}
19425
20334
  `, "utf8");
19426
20335
  },
@@ -19582,7 +20491,7 @@ function deriveWorkspaceRootScopeHint(params) {
19582
20491
  if (!targetPath) {
19583
20492
  return void 0;
19584
20493
  }
19585
- const candidateRoot = canonicalPath(path52.dirname(targetPath));
20494
+ const candidateRoot = canonicalPath(path54.dirname(targetPath));
19586
20495
  const validation = validateTrustedWorkspaceRootCandidate({
19587
20496
  candidatePath: candidateRoot,
19588
20497
  repoRoot: action.repoRoot,
@@ -19916,6 +20825,7 @@ async function evaluateGatedAction(ctx, deps, params) {
19916
20825
  event: resolveGateAuditEvent(sourceEvent, params.kind),
19917
20826
  sourceEvent,
19918
20827
  kind: params.kind,
20828
+ ...typeof params.payload?.tool_use_id === "string" ? { toolInvocationCorrelationId: toolInvocationCorrelationId(params.payload.tool_use_id) } : {},
19919
20829
  fingerprint: verdict2.fingerprint,
19920
20830
  verdict: verdict2.verdict,
19921
20831
  reason: verdict2.reason,
@@ -20073,6 +20983,7 @@ async function evaluateGatedAction(ctx, deps, params) {
20073
20983
  const scrubbedPayload = fingerprintReplayPayload(params.kind, params.payload, scrubOpts);
20074
20984
  return gateDecisionToVerdict(ctx, deps, params.kind, result, {
20075
20985
  sourceEvent: params.sourceEvent,
20986
+ toolInvocationCorrelationId: typeof params.payload?.tool_use_id === "string" ? toolInvocationCorrelationId(params.payload.tool_use_id) : void 0,
20076
20987
  predictedAssessment,
20077
20988
  observedAssessment: observedAssessment2,
20078
20989
  transactionalLayer,
@@ -20190,6 +21101,7 @@ async function gateDecisionToVerdict(ctx, deps, kind, result, auditExtras = {})
20190
21101
  event: auditEvent,
20191
21102
  sourceEvent,
20192
21103
  kind,
21104
+ ...auditExtras.toolInvocationCorrelationId ? { toolInvocationCorrelationId: auditExtras.toolInvocationCorrelationId } : {},
20193
21105
  fingerprint: result.fingerprint,
20194
21106
  summary: result.normalizedCommand ?? result.summary ?? "",
20195
21107
  assessment: result.assessment,
@@ -20640,29 +21552,56 @@ function gateVerdictToCodexUserPromptResponse(verdict2) {
20640
21552
  };
20641
21553
  }
20642
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);
20643
21557
  await deps.appendAudit(ctx, {
20644
21558
  event: eventName,
20645
21559
  kind: "audit",
20646
21560
  verdict: "allow",
20647
21561
  reason: "observed",
20648
- 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)
20649
21569
  });
20650
21570
  }
20651
21571
 
20652
21572
  // src/adapters/shared/repo-root.ts
20653
21573
  import { existsSync as existsSync17 } from "node:fs";
20654
- 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
+ }
20655
21594
  function findRepoRoot(startPath, layout) {
20656
- let current = path53.resolve(startPath);
21595
+ let current = path55.resolve(startPath);
20657
21596
  while (true) {
20658
21597
  for (const marker of layout.repoRootMarkers) {
20659
- if (existsSync17(path53.join(current, marker))) {
21598
+ if (markerMatches(current, marker, layout)) {
20660
21599
  return current;
20661
21600
  }
20662
21601
  }
20663
- const parent = path53.dirname(current);
21602
+ const parent = path55.dirname(current);
20664
21603
  if (parent === current) {
20665
- return path53.resolve(startPath);
21604
+ return path55.resolve(startPath);
20666
21605
  }
20667
21606
  current = parent;
20668
21607
  }
@@ -20688,6 +21627,16 @@ function jsonResponse(value) {
20688
21627
  process2.stdout.write(`${JSON.stringify(value)}
20689
21628
  `);
20690
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
+ }
20691
21640
  async function loadRuntimeContext(cwd) {
20692
21641
  const repoRoot = findRepoRoot(cwd, codexLayout);
20693
21642
  const configPath = codexLayout.configPath(repoRoot);
@@ -20780,7 +21729,8 @@ async function runBeforeSubmitPromptHook() {
20780
21729
  try {
20781
21730
  const payload = await readStdinJson();
20782
21731
  const prompt = String(payload.prompt ?? payload.user_message ?? "");
20783
- const ctx = await loadRuntimeContext(process2.cwd());
21732
+ const cwd = resolveCodexActionCwd(payload);
21733
+ const ctx = await loadRuntimeContext(cwd);
20784
21734
  const deps = createDefaultGateRuntimeDeps();
20785
21735
  const result = await processApprovalPrompt(ctx, deps, prompt);
20786
21736
  jsonResponse(gateVerdictToCodexUserPromptResponse(result));
@@ -20794,9 +21744,10 @@ async function runBeforeSubmitPromptHook() {
20794
21744
  async function runToolGateHook(eventName) {
20795
21745
  try {
20796
21746
  const payload = await readStdinJson();
20797
- const cwd = process2.cwd();
20798
21747
  const toolName = String(payload.tool_name ?? payload.toolName ?? "");
20799
21748
  const kind = resolveCodexGateKind(eventName, toolName);
21749
+ const includeToolInputCwd = (eventName === "PreToolUse" || eventName === "preToolUse") && kind === "shell";
21750
+ const cwd = resolveCodexActionCwd(payload, process2.cwd(), { includeToolInputCwd });
20800
21751
  const ctx = await loadRuntimeContext(cwd);
20801
21752
  const deps = createDefaultGateRuntimeDeps();
20802
21753
  if (!kind) {
@@ -20834,7 +21785,7 @@ async function runShellGateHook() {
20834
21785
  try {
20835
21786
  const payload = await readStdinJson();
20836
21787
  const command = extractString(payload.tool_input, "command") || String(payload.command ?? "");
20837
- const cwd = process2.cwd();
21788
+ const cwd = resolveCodexActionCwd(payload, process2.cwd(), { includeToolInputCwd: true });
20838
21789
  const ctx = await loadRuntimeContext(cwd);
20839
21790
  const deps = createDefaultGateRuntimeDeps();
20840
21791
  const verdict2 = await evaluateGatedAction(ctx, deps, {
@@ -20859,7 +21810,11 @@ async function runShellGateHook() {
20859
21810
  async function runAuditHook(eventName) {
20860
21811
  try {
20861
21812
  const payload = await readStdinJson();
20862
- 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);
20863
21818
  const deps = createDefaultGateRuntimeDeps();
20864
21819
  await appendObservedAudit(ctx, deps, eventName, payload);
20865
21820
  jsonResponse({});
@@ -20872,6 +21827,7 @@ async function runAuditHook(eventName) {
20872
21827
  }
20873
21828
  }
20874
21829
  export {
21830
+ resolveCodexActionCwd,
20875
21831
  runAuditHook,
20876
21832
  runBeforeSubmitPromptHook,
20877
21833
  runShellGateHook,