@guilz-dev/belay 0.9.3 → 0.9.4

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 (83) hide show
  1. package/README.md +35 -1
  2. package/dist/adapters/codex/hooks.d.ts +1 -0
  3. package/dist/adapters/codex/hooks.js +3 -0
  4. package/dist/adapters/cursor/dispatcher-generation.d.ts +3 -0
  5. package/dist/adapters/cursor/dispatcher-generation.js +5 -0
  6. package/dist/adapters/cursor/hook-dispatch-entry.d.ts +7 -0
  7. package/dist/adapters/cursor/hook-dispatch-entry.js +126 -0
  8. package/dist/adapters/cursor/hook-router.d.ts +23 -0
  9. package/dist/adapters/cursor/hook-router.js +266 -0
  10. package/dist/adapters/cursor/hooks.d.ts +4 -0
  11. package/dist/adapters/cursor/hooks.js +99 -8
  12. package/dist/adapters/cursor/routing-layout.d.ts +5 -0
  13. package/dist/adapters/cursor/routing-layout.js +29 -0
  14. package/dist/adapters/cursor/runtime-entry.d.ts +17 -0
  15. package/dist/adapters/cursor/runtime-entry.js +39 -39
  16. package/dist/adapters/layouts/scope.d.ts +5 -0
  17. package/dist/adapters/layouts/scope.js +71 -0
  18. package/dist/adapters/shared/gate-runtime.js +1 -0
  19. package/dist/bundle/claude-runtime.mjs +1932 -1740
  20. package/dist/bundle/codex-runtime.mjs +1934 -1742
  21. package/dist/bundle/cursor-dispatcher.mjs +443 -0
  22. package/dist/bundle/cursor-runtime.mjs +2017 -1821
  23. package/dist/commands/doctor.js +191 -15
  24. package/dist/commands/health-snapshot.js +9 -4
  25. package/dist/commands/recovery-checkpoints.d.ts +3 -3
  26. package/dist/config-io.js +11 -2
  27. package/dist/core/audit-legacy-archive.js +43 -25
  28. package/dist/core/classify-tool.js +16 -8
  29. package/dist/core/effect-ir/argv-delegate.d.ts +9 -0
  30. package/dist/core/effect-ir/argv-delegate.js +42 -0
  31. package/dist/core/effect-ir/shell-lower/argv-delegate-gate.d.ts +3 -0
  32. package/dist/core/effect-ir/shell-lower/argv-delegate-gate.js +40 -0
  33. package/dist/core/effect-ir/shell-lower/augment.d.ts +4 -0
  34. package/dist/core/effect-ir/shell-lower/augment.js +97 -0
  35. package/dist/core/effect-ir/shell-lower/context.d.ts +10 -0
  36. package/dist/core/effect-ir/shell-lower/context.js +1 -0
  37. package/dist/core/effect-ir/shell-lower/decode-process.d.ts +9 -0
  38. package/dist/core/effect-ir/shell-lower/decode-process.js +201 -0
  39. package/dist/core/effect-ir/shell-lower/decoders/belay.d.ts +2 -0
  40. package/dist/core/effect-ir/shell-lower/decoders/belay.js +29 -0
  41. package/dist/core/effect-ir/shell-lower/decoders/builtins.d.ts +7 -0
  42. package/dist/core/effect-ir/shell-lower/decoders/builtins.js +194 -0
  43. package/dist/core/effect-ir/shell-lower/decoders/decode-package-exec.d.ts +4 -0
  44. package/dist/core/effect-ir/shell-lower/decoders/decode-package-exec.js +68 -0
  45. package/dist/core/effect-ir/shell-lower/decoders/docker.d.ts +3 -0
  46. package/dist/core/effect-ir/shell-lower/decoders/docker.js +43 -0
  47. package/dist/core/effect-ir/shell-lower/decoders/filesystem.d.ts +9 -0
  48. package/dist/core/effect-ir/shell-lower/decoders/filesystem.js +217 -0
  49. package/dist/core/effect-ir/shell-lower/decoders/prisma.d.ts +2 -0
  50. package/dist/core/effect-ir/shell-lower/decoders/prisma.js +54 -0
  51. package/dist/core/effect-ir/shell-lower/decoders/ruby.d.ts +11 -0
  52. package/dist/core/effect-ir/shell-lower/decoders/ruby.js +147 -0
  53. package/dist/core/effect-ir/shell-lower/decoders/toolchain.d.ts +7 -0
  54. package/dist/core/effect-ir/shell-lower/decoders/toolchain.js +155 -0
  55. package/dist/core/effect-ir/shell-lower/requirement.d.ts +12 -0
  56. package/dist/core/effect-ir/shell-lower/requirement.js +65 -0
  57. package/dist/core/effect-ir/shell-lower/segment.d.ts +11 -0
  58. package/dist/core/effect-ir/shell-lower/segment.js +82 -0
  59. package/dist/core/effect-ir/shell-lower/tokens.d.ts +26 -0
  60. package/dist/core/effect-ir/shell-lower/tokens.js +183 -0
  61. package/dist/core/effect-ir/shell-lower.d.ts +2 -7
  62. package/dist/core/effect-ir/shell-lower.js +60 -1502
  63. package/dist/core/glob.js +32 -16
  64. package/dist/core/integrity.d.ts +2 -2
  65. package/dist/core/integrity.js +52 -12
  66. package/dist/core/replay-scrub.d.ts +2 -0
  67. package/dist/core/replay-scrub.js +8 -0
  68. package/dist/core/shell-substitution.js +2 -2
  69. package/dist/core/shell-unparseable.js +3 -1
  70. package/dist/defaults.d.ts +7 -3
  71. package/dist/defaults.js +29 -22
  72. package/dist/installer/bootstrap.d.ts +1 -0
  73. package/dist/installer/bootstrap.js +2 -2
  74. package/dist/installer/runtime-artifacts.js +43 -13
  75. package/dist/installer.js +44 -9
  76. package/dist/node-resolution.d.ts +1 -0
  77. package/dist/node-resolution.js +61 -0
  78. package/dist/templates.d.ts +7 -4
  79. package/dist/templates.js +42 -4
  80. package/dist/types.d.ts +1 -0
  81. package/dist/version.d.ts +1 -1
  82. package/dist/version.js +1 -1
  83. package/package.json +1 -1
@@ -3574,7 +3574,7 @@ var init_config_io = __esm({
3574
3574
 
3575
3575
  // src/adapters/codex/runtime-entry.ts
3576
3576
  init_codex();
3577
- import path56 from "node:path";
3577
+ import path63 from "node:path";
3578
3578
  import process2 from "node:process";
3579
3579
 
3580
3580
  // src/adapters/shared/gate-runtime.ts
@@ -3583,7 +3583,7 @@ init_approval_replay();
3583
3583
  import { randomUUID as randomUUID6 } from "node:crypto";
3584
3584
  import { existsSync as existsSync16 } from "node:fs";
3585
3585
  import { mkdir as mkdir14, readFile as readFile15, writeFile as writeFile11 } from "node:fs/promises";
3586
- import path54 from "node:path";
3586
+ import path61 from "node:path";
3587
3587
 
3588
3588
  // src/core/approval-service.ts
3589
3589
  init_config_io();
@@ -6590,31 +6590,44 @@ import { randomUUID as randomUUID3 } from "node:crypto";
6590
6590
  import path21 from "node:path";
6591
6591
 
6592
6592
  // src/core/glob.ts
6593
+ init_approval();
6594
+ function globPatternToRegexSource(pattern) {
6595
+ let result = "";
6596
+ let index = 0;
6597
+ while (index < pattern.length) {
6598
+ const char = pattern[index];
6599
+ if (char === "*" && pattern[index + 1] === "*") {
6600
+ index += 2;
6601
+ if (pattern[index] === "/") {
6602
+ result += "(?:.*/)?";
6603
+ index += 1;
6604
+ } else {
6605
+ result += ".*";
6606
+ }
6607
+ continue;
6608
+ }
6609
+ if (char === "*") {
6610
+ result += "[^/]*";
6611
+ index += 1;
6612
+ continue;
6613
+ }
6614
+ result += escapeRegex(char);
6615
+ index += 1;
6616
+ }
6617
+ return result;
6618
+ }
6619
+ function globPatternMatches(pattern, normalized, baseName) {
6620
+ const regex = new RegExp(`^${globPatternToRegexSource(pattern)}$`);
6621
+ return regex.test(normalized) || regex.test(baseName);
6622
+ }
6593
6623
  function matchesSensitivePath(filePath, patterns) {
6594
6624
  const normalized = filePath.replaceAll("\\", "/");
6595
6625
  const segments = normalized.split("/");
6596
6626
  const baseName = segments.at(-1) ?? normalized;
6597
6627
  for (const pattern of patterns) {
6598
6628
  const normalizedPattern = pattern.replaceAll("\\", "/");
6599
- if (normalizedPattern.includes("**")) {
6600
- const parts = normalizedPattern.split("**").map((part) => part.replace(/^\/+|\/+$/g, ""));
6601
- const prefix = parts[0]?.replace(/\/+$/, "") ?? "";
6602
- const suffix = parts[1]?.replace(/^\/+/, "") ?? "";
6603
- if (prefix && !normalized.startsWith(prefix)) {
6604
- continue;
6605
- }
6606
- if (suffix && !normalized.includes(suffix)) {
6607
- continue;
6608
- }
6609
- if (prefix || suffix) {
6610
- return true;
6611
- }
6612
- }
6613
6629
  if (normalizedPattern.includes("*")) {
6614
- const regex = new RegExp(
6615
- `^${normalizedPattern.replaceAll(".", "\\.").replaceAll("*", ".*")}$`
6616
- );
6617
- if (regex.test(normalized) || regex.test(baseName)) {
6630
+ if (globPatternMatches(normalizedPattern, normalized, baseName)) {
6618
6631
  return true;
6619
6632
  }
6620
6633
  continue;
@@ -11016,6 +11029,16 @@ function subagentFingerprintSource(payload, scrubOptions) {
11016
11029
  }
11017
11030
  return scrubValue(payload, scrubOptions);
11018
11031
  }
11032
+ function fingerprintToolInputSource(payload) {
11033
+ const toolInput = payload.tool_input;
11034
+ if (!toolInput || typeof toolInput !== "object") {
11035
+ return {};
11036
+ }
11037
+ return redactToolInvocationId(
11038
+ toolInput,
11039
+ typeof payload.tool_use_id === "string" ? payload.tool_use_id : void 0
11040
+ );
11041
+ }
11019
11042
  function fingerprintReplayPayload(kind, payload, scrubOptions) {
11020
11043
  if (!payload) {
11021
11044
  return void 0;
@@ -11125,7 +11148,7 @@ function classifySubagent(payload, repoRoot, options = {}, config) {
11125
11148
  }
11126
11149
 
11127
11150
  // src/core/classify-tool.ts
11128
- import path42 from "node:path";
11151
+ import path49 from "node:path";
11129
11152
  init_fingerprint2();
11130
11153
  init_path_utils();
11131
11154
  init_scrub();
@@ -11244,11 +11267,8 @@ function worstEffectDecision(decisions) {
11244
11267
  }
11245
11268
 
11246
11269
  // src/core/effect-ir/shell-lower.ts
11247
- init_git_resource_identity();
11248
- init_path_utils();
11249
11270
  init_shell_tokenizer();
11250
- import { lstatSync as lstatSync2, realpathSync as realpathSync4 } from "node:fs";
11251
- import path41 from "node:path";
11271
+ import path48 from "node:path";
11252
11272
 
11253
11273
  // src/core/verdict/docker-compose-run.ts
11254
11274
  import path36 from "node:path";
@@ -11588,6 +11608,17 @@ function decodeDockerComposeRun(tokens) {
11588
11608
 
11589
11609
  // src/core/verdict/egress-classify.ts
11590
11610
  import path37 from "node:path";
11611
+ var EGRESS_TOOL_HEADS = /* @__PURE__ */ new Set([
11612
+ "aws",
11613
+ "curl",
11614
+ "gh",
11615
+ "gcloud",
11616
+ "heroku",
11617
+ "kubectl",
11618
+ "netlify",
11619
+ "vercel",
11620
+ "wget"
11621
+ ]);
11591
11622
  var CURL_EFFECT_NEUTRAL_FLAGS = /* @__PURE__ */ new Set([
11592
11623
  "-f",
11593
11624
  "-L",
@@ -11621,6 +11652,9 @@ var GH_READ_COMMANDS = /* @__PURE__ */ new Set([
11621
11652
  "workflow list",
11622
11653
  "workflow view"
11623
11654
  ]);
11655
+ function isEgressToolHead(head) {
11656
+ return EGRESS_TOOL_HEADS.has(head);
11657
+ }
11624
11658
  function decodeEgressEffects(params) {
11625
11659
  const head = path37.basename(params.tokens[0] ?? "");
11626
11660
  if (head !== "curl" && head !== "wget" && head !== "gh") {
@@ -13883,7 +13917,7 @@ function extractBalancedParenContent(command, start) {
13883
13917
  index += 1;
13884
13918
  continue;
13885
13919
  }
13886
- if (char === "\\" && (inSingle || inDouble)) {
13920
+ if (char === "\\" && inDouble) {
13887
13921
  escaping = true;
13888
13922
  index += 1;
13889
13923
  continue;
@@ -13961,7 +13995,9 @@ function hasUnclosedQuote(command) {
13961
13995
  continue;
13962
13996
  }
13963
13997
  if (char === "\\") {
13964
- escaping = true;
13998
+ if (quote !== "'") {
13999
+ escaping = true;
14000
+ }
13965
14001
  continue;
13966
14002
  }
13967
14003
  if (quote) {
@@ -14478,6 +14514,50 @@ function redactCommand(command) {
14478
14514
  return command.replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [REDACTED]").replace(/sk-[A-Za-z0-9]{8,}/g, "sk-[REDACTED]").trim();
14479
14515
  }
14480
14516
 
14517
+ // src/core/effect-ir/argv-delegate.ts
14518
+ function innerRecipeFromArgvDelegate(peel) {
14519
+ if (peel.opaque || peel.innerTokens.length === 0) {
14520
+ return null;
14521
+ }
14522
+ return peel.innerTokens.join(" ");
14523
+ }
14524
+ function peelArgvDelegateArgv(tokens) {
14525
+ const launcher = tokens[0] ?? "";
14526
+ if (!launcher || tokens.length < 2) {
14527
+ return null;
14528
+ }
14529
+ const signals = ["process.argv_delegate"];
14530
+ let index = 1;
14531
+ while (index < tokens.length) {
14532
+ const token = tokens[index] ?? "";
14533
+ if (token === "--") {
14534
+ index += 1;
14535
+ break;
14536
+ }
14537
+ if (token.startsWith("-")) {
14538
+ return {
14539
+ launcher,
14540
+ innerTokens: [],
14541
+ opaque: true,
14542
+ reason: "argv_delegate_wrapper_options",
14543
+ signals: [...signals, "argv_delegate_wrapper_options"]
14544
+ };
14545
+ }
14546
+ break;
14547
+ }
14548
+ const innerTokens = tokens.slice(index);
14549
+ if (innerTokens.length === 0) {
14550
+ return null;
14551
+ }
14552
+ return {
14553
+ launcher,
14554
+ innerTokens,
14555
+ opaque: false,
14556
+ reason: "argv_delegate_peel",
14557
+ signals
14558
+ };
14559
+ }
14560
+
14481
14561
  // src/core/effect-ir/shell-build.ts
14482
14562
  function buildShellEffectPlan(params) {
14483
14563
  const nodes = params.segments.map(buildSegmentNode);
@@ -14535,933 +14615,416 @@ function mergeNodes(nodes) {
14535
14615
  return { kind: "merge", children: nodes };
14536
14616
  }
14537
14617
 
14538
- // src/core/effect-ir/shell-lower.ts
14539
- var MAX_LOWER_DEPTH = 8;
14540
- var ENV_PREFIX_PATTERN2 = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
14541
- var LOOPBACK_HOSTS2 = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1", "0:0:0:0:0:0:0:1"]);
14618
+ // src/core/effect-ir/shell-lower/argv-delegate-gate.ts
14619
+ import path41 from "node:path";
14620
+ var ARGV_DELEGATE_INNER_BLOCKLIST = /* @__PURE__ */ new Set([
14621
+ "sudo",
14622
+ "env",
14623
+ "command",
14624
+ "builtin",
14625
+ "exec",
14626
+ "time",
14627
+ "nice",
14628
+ "nohup",
14629
+ "stdbuf",
14630
+ "setsid",
14631
+ "("
14632
+ ]);
14633
+ function shouldApplyArgvDelegate(head, innerTokens, depth) {
14634
+ if (depth > 0 || isEgressToolHead(head) || head === "bundle") {
14635
+ return false;
14636
+ }
14637
+ if (ARGV_DELEGATE_INNER_BLOCKLIST.has(head)) {
14638
+ return false;
14639
+ }
14640
+ const innerHead = path41.basename(innerTokens[0] ?? "");
14641
+ if (ARGV_DELEGATE_INNER_BLOCKLIST.has(innerHead)) {
14642
+ return false;
14643
+ }
14644
+ return innerTokens.length >= 2;
14645
+ }
14646
+ function isGrammarUnknownOnly(requirements, head) {
14647
+ if (requirements.length !== 2) {
14648
+ return false;
14649
+ }
14650
+ const [spawn9, indeterminate] = requirements;
14651
+ return spawn9.action === "process.exec" && spawn9.resource.kind === "executable" && spawn9.resource.command === head && spawn9.resource.operation === "spawn" && indeterminate.action === "indeterminate" && indeterminate.evidence.signals.includes("process.grammar_unknown");
14652
+ }
14653
+
14654
+ // src/core/effect-ir/shell-lower/augment.ts
14655
+ init_shell_tokenizer();
14656
+
14657
+ // src/core/effect-ir/shell-lower/requirement.ts
14542
14658
  var SECRET_PATH_PATTERN = /(?:^|[/\\])(?:\.env(?:\..*)?|credentials?|secrets?|id_rsa)(?:$|[/\\])/i;
14543
14659
  var CONTROL_PLANE_PATH_PATTERN = /^(?:\/etc(?:\/|$)|\/var\/run(?:\/|$)|.*(?:^|[/\\])\.(?:git|ssh|cursor|claude)(?:[/\\]|$))/;
14544
- function lowerShellEffectPlan(params) {
14545
- const context = { ...params, depth: 0 };
14546
- const segments = lowerTopLevelSegments(params.command, context);
14547
- return buildShellEffectPlan({
14548
- inputFingerprint: params.inputFingerprint,
14549
- segments,
14550
- signals: pipeToShell(params.command) ? ["pipe_to_shell"] : []
14551
- });
14660
+ function requirement2(tag, action, resource, segment, signals) {
14661
+ return {
14662
+ tag,
14663
+ action,
14664
+ resource,
14665
+ evidence: {
14666
+ level: tag === "indeterminate" ? "indeterminate" : "certain",
14667
+ signals: [...new Set(signals)].sort(),
14668
+ basis: ["shell_semantic_lowering"]
14669
+ },
14670
+ provenance: { segment }
14671
+ };
14552
14672
  }
14553
- function lowerTopLevelSegments(command, context) {
14554
- const commands = splitStructuralShellSegments(command);
14555
- if (commands.length === 0 && command.trim()) {
14556
- commands.push(command.trim());
14557
- }
14558
- const pipedToShell = pipeToShell(command);
14559
- const lowered = [];
14560
- let cwd = context.cwd;
14561
- let cwdKnown = true;
14562
- let inferredEnv = { ...context.env };
14563
- for (const segment of commands) {
14564
- let result = lowerSegment(segment, {
14565
- ...context,
14566
- cwd,
14567
- env: inferredEnv,
14568
- command: segment,
14569
- depth: context.depth,
14570
- inputFingerprint: context.inputFingerprint,
14571
- ...pipedToShell && isShellHead(parseSegment(segment).head) ? { pipeToShellSegment: true } : {}
14572
- });
14573
- if (!cwdKnown) {
14574
- const requiresCwd = result.requirements.some(requiresKnownCwd);
14575
- const requirements = [...result.requirements];
14576
- if (requiresCwd) {
14577
- requirements.push(
14578
- requirement2(
14579
- "indeterminate",
14580
- "indeterminate",
14581
- { kind: "unknown" },
14582
- result.commandRedacted,
14583
- ["shell.cwd_unknown"]
14584
- )
14585
- );
14586
- }
14587
- result = {
14588
- ...result,
14589
- requirements,
14590
- ...requiresCwd ? {
14591
- completeness: "partial",
14592
- opacity: joinEffectOpacity(result.opacity, "opaque")
14593
- } : {},
14594
- signals: [.../* @__PURE__ */ new Set([...result.signals, "shell.cwd_unknown"])]
14595
- };
14596
- }
14597
- lowered.push(result);
14598
- if (!inferredEnv.DATABASE_URL && startsLocalPostgresService(segment)) {
14599
- inferredEnv = {
14600
- ...inferredEnv,
14601
- DATABASE_URL: "postgresql://127.0.0.1:5432/local"
14602
- };
14603
- }
14604
- const nextCwd = resolveCdTransition(segment, cwd);
14605
- if (nextCwd) {
14606
- cwd = nextCwd.cwd;
14607
- cwdKnown = nextCwd.known;
14608
- }
14609
- }
14610
- if (commands.length > 1 && /[\r\n]/.test(command) && lowered.some(
14611
- (segment) => segment.requirements.some((entry) => entry.action === "network.connect")
14612
- )) {
14613
- lowered.push(
14614
- shellSegment(
14615
- "[multiline network boundary]",
14616
- "shell-boundary",
14617
- [
14618
- requirement2(
14619
- "indeterminate",
14620
- "indeterminate",
14621
- { kind: "unknown" },
14622
- "[multiline network boundary]",
14623
- ["shell.multiline_network_boundary"]
14624
- )
14625
- ],
14626
- "opaque",
14627
- /* @__PURE__ */ new Set(["shell.multiline_network_boundary"])
14673
+ function processRequirement(command, operation, segment, signals) {
14674
+ return requirement2(
14675
+ "process.exec",
14676
+ "process.exec",
14677
+ { kind: "executable", command, operation },
14678
+ segment,
14679
+ signals
14680
+ );
14681
+ }
14682
+ function unsupportedProcess(command, segment, signal) {
14683
+ return [
14684
+ processRequirement(command, "spawn", segment, [signal]),
14685
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [signal])
14686
+ ];
14687
+ }
14688
+ function addWriteEffects(requirements, resolved, segment, signals) {
14689
+ requirements.push(
14690
+ requirement2("fs.write", "fs.write", { kind: "path", path: resolved }, segment, signals)
14691
+ );
14692
+ if (CONTROL_PLANE_PATH_PATTERN.test(resolved)) {
14693
+ requirements.push(
14694
+ requirement2(
14695
+ "control_plane.write",
14696
+ "control_plane.write",
14697
+ { kind: "path", path: resolved },
14698
+ segment,
14699
+ [...signals, "protected_path"]
14628
14700
  )
14629
14701
  );
14630
14702
  }
14631
- return lowered;
14632
- }
14633
- function startsLocalPostgresService(command) {
14634
- const tokens = tokenizeShell(command);
14635
- return path41.basename(tokens[0] ?? "") === "docker" && tokens[1] === "compose" && ["up", "start", "restart"].includes(tokens[2] ?? "") && tokens.includes("postgres");
14636
14703
  }
14637
- function resolveCdTransition(command, currentCwd) {
14638
- const tokens = tokenizeShell(command);
14639
- if (path41.basename(tokens[0] ?? "") !== "cd") {
14640
- return null;
14641
- }
14642
- const target = tokens[1] ?? "~";
14643
- if (!target || target === "-" || target.includes("$") || target.includes("`")) {
14644
- return { cwd: currentCwd, known: false };
14704
+ function addSecretRead(requirements, resolved, segment) {
14705
+ if (SECRET_PATH_PATTERN.test(resolved)) {
14706
+ requirements.push(
14707
+ requirement2("secret.read", "secret.read", { kind: "path", path: resolved }, segment, [
14708
+ "secret_path_read"
14709
+ ])
14710
+ );
14645
14711
  }
14646
- return { cwd: resolvePathOperand(target, currentCwd), known: true };
14647
14712
  }
14648
- function requiresKnownCwd(requirementValue) {
14649
- if (requirementValue.action === "fs.write" || requirementValue.action === "git.ref.write" || requirementValue.action === "control_plane.write") {
14650
- return true;
14713
+ function ensureRequirement(requirements, candidate) {
14714
+ if (!requirements.some(
14715
+ (entry) => entry.action === candidate.action && JSON.stringify(entry.resource) === JSON.stringify(candidate.resource)
14716
+ )) {
14717
+ requirements.push(candidate);
14651
14718
  }
14652
- return requirementValue.action === "process.exec" && requirementValue.resource.kind === "executable" && requirementValue.resource.operation === "spawn";
14653
- }
14654
- function joinNestedOpacity(outer, nested) {
14655
- const nestedOpacity = nested.completeness === "partial" && nested.opacity === "transparent" ? "recursive" : nested.opacity;
14656
- return joinEffectOpacity(outer, nestedOpacity);
14657
14719
  }
14658
- function lowerSegment(command, context) {
14659
- const commandRedacted = redactCommand(command);
14660
- const lexed = lexShell(command);
14661
- const rawTokens = lexed.tokens.map((token) => token.value);
14662
- const environment = extractEnvironment(rawTokens, context.env);
14663
- const env = environment.env;
14664
- const parsed = parseSegment(command);
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)
14672
- );
14673
- const head = path41.basename(tokens[0] ?? parsed.head);
14674
- let opacity = segmentOpacity(command);
14675
- const signals = /* @__PURE__ */ new Set();
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
- }
14686
- addRedirectEffects(requirements, rawTokens, env, context, commandRedacted);
14687
- addSubstitutionEffects(requirements, command, context, commandRedacted, signals);
14688
- if (environment.malformed) {
14689
- requirements.push(
14690
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
14691
- "shell.env_wrapper_incomplete"
14692
- ])
14693
- );
14694
- opacity = joinEffectOpacity(opacity, "opaque");
14695
- }
14696
- if (parsed.encounteredXargs) {
14697
- requirements.push(
14698
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
14699
- "shell.xargs_stdin_dynamic"
14700
- ])
14701
- );
14702
- signals.add("shell.xargs_stdin_dynamic");
14703
- opacity = joinEffectOpacity(opacity, "opaque");
14704
- }
14705
- if (context.depth > MAX_LOWER_DEPTH) {
14706
- requirements.push(
14707
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
14708
- "shell.lower_depth_exceeded"
14709
- ])
14710
- );
14711
- return shellSegment(
14712
- commandRedacted,
14713
- head,
14714
- requirements,
14715
- joinEffectOpacity(opacity, "opaque"),
14716
- signals
14717
- );
14718
- }
14719
- const packageExec = peelPackageExecArgv(tokens);
14720
- if (packageExec) {
14721
- requirements.push(
14722
- ...decodePackageExec(packageExec, context.cwd, context.repoRoot, commandRedacted)
14723
- );
14724
- for (const signal of packageExec.signals) {
14725
- signals.add(signal);
14726
- }
14727
- if (packageExec.opaque) {
14728
- opacity = joinEffectOpacity(opacity, "opaque");
14729
- }
14730
- const innerRecipe = packageExecInnerIsMetadata(packageExec) ? null : innerRecipeFromPeel(packageExec);
14731
- if (innerRecipe) {
14732
- const nested = lowerTopLevelSegments(innerRecipe, {
14733
- ...context,
14734
- command: innerRecipe,
14735
- env,
14736
- depth: context.depth + 1
14737
- });
14738
- for (const nestedSegment of nested) {
14739
- requirements.push(
14740
- ...nestedSegment.requirements.map(
14741
- (entry) => withInnerProvenance(entry, innerRecipe, packageExec.launcher, commandRedacted)
14742
- )
14743
- );
14744
- for (const signal of nestedSegment.signals) {
14745
- signals.add(signal);
14746
- }
14747
- opacity = joinNestedOpacity(opacity, nestedSegment);
14748
- }
14749
- }
14750
- return shellSegment(commandRedacted, head, requirements, opacity, signals);
14751
- }
14752
- const recursive = decodeRecursiveInvocationTokens(decoderTokens);
14753
- if (recursive.kind === "static" && opacity !== "opaque" && opacity !== "unparseable") {
14754
- requirements.push(
14755
- processRequirement(recursive.interpreter, "spawn", commandRedacted, [
14756
- "shell.recursive_wrapper",
14757
- "dynamic_shell_evaluation"
14758
- ])
14759
- );
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);
14774
- }
14775
- }
14776
- signals.add("shell.recursive_wrapper");
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
- }
14821
- }
14822
- signals.add("process.docker_compose_run");
14823
- return shellSegment(commandRedacted, head, requirements, "recursive", signals);
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
- }
14842
- const launcher = resolveLauncherRecipe({
14843
- tokens,
14844
- cwd: context.cwd,
14845
- repoRoot: context.repoRoot,
14846
- depth: context.depth
14847
- });
14848
- if (launcher) {
14849
- signals.add(`launcher.${launcher.reason}`);
14850
- requirements.push(
14851
- requirement2(
14852
- "process.exec",
14853
- "process.exec",
14854
- { kind: "executable", command: head, operation: "spawn" },
14855
- commandRedacted,
14856
- ["launcher.invoke"]
14857
- )
14858
- );
14859
- for (const recipe of launcher.recipes) {
14860
- const nested = lowerTopLevelSegments(recipe, {
14861
- ...context,
14862
- command: recipe,
14863
- env,
14864
- depth: context.depth + 1
14865
- });
14866
- for (const nestedSegment of nested) {
14867
- requirements.push(
14868
- ...nestedSegment.requirements.map(
14869
- (entry) => withInnerProvenance(entry, recipe, head, commandRedacted)
14870
- )
14871
- );
14872
- for (const signal of nestedSegment.signals) {
14873
- signals.add(signal);
14874
- }
14875
- opacity = joinNestedOpacity(opacity, nestedSegment);
14876
- }
14877
- }
14878
- if (launcher.opaque || launcher.recipes.length === 0) {
14879
- requirements.push(
14880
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
14881
- `launcher.${launcher.reason}`
14882
- ])
14883
- );
14884
- opacity = joinEffectOpacity(opacity, "opaque");
14885
- }
14886
- return shellSegment(commandRedacted, head, requirements, opacity, signals);
14887
- }
14888
- const egress = decodeEgressEffects({
14889
- tokens,
14890
- cwd: context.cwd,
14891
- segment: commandRedacted
14892
- });
14893
- if (egress) {
14894
- requirements.push(...egress);
14895
- for (const signal of egress.flatMap((entry) => entry.evidence.signals)) {
14896
- signals.add(signal);
14897
- }
14898
- } else {
14899
- const git = decodeGitEffects({
14900
- tokens,
14901
- cwd: context.cwd,
14902
- repoRoot: context.repoRoot,
14903
- segment: commandRedacted
14904
- });
14905
- if (git) {
14906
- requirements.push(...git);
14907
- for (const signal of git.flatMap((entry) => entry.evidence.signals)) {
14908
- signals.add(signal);
14909
- }
14910
- } else {
14911
- requirements.push(
14912
- ...decodeProcessOrFilesystem({
14913
- tokens,
14914
- head,
14915
- env,
14916
- cwd: context.cwd,
14917
- repoRoot: context.repoRoot,
14918
- segment: commandRedacted
14919
- })
14920
- );
14921
- }
14922
- }
14923
- const environmentSignals = effectChangingEnvironmentSignals(head, env, environment.changedNames);
14924
- if (environmentSignals.length > 0) {
14925
- requirements.push(
14926
- requirement2(
14927
- "indeterminate",
14928
- "indeterminate",
14929
- { kind: "unknown" },
14930
- commandRedacted,
14931
- environmentSignals
14932
- )
14933
- );
14934
- opacity = joinEffectOpacity(opacity, "opaque");
14935
- for (const signal of environmentSignals) {
14936
- signals.add(signal);
14937
- }
14938
- }
14939
- if (context.pipeToShellSegment) {
14940
- signals.add("pipe_to_shell");
14941
- ensureRequirement(
14942
- requirements,
14943
- requirement2(
14944
- "process.exec",
14945
- "process.exec",
14946
- { kind: "executable", command: head || "sh", operation: "spawn" },
14947
- commandRedacted,
14948
- ["pipe_to_shell"]
14949
- )
14950
- );
14951
- ensureRequirement(
14952
- requirements,
14953
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
14954
- "pipe_to_shell"
14955
- ])
14956
- );
14957
- opacity = joinEffectOpacity(opacity, "opaque");
14958
- }
14959
- if (opacity === "unparseable" || opacity === "opaque") {
14960
- ensureRequirement(
14961
- requirements,
14962
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
14963
- `shell.${opacity}`
14964
- ])
14965
- );
14966
- }
14967
- return shellSegment(commandRedacted, head, requirements, opacity, signals);
14968
- }
14969
- function decodePackageExec(peel, cwd, repoRoot, segment) {
14970
- if (peel.opaque) {
14971
- return [
14972
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
14973
- peel.reason,
14974
- ...peel.signals
14975
- ])
14976
- ];
14977
- }
14978
- if (peel.reason === "npx_wrapper_readonly") {
14979
- return [];
14980
- }
14981
- const binHead = peel.innerTokens[0] ?? "";
14982
- const metadataOnly = packageExecInnerIsMetadata(peel);
14983
- const local = !peel.forceAcquire ? resolveLocalBin(binHead, cwd, repoRoot) : null;
14984
- if (local) {
14985
- return metadataOnly ? [
14986
- requirement2(
14987
- "process.exec",
14988
- "process.exec",
14989
- { kind: "executable", command: local.path, operation: "inspect" },
14990
- segment,
14991
- ["package_exec.delegate", "package_exec.local_bin_resolved"]
14992
- )
14993
- ] : [];
14994
- }
14995
- const networkResources = peel.acquisitionSpecs.map((spec) => classifyPackageAcquisitionSpec(spec)).filter((source) => source.kind !== "local").map(
14996
- (source) => source.kind === "registry" ? {
14997
- kind: "network",
14998
- host: "registry.npmjs.org",
14999
- protocol: "registry",
15000
- mode: "read",
15001
- payload: "none"
15002
- } : {
15003
- kind: "network",
15004
- host: source.host,
15005
- ...source.port ? { port: source.port } : {},
15006
- protocol: source.protocol,
15007
- mode: "read",
15008
- payload: "none"
15009
- }
14720
+ function withProvenances(requirementValue, provenance, contributing) {
14721
+ const provenances = [
14722
+ ...requirementValue.provenances ?? [requirementValue.provenance],
14723
+ contributing,
14724
+ provenance
14725
+ ].filter(
14726
+ (candidate, index, all) => all.findIndex((entry) => JSON.stringify(entry) === JSON.stringify(candidate)) === index
15010
14727
  );
15011
- if (networkResources.length === 0) {
15012
- networkResources.push({
15013
- kind: "network",
15014
- host: "registry.npmjs.org",
15015
- protocol: "registry",
15016
- mode: "read",
15017
- payload: "none"
15018
- });
15019
- }
15020
- return [
15021
- ...networkResources.map(
15022
- (resource) => requirement2("network.acquire", "network.connect", resource, segment, [
15023
- "package_acquire_possible"
15024
- ])
15025
- ),
15026
- requirement2(
15027
- "fs.write",
15028
- "fs.write",
15029
- { kind: "package-cache", manager: peel.launcher === "pnpm" ? "pnpm" : "npm" },
15030
- segment,
15031
- ["package_cache_write"]
15032
- ),
15033
- ...metadataOnly ? [
15034
- requirement2(
15035
- "process.exec",
15036
- "process.exec",
15037
- { kind: "executable", command: binHead || peel.launcher, operation: "inspect" },
15038
- segment,
15039
- ["package_exec.delegate"]
15040
- )
15041
- ] : []
15042
- ];
14728
+ return { ...requirementValue, provenance, provenances };
15043
14729
  }
15044
- function packageExecInnerIsMetadata(peel) {
15045
- const args = peel.innerTokens.slice(1);
15046
- return args.length > 0 && args.every((arg) => ["--help", "--version", "-V", "-h", "-v"].includes(arg));
14730
+ function withInnerProvenance(requirementValue, innerCommand, launcher, outerSegment) {
14731
+ const provenance = {
14732
+ ...requirementValue.provenance,
14733
+ segment: outerSegment,
14734
+ innerCommand: requirementValue.provenance.innerCommand ?? innerCommand,
14735
+ ...launcher === "npm" || launcher === "pnpm" ? { launcher, phase: "delegate" } : {}
14736
+ };
14737
+ const launcherProvenance = {
14738
+ segment: outerSegment,
14739
+ innerCommand,
14740
+ ...launcher === "npm" || launcher === "pnpm" ? { launcher, phase: "delegate" } : {}
14741
+ };
14742
+ return withProvenances(requirementValue, provenance, launcherProvenance);
15047
14743
  }
14744
+
14745
+ // src/core/effect-ir/shell-lower/tokens.ts
14746
+ init_shell_tokenizer();
14747
+ import path42 from "node:path";
14748
+ var ENV_PREFIX_PATTERN2 = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
14749
+ var LOOPBACK_HOSTS2 = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1", "0:0:0:0:0:0:0:1"]);
15048
14750
  var METADATA_ONLY_FLAGS = /* @__PURE__ */ new Set(["--version", "-v", "-V", "--help", "-h"]);
15049
- function isMetadataOnlyArgv(argv) {
15050
- return argv.length > 0 && argv.every((token) => METADATA_ONLY_FLAGS.has(token));
15051
- }
15052
- function executableBaseName(head) {
15053
- return path41.basename(head);
15054
- }
15055
- var RAILS_READ_ONLY_SUBCOMMANDS = /* @__PURE__ */ new Set(["routes", "middleware", "stats", "about", "version"]);
15056
- function railsReadOnlySubcommand(args) {
15057
- const subcommand = args[0];
15058
- if (!subcommand || subcommand.startsWith("-")) {
15059
- return false;
15060
- }
15061
- return RAILS_READ_ONLY_SUBCOMMANDS.has(subcommand);
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;
14751
+ function extractEnvironment(tokens, inherited) {
14752
+ const env = { ...inherited };
14753
+ const changedNames = /* @__PURE__ */ new Set();
14754
+ if (tokens[0] === "env") {
14755
+ let index = 1;
14756
+ let malformed = false;
14757
+ while (index < tokens.length) {
14758
+ const option = tokens[index] ?? "";
14759
+ if (option === "-u" || option === "--unset") {
14760
+ const name = tokens[index + 1] ?? "";
14761
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
14762
+ malformed = true;
14763
+ index += 1;
14764
+ break;
14765
+ }
14766
+ delete env[name];
14767
+ changedNames.add(name);
14768
+ index += 2;
14769
+ continue;
15078
14770
  }
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;
14771
+ if (option.startsWith("--unset=")) {
14772
+ const name = option.slice("--unset=".length);
14773
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
14774
+ malformed = true;
14775
+ index += 1;
14776
+ break;
15091
14777
  }
14778
+ delete env[name];
14779
+ changedNames.add(name);
15092
14780
  index += 1;
15093
14781
  continue;
15094
14782
  }
15095
- if (arg.startsWith("-n")) {
14783
+ if (option === "-i" || option === "--ignore-environment") {
14784
+ for (const name of Object.keys(env)) {
14785
+ delete env[name];
14786
+ }
14787
+ changedNames.add("*");
14788
+ index += 1;
15096
14789
  continue;
15097
14790
  }
15098
- return null;
14791
+ if (option === "--") {
14792
+ index += 1;
14793
+ break;
14794
+ }
14795
+ if (option.startsWith("-")) {
14796
+ malformed = true;
14797
+ index += 1;
14798
+ break;
14799
+ }
14800
+ break;
15099
14801
  }
15100
- if (isRubyTestScript(arg)) {
15101
- return { includePaths, scriptPath: arg };
14802
+ while (index < tokens.length) {
14803
+ const token = tokens[index] ?? "";
14804
+ const match = ENV_PREFIX_PATTERN2.exec(token);
14805
+ if (!match) {
14806
+ break;
14807
+ }
14808
+ const [, name, value] = match;
14809
+ if (name) {
14810
+ env[name] = expandKnownVariables(value ?? "", env);
14811
+ changedNames.add(name);
14812
+ }
14813
+ index += 1;
15102
14814
  }
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
- ];
14815
+ const commandTokens = tokens.slice(index);
14816
+ return {
14817
+ env,
14818
+ commandTokens,
14819
+ malformed: malformed || commandTokens.length === 0,
14820
+ changedNames
14821
+ };
15124
14822
  }
15125
- if (innerBase === "rspec") {
15126
- const targetArgs = innerArgs.filter((arg) => !arg.startsWith("-"));
15127
- if (targetArgs.length === 0) {
15128
- return null;
14823
+ for (const token of tokens) {
14824
+ const match = ENV_PREFIX_PATTERN2.exec(token);
14825
+ if (!match) {
14826
+ break;
14827
+ }
14828
+ const [, name, value] = match;
14829
+ if (name) {
14830
+ env[name] = expandKnownVariables(value ?? "", env);
14831
+ changedNames.add(name);
15129
14832
  }
15130
- return [processRequirement(innerHead, "spawn", segment, ["process.test_runner.rspec"])];
15131
14833
  }
15132
- return null;
14834
+ return { env, malformed: false, changedNames };
15133
14835
  }
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");
14836
+ function expandKnownVariables(token, env) {
14837
+ return token.replace(
14838
+ /\$(?:\{([A-Za-z_][A-Za-z0-9_]*)\}|([A-Za-z_][A-Za-z0-9_]*))/g,
14839
+ (raw, a, b) => {
14840
+ const value = env[a ?? b];
14841
+ return value === void 0 ? raw : value;
15147
14842
  }
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;
14843
+ );
15167
14844
  }
15168
- function decodeRuntimeMetadataProcess(head, args, segment) {
15169
- if (head === "bundle") {
15170
- if (args.length === 1 && isMetadataOnlyArgv(args)) {
15171
- return [processRequirement(head, "inspect", segment, ["process.inspect.runtime_metadata"])];
14845
+ function stripRedirects(tokens) {
14846
+ const stripped = [];
14847
+ for (let index = 0; index < tokens.length; index += 1) {
14848
+ const token = tokens[index] ?? "";
14849
+ if (isFdDuplication(token)) {
14850
+ continue;
15172
14851
  }
15173
- if (args[0] === "exec" && args.length >= 2) {
15174
- const innerHead = args[1] ?? "";
15175
- const innerArgs = args.slice(2);
15176
- const innerBase = executableBaseName(innerHead);
15177
- if ((innerBase === "rails" || innerBase === "rake") && railsReadOnlySubcommand(innerArgs)) {
15178
- return [
15179
- processRequirement(innerHead, "inspect", segment, ["process.inspect.rails_read_only"])
15180
- ];
15181
- }
15182
- if (isMetadataOnlyArgv(innerArgs)) {
15183
- return [
15184
- processRequirement(innerHead, "inspect", segment, [
15185
- "process.inspect.bundle_exec_metadata"
15186
- ])
15187
- ];
15188
- }
15189
- const bundleExecInner = decodeBundleExecInner(innerHead, innerArgs, segment);
15190
- if (bundleExecInner) {
15191
- return bundleExecInner;
15192
- }
14852
+ if (!isRedirectOperator(token)) {
14853
+ stripped.push(token);
14854
+ continue;
15193
14855
  }
15194
- return null;
15195
- }
15196
- if ((head === "ruby" || head === "yarn") && args.length >= 1 && isMetadataOnlyArgv(args)) {
15197
- return [processRequirement(head, "inspect", segment, ["process.inspect.runtime_metadata"])];
15198
- }
15199
- if (head === "make" && args.includes("-n")) {
15200
- return [processRequirement(head, "inspect", segment, ["process.inspect.make_dry_run"])];
15201
- }
15202
- const base = executableBaseName(head);
15203
- if ((base === "rails" || base === "rake") && railsReadOnlySubcommand(args)) {
15204
- return [processRequirement(head, "inspect", segment, ["process.inspect.rails_read_only"])];
14856
+ index += 1;
15205
14857
  }
15206
- return null;
14858
+ return stripped;
15207
14859
  }
15208
- function decodeSetBuiltin(args) {
15209
- let index = 0;
15210
- while (index < args.length) {
15211
- const arg = args[index] ?? "";
15212
- if (arg === "--") {
15213
- index += 1;
14860
+ function stripStructuredRedirects(tokens) {
14861
+ const stripped = [];
14862
+ for (let index = 0; index < tokens.length; index += 1) {
14863
+ const token = tokens[index];
14864
+ if (!token) continue;
14865
+ if (isFdDuplication(token.value)) {
15214
14866
  continue;
15215
14867
  }
15216
- if (arg === "-o" || arg === "+o") {
15217
- if (!args[index + 1]) {
15218
- return false;
15219
- }
15220
- index += 2;
14868
+ if (!isRedirectOperator(token.value)) {
14869
+ stripped.push(token);
15221
14870
  continue;
15222
14871
  }
15223
- if (/^[-+][A-Za-z0-9]+$/.test(arg)) {
15224
- index += 1;
15225
- continue;
14872
+ index += 1;
14873
+ }
14874
+ return stripped;
14875
+ }
14876
+ function alignStructuredTokens(tokens, values) {
14877
+ if (values.length === 0) return [];
14878
+ for (let start = tokens.length - values.length; start >= 0; start -= 1) {
14879
+ const candidate = tokens.slice(start);
14880
+ if (candidate.length === values.length && candidate.every((token, index) => token.value === values[index])) {
14881
+ return candidate;
15226
14882
  }
15227
- return false;
15228
14883
  }
15229
- return true;
14884
+ return [];
15230
14885
  }
15231
- function decodeShellControlBuiltin(head, args) {
15232
- if (head === "set") {
15233
- return decodeSetBuiltin(args) ? [] : null;
14886
+ function resolvePathOperand(operand, cwd) {
14887
+ if (operand === "~") {
14888
+ return process.env.HOME ?? operand;
15234
14889
  }
15235
- if (head === "wait") {
15236
- if (args.length === 0 || args.every((arg) => /^\d+$/.test(arg))) {
15237
- return [];
15238
- }
14890
+ if (operand.startsWith("~/")) {
14891
+ return path42.join(process.env.HOME ?? "~", operand.slice(2));
14892
+ }
14893
+ return path42.resolve(cwd, operand);
14894
+ }
14895
+ function isOptionToken(value) {
14896
+ return value.startsWith("-") || value.startsWith("+");
14897
+ }
14898
+ function isShellHead(head) {
14899
+ return head === "bash" || head === "sh" || head === "zsh" || head === "dash" || head === "fish";
14900
+ }
14901
+ function pipeToShell(command) {
14902
+ return /(?:^|[|;&]\s*)(?:bash|sh|zsh|dash|fish)(?:\s|$)/.test(command) && /\|/.test(command);
14903
+ }
14904
+ function executableBaseName(head) {
14905
+ return path42.basename(head);
14906
+ }
14907
+ function isMetadataOnlyArgv(argv) {
14908
+ return argv.length > 0 && argv.every((token) => METADATA_ONLY_FLAGS.has(token));
14909
+ }
14910
+ function databaseEndpoint(raw) {
14911
+ if (!raw || raw.includes("$")) {
15239
14912
  return null;
15240
14913
  }
15241
- if (head === "exit") {
15242
- if (args.length === 0 || args.length === 1 && /^-?\d+$/.test(args[0] ?? "")) {
15243
- return [];
14914
+ try {
14915
+ const url = new URL(raw);
14916
+ if (!url.hostname || !url.protocol) {
14917
+ return null;
15244
14918
  }
14919
+ return {
14920
+ host: url.hostname.toLowerCase(),
14921
+ protocol: url.protocol.slice(0, -1),
14922
+ ...url.port ? { port: Number(url.port) } : {}
14923
+ };
14924
+ } catch {
15245
14925
  return null;
15246
14926
  }
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
14927
  }
15266
- function decodeProcessOrFilesystem(params) {
15267
- const { tokens, head, env, cwd, repoRoot, segment } = params;
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
- }
15276
- if (isCommandInspection(tokens)) {
15277
- return [processRequirement(head, "inspect", segment, ["process.inspect.command_lookup"])];
15278
- }
15279
- if (head === "lsof") {
15280
- return validLsof(args) ? [processRequirement(head, "inspect", segment, ["process.inspect.lsof"])] : unsupportedProcess(head, segment, "process.lsof_grammar_incomplete");
15281
- }
15282
- if (head === "ps") {
15283
- return validPs(args) ? [processRequirement(head, "inspect", segment, ["process.inspect.ps"])] : unsupportedProcess(head, segment, "process.ps_grammar_incomplete");
15284
- }
15285
- if (head === "test" || head === "[") {
15286
- return validFilesystemTest(head, args) ? [processRequirement(head, "inspect", segment, ["process.inspect.filesystem_test"])] : unsupportedProcess(head, segment, "process.filesystem_test_grammar_incomplete");
15287
- }
15288
- if (head === "docker") {
15289
- if (args[0] === "info") {
15290
- return validDockerInfo(args.slice(1)) ? [processRequirement(head, "inspect", segment, ["process.inspect.docker_info"])] : unsupportedProcess(head, segment, "process.docker_info_grammar_incomplete");
14928
+
14929
+ // src/core/effect-ir/shell-lower/augment.ts
14930
+ function addRedirectEffects(requirements, tokens, env, context, segment) {
14931
+ for (let index = 0; index < tokens.length; index += 1) {
14932
+ const operator = tokens[index] ?? "";
14933
+ if (!isRedirectOperator(operator) || isFdDuplication(operator)) {
14934
+ continue;
15291
14935
  }
15292
- if (args[0] === "compose" && ["up", "down", "start", "stop", "restart", "create", "rm"].includes(args[1] ?? "")) {
15293
- return [processRequirement(head, "spawn", segment, ["service.local_mutation"])];
14936
+ const rawTarget = tokens[index + 1];
14937
+ if (!rawTarget) {
14938
+ requirements.push(
14939
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
14940
+ "shell.redirect_target_missing"
14941
+ ])
14942
+ );
14943
+ continue;
15294
14944
  }
15295
- if (args[0] === "push" || args[0] === "build" && args.includes("--push") || args[0] === "buildx" && args[1] === "build" && (args.includes("--push") || args.some(
15296
- (arg) => arg.startsWith("--output=type=registry") || arg.startsWith("-o=type=registry")
15297
- ))) {
15298
- return [
15299
- processRequirement(head, "spawn", segment, ["tier0_external", "docker.remote_mutation"]),
15300
- requirement2(
15301
- "network.connect",
15302
- "network.connect",
15303
- {
15304
- kind: "network",
15305
- host: "registry",
15306
- protocol: "container-registry",
15307
- mode: "mutate",
15308
- payload: "present"
15309
- },
15310
- segment,
15311
- ["tier0_external", "docker.remote_mutation"]
15312
- )
15313
- ];
14945
+ if (operator.includes("<<")) {
14946
+ requirements.push(
14947
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
14948
+ "shell.heredoc_opaque"
14949
+ ])
14950
+ );
14951
+ index += 1;
14952
+ continue;
15314
14953
  }
15315
- if (args[0] === "run" || args[0] === "create" || args[0] === "pull" || args[0] === "build" || args[0] === "buildx" && args[1] === "build") {
15316
- return [
15317
- processRequirement(head, "spawn", segment, ["process.docker_spawn"]),
15318
- requirement2(
15319
- "network.acquire",
15320
- "network.connect",
15321
- {
15322
- kind: "network",
15323
- host: "unknown",
15324
- protocol: "container-registry",
15325
- mode: "ambiguous",
15326
- payload: "none"
15327
- },
15328
- segment,
15329
- ["docker.image_acquisition_possible"]
15330
- )
15331
- ];
14954
+ const target = expandKnownVariables(rawTarget, env);
14955
+ const resolved = resolvePathOperand(target, context.cwd);
14956
+ if (resolved === "/dev/null") {
14957
+ index += 1;
14958
+ continue;
15332
14959
  }
15333
- return [processRequirement(head, "spawn", segment, ["process.docker_spawn"])];
15334
- }
15335
- if (isShellHead(head)) {
15336
- const syntax = bashSyntaxTarget(args);
15337
- if (syntax) {
15338
- return [
15339
- processRequirement(head, "inspect", segment, ["process.inspect.shell_syntax"]),
15340
- requirement2(
15341
- "fs.read",
15342
- "fs.read",
15343
- { kind: "path", path: path41.resolve(cwd, syntax) },
15344
- segment,
15345
- ["shell.syntax_source_read"]
15346
- )
15347
- ];
14960
+ if (operator.includes("<")) {
14961
+ requirements.push(
14962
+ requirement2("fs.read", "fs.read", { kind: "path", path: resolved }, segment, [
14963
+ "shell.input_redirect"
14964
+ ])
14965
+ );
14966
+ addSecretRead(requirements, resolved, segment);
15348
14967
  }
15349
- return unsupportedProcess(head, segment, "process.shell_grammar_incomplete");
15350
- }
15351
- if (head === "prisma") {
15352
- return decodePrisma(args, env, repoRoot, segment);
15353
- }
15354
- const runtimeMetadata = decodeRuntimeMetadataProcess(head, args, segment);
15355
- if (runtimeMetadata) {
15356
- return runtimeMetadata;
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;
14968
+ if (operator.includes(">")) {
14969
+ addWriteEffects(requirements, resolved, segment, ["shell.output_redirect"]);
15365
14970
  }
14971
+ index += 1;
15366
14972
  }
15367
- const dockerCompose = decodeDockerComposeRun2(head, args, segment);
15368
- if (dockerCompose) {
15369
- return dockerCompose;
15370
- }
15371
- if ((head === "npm" || head === "pnpm") && args.length === 1 && isMetadataOnlyArgv(args)) {
15372
- return [processRequirement(head, "inspect", segment, ["process.inspect.package_manager"])];
15373
- }
15374
- if (head === "belay") {
15375
- return decodeBelay(args, repoRoot, segment);
15376
- }
15377
- if (head === "tsc") {
15378
- return decodeTsc(args, cwd, segment);
15379
- }
15380
- if (head === "go") {
15381
- return decodeGo(args, segment);
15382
- }
15383
- if (head === "rsync") {
15384
- return decodeRsync(args, cwd, segment);
15385
- }
15386
- if (head === "sed") {
15387
- return decodeSed(args, cwd, segment);
15388
- }
15389
- if (head === "base64" && args.length === 1 && ["-d", "-D", "--decode"].includes(args[0] ?? "")) {
15390
- return [processRequirement(head, "inspect", segment, ["process.inspect.base64_stdin"])];
15391
- }
15392
- if (head === "node") {
15393
- return decodeNode2(args, cwd, segment);
15394
- }
15395
- if (head === "vite" || head === "vite-node") {
15396
- return [processRequirement(head, "spawn", segment, ["process.local_dev_spawn"])];
14973
+ }
14974
+ function effectChangingEnvironmentSignals(head, env, changedNames) {
14975
+ if (head === "curl" && (env.CURL_HOME || changedNames.has("CURL_HOME") || changedNames.has("HOME") || changedNames.has("XDG_CONFIG_HOME"))) {
14976
+ return ["egress.curl.environment_config_override"];
15397
14977
  }
15398
- if ((head === "npm" || head === "pnpm") && args[0] === "publish" || head === "terraform" && args[0] === "apply") {
15399
- return [
15400
- processRequirement(head, "spawn", segment, ["tier0_external"]),
15401
- requirement2(
15402
- "network.connect",
15403
- "network.connect",
15404
- {
15405
- kind: "network",
15406
- host: "control-plane",
15407
- protocol: head,
15408
- mode: "mutate",
15409
- payload: "present"
15410
- },
15411
- segment,
15412
- ["tier0_external"]
15413
- )
15414
- ];
14978
+ if (head === "wget" && (env.WGETRC || changedNames.has("WGETRC") || changedNames.has("HOME"))) {
14979
+ return ["egress.wget.environment_config_override"];
15415
14980
  }
15416
- if (head === "cd") {
14981
+ if (head !== "git") {
15417
14982
  return [];
15418
14983
  }
15419
- if (head === "cp" || head === "mv") {
15420
- return decodeCopyMove(head, args, cwd, segment);
15421
- }
15422
- if (head === "rm") {
15423
- return decodeRm(args, cwd, repoRoot, segment);
15424
- }
15425
- if (head === "vitest" || head === "eslint" || head === "biome" || head === "belay") {
15426
- return [processRequirement(head, "spawn", segment, ["process.known_local_spawn"])];
15427
- }
15428
- if (head === "pwd" || head === "which" || head === "whoami") {
15429
- return [processRequirement(head, "inspect", segment, ["process.pure_inspection"])];
15430
- }
15431
- const readOperands = filesystemReadOperands(head, args);
15432
- if (readOperands !== null) {
15433
- const lowered = [processRequirement(head, "inspect", segment, ["process.filesystem_inspect"])];
15434
- for (const operand of readOperands) {
15435
- const resolved = resolvePathOperand(operand, cwd);
15436
- lowered.push(
15437
- requirement2("fs.read", "fs.read", { kind: "path", path: resolved }, segment, [
15438
- "filesystem.read"
15439
- ])
15440
- );
15441
- addSecretRead(lowered, resolved, segment);
14984
+ const hazardousNames = /* @__PURE__ */ new Set([
14985
+ "GIT_ASKPASS",
14986
+ "GIT_ALTERNATE_OBJECT_DIRECTORIES",
14987
+ "GIT_CEILING_DIRECTORIES",
14988
+ "GIT_COMMON_DIR",
14989
+ "GIT_CONFIG",
14990
+ "GIT_CONFIG_GLOBAL",
14991
+ "GIT_CONFIG_SYSTEM",
14992
+ "GIT_DIR",
14993
+ "GIT_DISCOVERY_ACROSS_FILESYSTEM",
14994
+ "GIT_EDITOR",
14995
+ "GIT_EXEC_PATH",
14996
+ "GIT_EXTERNAL_DIFF",
14997
+ "GIT_INDEX_FILE",
14998
+ "GIT_NAMESPACE",
14999
+ "GIT_OBJECT_DIRECTORY",
15000
+ "GIT_PAGER",
15001
+ "GIT_PROXY_COMMAND",
15002
+ "GIT_QUARANTINE_PATH",
15003
+ "GIT_SEQUENCE_EDITOR",
15004
+ "GIT_SHALLOW_FILE",
15005
+ "GIT_SSH",
15006
+ "GIT_SSH_COMMAND",
15007
+ "GIT_TEMPLATE_DIR",
15008
+ "GIT_WORK_TREE",
15009
+ "SSH_ASKPASS"
15010
+ ]);
15011
+ const overridden = changedNames.has("HOME") || changedNames.has("XDG_CONFIG_HOME") || Object.entries(env).some(([name, value]) => {
15012
+ if (!value) {
15013
+ return false;
15442
15014
  }
15443
- return lowered;
15444
- }
15445
- const writeOperands = filesystemWriteOperands(head, args);
15446
- if (writeOperands !== null) {
15447
- const lowered = [processRequirement(head, "spawn", segment, ["process.filesystem_mutation"])];
15448
- for (const operand of writeOperands) {
15449
- addWriteEffects(lowered, resolvePathOperand(operand, cwd), segment, ["filesystem.write"]);
15015
+ if (hazardousNames.has(name) || /^GIT_CONFIG_(?:KEY|VALUE)_\d+$/.test(name) || /^GIT_TRACE/.test(name)) {
15016
+ return true;
15450
15017
  }
15451
- return lowered;
15452
- }
15453
- if (head === "printf" || head === "echo" || head === "true" || head === "false" || head === ":") {
15454
- return [];
15455
- }
15456
- if (!head) {
15457
- return [
15458
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
15459
- "shell.segment_empty"
15460
- ])
15461
- ];
15462
- }
15463
- return unsupportedProcess(head, segment, "process.grammar_unknown");
15018
+ return name === "GIT_CONFIG_COUNT" && value !== "0";
15019
+ });
15020
+ return overridden ? ["git.environment_execution_or_config_override"] : [];
15464
15021
  }
15022
+
15023
+ // src/core/effect-ir/shell-lower/decode-process.ts
15024
+ import path46 from "node:path";
15025
+
15026
+ // src/core/effect-ir/shell-lower/decoders/belay.ts
15027
+ import path43 from "node:path";
15465
15028
  function decodeBelay(args, repoRoot, segment) {
15466
15029
  const [section, operation, key] = args;
15467
15030
  const judgeCommand = section === "judge" && operation !== void 0 && ["consent", "list", "status", "test", "use"].includes(operation);
@@ -15480,7 +15043,7 @@ function decodeBelay(args, repoRoot, segment) {
15480
15043
  requirement2(
15481
15044
  "control_plane.write",
15482
15045
  "control_plane.write",
15483
- { kind: "path", path: path41.join(repoRoot, ".belay-control-plane") },
15046
+ { kind: "path", path: path43.join(repoRoot, ".belay-control-plane") },
15484
15047
  segment,
15485
15048
  ["belay.config_non_judge_mutation"]
15486
15049
  )
@@ -15488,33 +15051,240 @@ function decodeBelay(args, repoRoot, segment) {
15488
15051
  }
15489
15052
  return [processRequirement("belay", "spawn", segment, ["process.known_local_spawn"])];
15490
15053
  }
15491
- function decodeTsc(args, cwd, segment) {
15492
- const metadataOnly = args.length > 0 && args.every((arg) => ["--help", "--version", "-h", "-v"].includes(arg));
15493
- const requirements = [];
15494
- let hasOutput = false;
15054
+
15055
+ // src/core/effect-ir/shell-lower/decoders/builtins.ts
15056
+ function decodeSetBuiltin(args) {
15057
+ let index = 0;
15058
+ while (index < args.length) {
15059
+ const arg = args[index] ?? "";
15060
+ if (arg === "--") {
15061
+ index += 1;
15062
+ continue;
15063
+ }
15064
+ if (arg === "-o" || arg === "+o") {
15065
+ if (!args[index + 1]) {
15066
+ return false;
15067
+ }
15068
+ index += 2;
15069
+ continue;
15070
+ }
15071
+ if (/^[-+][A-Za-z0-9]+$/.test(arg)) {
15072
+ index += 1;
15073
+ continue;
15074
+ }
15075
+ return false;
15076
+ }
15077
+ return true;
15078
+ }
15079
+ function decodeShellControlBuiltin(head, args) {
15080
+ if (head === "set") {
15081
+ return decodeSetBuiltin(args) ? [] : null;
15082
+ }
15083
+ if (head === "wait") {
15084
+ if (args.length === 0 || args.every((arg) => /^\d+$/.test(arg))) {
15085
+ return [];
15086
+ }
15087
+ return null;
15088
+ }
15089
+ if (head === "exit") {
15090
+ if (args.length === 0 || args.length === 1 && /^-?\d+$/.test(args[0] ?? "")) {
15091
+ return [];
15092
+ }
15093
+ return null;
15094
+ }
15095
+ return null;
15096
+ }
15097
+ function validLsof(args) {
15098
+ const flagsWithoutValues = /* @__PURE__ */ new Set(["-a", "-b", "-l", "-n", "-P", "-R", "-t", "-V"]);
15099
+ const flagsWithValues = /* @__PURE__ */ new Set(["-c", "-d", "-F", "-g", "-p", "-T", "-u", "+d", "+D"]);
15495
15100
  for (let index = 0; index < args.length; index += 1) {
15496
15101
  const arg = args[index] ?? "";
15497
- const inline = arg.match(/^--(?:outDir|outFile)=(.+)$/)?.[1];
15498
- const separate = (arg === "--outDir" || arg === "--outFile") && args[index + 1] ? args[index + 1] : null;
15499
- const output = inline ?? separate;
15500
- if (!output) {
15102
+ if (flagsWithoutValues.has(arg) || arg === "-i") {
15103
+ continue;
15104
+ }
15105
+ if (flagsWithValues.has(arg)) {
15106
+ if (!args[index + 1] || isOptionToken(args[index + 1] ?? "")) {
15107
+ return false;
15108
+ }
15109
+ index += 1;
15110
+ continue;
15111
+ }
15112
+ if (/^-[ablnPRtV]+$/.test(arg) && [...arg.slice(1)].every((flag) => "ablnPRtV".includes(flag)) || /^-i(?:TCP|UDP)?(?::\d+(?:-\d+)?)?$/.test(arg) || /^-s(?:TCP|UDP):[A-Za-z]+$/.test(arg) || /^-(?:c|d|F|g|p|T|u).+$/.test(arg) || /^\+(?:d|D).+$/.test(arg)) {
15113
+ continue;
15114
+ }
15115
+ return false;
15116
+ }
15117
+ return true;
15118
+ }
15119
+ function validPs(args) {
15120
+ const bareForms = /* @__PURE__ */ new Set(["aux", "ax", "x", "u"]);
15121
+ const flagsWithoutValues = /* @__PURE__ */ new Set(["-A", "-a", "-d", "-e", "-f", "-j", "-l", "-T", "-x"]);
15122
+ const flagsWithValues = /* @__PURE__ */ new Set([
15123
+ "--format",
15124
+ "--group",
15125
+ "--pid",
15126
+ "--ppid",
15127
+ "--sort",
15128
+ "--tty",
15129
+ "--user"
15130
+ ]);
15131
+ for (let index = 0; index < args.length; index += 1) {
15132
+ const arg = args[index] ?? "";
15133
+ if (bareForms.has(arg) || flagsWithoutValues.has(arg)) {
15134
+ continue;
15135
+ }
15136
+ if (flagsWithValues.has(arg)) {
15137
+ if (!args[index + 1] || isOptionToken(args[index + 1] ?? "")) {
15138
+ return false;
15139
+ }
15140
+ index += 1;
15141
+ continue;
15142
+ }
15143
+ if (/^(?:--format|--group|--pid|--ppid|--sort|--tty|--user)=.+$/.test(arg)) {
15144
+ continue;
15145
+ }
15146
+ if (/^-[^-]/.test(arg)) {
15147
+ const noValue = /* @__PURE__ */ new Set(["A", "a", "d", "e", "f", "j", "l", "T", "x"]);
15148
+ const withValue = /* @__PURE__ */ new Set(["G", "g", "N", "o", "p", "t", "U", "u"]);
15149
+ const characters = arg.slice(1);
15150
+ let valid = true;
15151
+ for (let optionIndex = 0; optionIndex < characters.length; optionIndex += 1) {
15152
+ const option = characters[optionIndex] ?? "";
15153
+ if (noValue.has(option)) {
15154
+ continue;
15155
+ }
15156
+ if (withValue.has(option)) {
15157
+ if (optionIndex === characters.length - 1) {
15158
+ if (!args[index + 1] || isOptionToken(args[index + 1] ?? "")) {
15159
+ return false;
15160
+ }
15161
+ index += 1;
15162
+ }
15163
+ break;
15164
+ }
15165
+ valid = false;
15166
+ break;
15167
+ }
15168
+ if (valid) {
15169
+ continue;
15170
+ }
15171
+ }
15172
+ return false;
15173
+ }
15174
+ return true;
15175
+ }
15176
+ function validFilesystemTest(head, args) {
15177
+ const operands = head === "[" && args.at(-1) === "]" ? args.slice(0, -1) : args;
15178
+ if (head === "[" && args.at(-1) !== "]") {
15179
+ return false;
15180
+ }
15181
+ return operands.length === 2 && (/* @__PURE__ */ new Set(["-b", "-c", "-d", "-e", "-f", "-h", "-L", "-r", "-s", "-w", "-x"])).has(
15182
+ operands[0] ?? ""
15183
+ ) && Boolean(operands[1]);
15184
+ }
15185
+ function bashSyntaxTarget(args) {
15186
+ let syntaxOnly = false;
15187
+ let script = null;
15188
+ const shortFlags = new Set("abefhkmnptuvxBCEHPT".split(""));
15189
+ const longFlags = /* @__PURE__ */ new Set([
15190
+ "--debugger",
15191
+ "--dump-po-strings",
15192
+ "--dump-strings",
15193
+ "--help",
15194
+ "--login",
15195
+ "--noediting",
15196
+ "--noprofile",
15197
+ "--norc",
15198
+ "--posix",
15199
+ "--restricted",
15200
+ "--verbose",
15201
+ "--version"
15202
+ ]);
15203
+ for (let index = 0; index < args.length; index += 1) {
15204
+ const arg = args[index] ?? "";
15205
+ if (arg === "-c" || arg === "-lc") {
15206
+ return null;
15207
+ }
15208
+ if (arg === "-O" || arg === "+O" || arg === "--rcfile" || arg === "--init-file") {
15209
+ if (!args[index + 1] || isOptionToken(args[index + 1] ?? "")) {
15210
+ return null;
15211
+ }
15212
+ index += 1;
15213
+ continue;
15214
+ }
15215
+ if (arg.startsWith("-O") && arg.length > 2 || arg.startsWith("+O") && arg.length > 2 || arg.startsWith("--rcfile=") && arg.length > "--rcfile=".length || arg.startsWith("--init-file=") && arg.length > "--init-file=".length) {
15216
+ continue;
15217
+ }
15218
+ if (longFlags.has(arg)) {
15219
+ continue;
15220
+ }
15221
+ if (/^[+-][A-Za-z]+$/.test(arg)) {
15222
+ const options = arg.slice(1);
15223
+ if ([...options].some((option) => !shortFlags.has(option))) {
15224
+ return null;
15225
+ }
15226
+ syntaxOnly ||= options.includes("n");
15227
+ continue;
15228
+ }
15229
+ if (arg === "--") {
15230
+ script = args[index + 1] ?? null;
15231
+ break;
15232
+ }
15233
+ if (arg.startsWith("-") || arg.startsWith("+")) {
15234
+ return null;
15235
+ }
15236
+ script = arg;
15237
+ break;
15238
+ }
15239
+ return syntaxOnly ? script : null;
15240
+ }
15241
+
15242
+ // src/core/effect-ir/shell-lower/decoders/docker.ts
15243
+ function decodeDockerComposeRun2(head, args, segment) {
15244
+ let composeArgs = null;
15245
+ let command = head;
15246
+ if (head === "docker-compose") {
15247
+ composeArgs = args;
15248
+ } else if (head === "docker" && args[0] === "compose") {
15249
+ composeArgs = args.slice(1);
15250
+ command = "docker";
15251
+ }
15252
+ if (!composeArgs) {
15253
+ return null;
15254
+ }
15255
+ if (composeArgs.includes("run")) {
15256
+ return [processRequirement(command, "spawn", segment, ["process.docker_compose_run"])];
15257
+ }
15258
+ return unsupportedProcess(command, segment, "process.docker_compose_grammar_incomplete");
15259
+ }
15260
+ function validDockerInfo(args) {
15261
+ for (let index = 0; index < args.length; index += 1) {
15262
+ const arg = args[index] ?? "";
15263
+ if (arg === "--help") {
15501
15264
  continue;
15502
15265
  }
15503
- hasOutput = true;
15504
- addWriteEffects(requirements, resolvePathOperand(output, cwd), segment, ["tsc.output"]);
15505
- if (separate) {
15266
+ if (arg === "--format" || arg === "-f") {
15267
+ if (!args[index + 1] || isOptionToken(args[index + 1] ?? "")) {
15268
+ return false;
15269
+ }
15506
15270
  index += 1;
15271
+ continue;
15507
15272
  }
15273
+ if (arg.startsWith("--format=")) {
15274
+ if (arg.slice("--format=".length).length === 0) {
15275
+ return false;
15276
+ }
15277
+ continue;
15278
+ }
15279
+ return false;
15508
15280
  }
15509
- const typecheckOnly = args.includes("--noEmit") && !hasOutput;
15510
- const inspect = metadataOnly || typecheckOnly;
15511
- requirements.unshift(
15512
- processRequirement("tsc", inspect ? "inspect" : "spawn", segment, [
15513
- metadataOnly ? "process.inspect.tsc_metadata" : typecheckOnly ? "process.inspect.tsc_typecheck" : "process.known_local_spawn"
15514
- ])
15515
- );
15516
- return requirements;
15281
+ return true;
15517
15282
  }
15283
+
15284
+ // src/core/effect-ir/shell-lower/decoders/filesystem.ts
15285
+ init_git_resource_identity();
15286
+ import { lstatSync as lstatSync2, realpathSync as realpathSync4 } from "node:fs";
15287
+ import path44 from "node:path";
15518
15288
  function decodeCopyMove(head, args, cwd, segment) {
15519
15289
  const requirements = [processRequirement(head, "spawn", segment, ["process.filesystem_mutation"])];
15520
15290
  const operands = [];
@@ -15637,28 +15407,349 @@ function decodeRm(args, cwd, repoRoot, segment) {
15637
15407
  );
15638
15408
  }
15639
15409
  }
15640
- return requirements;
15410
+ return requirements;
15411
+ }
15412
+ function canonicalRmOperand(targetPath, finalOperandIsSymlink) {
15413
+ try {
15414
+ if (finalOperandIsSymlink) {
15415
+ return path44.join(realpathSync4.native(path44.dirname(targetPath)), path44.basename(targetPath));
15416
+ }
15417
+ return realpathSync4.native(targetPath);
15418
+ } catch {
15419
+ return path44.resolve(targetPath);
15420
+ }
15421
+ }
15422
+ function isSymbolicLink(targetPath) {
15423
+ try {
15424
+ return lstatSync2(targetPath).isSymbolicLink();
15425
+ } catch {
15426
+ return false;
15427
+ }
15428
+ }
15429
+ function pathContains(ancestor, candidate) {
15430
+ const relative = path44.relative(path44.resolve(ancestor), path44.resolve(candidate));
15431
+ return relative === "" || !relative.startsWith("..") && !path44.isAbsolute(relative);
15432
+ }
15433
+ function filesystemReadOperands(head, args) {
15434
+ switch (head) {
15435
+ case "ls": {
15436
+ const operands = args.filter((arg) => arg && !arg.startsWith("-"));
15437
+ return operands.length > 0 ? operands : ["."];
15438
+ }
15439
+ case "find":
15440
+ return findReadOperands(args);
15441
+ case "cat":
15442
+ case "head":
15443
+ case "tail":
15444
+ case "less":
15445
+ case "stat":
15446
+ case "file":
15447
+ case "du":
15448
+ case "wc":
15449
+ return args.filter((arg) => arg && !arg.startsWith("-"));
15450
+ case "grep":
15451
+ case "rg":
15452
+ return args.slice(1).filter((arg) => arg && !arg.startsWith("-"));
15453
+ case "jq":
15454
+ return args.slice(1).filter((arg) => arg && !arg.startsWith("-"));
15455
+ default:
15456
+ return null;
15457
+ }
15458
+ }
15459
+ function findReadOperands(args) {
15460
+ const mutatingPrimaries = /* @__PURE__ */ new Set([
15461
+ "-delete",
15462
+ "-exec",
15463
+ "-execdir",
15464
+ "-fls",
15465
+ "-fprint",
15466
+ "-fprintf",
15467
+ "-ok",
15468
+ "-okdir"
15469
+ ]);
15470
+ if (args.some(
15471
+ (arg) => mutatingPrimaries.has(arg) || [...mutatingPrimaries].some((primary) => arg.startsWith(`${primary}=`))
15472
+ )) {
15473
+ return null;
15474
+ }
15475
+ const paths = [];
15476
+ for (const arg of args) {
15477
+ if (arg.startsWith("-") || arg === "!" || arg === "(") {
15478
+ break;
15479
+ }
15480
+ paths.push(arg);
15481
+ }
15482
+ return paths.length > 0 ? paths : ["."];
15483
+ }
15484
+ function filesystemWriteOperands(head, args) {
15485
+ const positional = args.filter((arg) => arg && !arg.startsWith("-"));
15486
+ switch (head) {
15487
+ case "touch":
15488
+ case "mkdir":
15489
+ case "rm":
15490
+ case "truncate":
15491
+ case "chmod":
15492
+ return positional;
15493
+ case "cp":
15494
+ case "mv":
15495
+ return positional.length > 0 ? [positional[positional.length - 1] ?? ""] : [];
15496
+ case "tee":
15497
+ return positional;
15498
+ default:
15499
+ return null;
15500
+ }
15501
+ }
15502
+
15503
+ // src/core/effect-ir/shell-lower/decoders/prisma.ts
15504
+ function decodePrisma(args, env, repoRoot, segment) {
15505
+ const processEffect = processRequirement("prisma", "spawn", segment, ["process.prisma"]);
15506
+ if (args[0] === "generate") {
15507
+ return [
15508
+ processEffect,
15509
+ requirement2("fs.write", "fs.write", { kind: "path", path: repoRoot }, segment, [
15510
+ "prisma.generate.repo_local"
15511
+ ])
15512
+ ];
15513
+ }
15514
+ const databaseMutation = args[0] === "migrate" || args[0] === "db" && args[1] === "seed" || args[0] === "seed";
15515
+ if (!databaseMutation) {
15516
+ return [
15517
+ processEffect,
15518
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
15519
+ "prisma.grammar_incomplete"
15520
+ ])
15521
+ ];
15522
+ }
15523
+ const endpoint = databaseEndpoint(env.DATABASE_URL);
15524
+ if (!endpoint) {
15525
+ return [
15526
+ processEffect,
15527
+ requirement2(
15528
+ "network.connect",
15529
+ "network.connect",
15530
+ {
15531
+ kind: "network",
15532
+ host: "unknown",
15533
+ protocol: "database",
15534
+ mode: "ambiguous",
15535
+ payload: "present"
15536
+ },
15537
+ segment,
15538
+ ["prisma.database_endpoint_unknown"]
15539
+ ),
15540
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
15541
+ "prisma.database_endpoint_unknown"
15542
+ ])
15543
+ ];
15544
+ }
15545
+ const network = requirement2(
15546
+ "network.connect",
15547
+ "network.connect",
15548
+ {
15549
+ kind: "network",
15550
+ ...endpoint,
15551
+ mode: "mutate",
15552
+ payload: "present"
15553
+ },
15554
+ segment,
15555
+ [LOOPBACK_HOSTS2.has(endpoint.host) ? "prisma.database_local" : "prisma.database_remote"]
15556
+ );
15557
+ if (LOOPBACK_HOSTS2.has(endpoint.host)) {
15558
+ return [processEffect, network];
15559
+ }
15560
+ return [
15561
+ processEffect,
15562
+ network,
15563
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
15564
+ "prisma.remote_database_mutation"
15565
+ ])
15566
+ ];
15567
+ }
15568
+
15569
+ // src/core/effect-ir/shell-lower/decoders/ruby.ts
15570
+ init_path_utils();
15571
+ import path45 from "node:path";
15572
+ var RAILS_READ_ONLY_SUBCOMMANDS = /* @__PURE__ */ new Set(["routes", "middleware", "stats", "about", "version"]);
15573
+ function railsReadOnlySubcommand(args) {
15574
+ const subcommand = args[0];
15575
+ if (!subcommand || subcommand.startsWith("-")) {
15576
+ return false;
15577
+ }
15578
+ return RAILS_READ_ONLY_SUBCOMMANDS.has(subcommand);
15579
+ }
15580
+ function isRubyTestScript(scriptPath) {
15581
+ const base = path45.basename(scriptPath);
15582
+ return base.endsWith("_test.rb") || base.endsWith("_spec.rb");
15583
+ }
15584
+ function parseRubyTestInvocation(args) {
15585
+ const includePaths = [];
15586
+ for (let index = 0; index < args.length; index += 1) {
15587
+ const arg = args[index] ?? "";
15588
+ if (arg === "-e" || arg === "-r") {
15589
+ return null;
15590
+ }
15591
+ if (arg === "-I") {
15592
+ const includePath = args[index + 1];
15593
+ if (!includePath) {
15594
+ return null;
15595
+ }
15596
+ includePaths.push(includePath);
15597
+ index += 1;
15598
+ continue;
15599
+ }
15600
+ if (arg.startsWith("-I") && arg.length > 2) {
15601
+ includePaths.push(arg.slice(2));
15602
+ continue;
15603
+ }
15604
+ if (arg.startsWith("-")) {
15605
+ if (arg === "-n") {
15606
+ if (!args[index + 1]) {
15607
+ return null;
15608
+ }
15609
+ index += 1;
15610
+ continue;
15611
+ }
15612
+ if (arg.startsWith("-n")) {
15613
+ continue;
15614
+ }
15615
+ return null;
15616
+ }
15617
+ if (isRubyTestScript(arg)) {
15618
+ return { includePaths, scriptPath: arg };
15619
+ }
15620
+ return null;
15621
+ }
15622
+ return null;
15623
+ }
15624
+ function isRubocopMutating(args) {
15625
+ return args.some(
15626
+ (arg) => arg === "-A" || arg === "-a" || arg === "--auto-correct" || arg === "--autocorrect" || arg.startsWith("--auto-correct-all") || arg.startsWith("--autocorrect-all")
15627
+ );
15628
+ }
15629
+ function decodeBundleExecInner(innerHead, innerArgs, segment) {
15630
+ const innerBase = executableBaseName(innerHead);
15631
+ if (innerBase === "rubocop") {
15632
+ const mutating = isRubocopMutating(innerArgs);
15633
+ return [
15634
+ processRequirement(
15635
+ innerHead,
15636
+ mutating ? "spawn" : "inspect",
15637
+ segment,
15638
+ mutating ? ["process.linter.mutating"] : ["process.inspect.linter"]
15639
+ )
15640
+ ];
15641
+ }
15642
+ if (innerBase === "rspec") {
15643
+ const targetArgs = innerArgs.filter((arg) => !arg.startsWith("-"));
15644
+ if (targetArgs.length === 0) {
15645
+ return null;
15646
+ }
15647
+ return [processRequirement(innerHead, "spawn", segment, ["process.test_runner.rspec"])];
15648
+ }
15649
+ return null;
15641
15650
  }
15642
- function canonicalRmOperand(targetPath, finalOperandIsSymlink) {
15643
- try {
15644
- if (finalOperandIsSymlink) {
15645
- return path41.join(realpathSync4.native(path41.dirname(targetPath)), path41.basename(targetPath));
15651
+ function decodeRuby(args, cwd, repoRoot, segment) {
15652
+ const parsed = parseRubyTestInvocation(args);
15653
+ if (!parsed) {
15654
+ return unsupportedProcess("ruby", segment, "process.ruby_grammar_incomplete");
15655
+ }
15656
+ const scriptPath = resolvePathOperand(parsed.scriptPath, cwd);
15657
+ if (!pathWithinRoot(canonicalPath(repoRoot), canonicalPath(scriptPath))) {
15658
+ return unsupportedProcess("ruby", segment, "process.ruby_outside_repo");
15659
+ }
15660
+ for (const includePath of parsed.includePaths) {
15661
+ const resolvedInclude = resolvePathOperand(includePath, cwd);
15662
+ if (!pathWithinRoot(canonicalPath(repoRoot), canonicalPath(resolvedInclude))) {
15663
+ return unsupportedProcess("ruby", segment, "process.ruby_outside_repo");
15646
15664
  }
15647
- return realpathSync4.native(targetPath);
15648
- } catch {
15649
- return path41.resolve(targetPath);
15650
15665
  }
15666
+ const lowered = [
15667
+ processRequirement("ruby", "spawn", segment, ["process.test_runner.minitest"]),
15668
+ requirement2("fs.read", "fs.read", { kind: "path", path: scriptPath }, segment, [
15669
+ "ruby.minitest_script_read"
15670
+ ])
15671
+ ];
15672
+ for (const includePath of parsed.includePaths) {
15673
+ lowered.push(
15674
+ requirement2(
15675
+ "fs.read",
15676
+ "fs.read",
15677
+ { kind: "path", path: resolvePathOperand(includePath, cwd) },
15678
+ segment,
15679
+ ["ruby.minitest_load_path_read"]
15680
+ )
15681
+ );
15682
+ }
15683
+ return lowered;
15651
15684
  }
15652
- function isSymbolicLink(targetPath) {
15653
- try {
15654
- return lstatSync2(targetPath).isSymbolicLink();
15655
- } catch {
15656
- return false;
15685
+ function decodeRuntimeMetadataProcess(head, args, segment) {
15686
+ if (head === "bundle") {
15687
+ if (args.length === 1 && isMetadataOnlyArgv(args)) {
15688
+ return [processRequirement(head, "inspect", segment, ["process.inspect.runtime_metadata"])];
15689
+ }
15690
+ if (args[0] === "exec" && args.length >= 2) {
15691
+ const innerHead = args[1] ?? "";
15692
+ const innerArgs = args.slice(2);
15693
+ const innerBase = executableBaseName(innerHead);
15694
+ if ((innerBase === "rails" || innerBase === "rake") && railsReadOnlySubcommand(innerArgs)) {
15695
+ return [
15696
+ processRequirement(innerHead, "inspect", segment, ["process.inspect.rails_read_only"])
15697
+ ];
15698
+ }
15699
+ if (isMetadataOnlyArgv(innerArgs)) {
15700
+ return [
15701
+ processRequirement(innerHead, "inspect", segment, [
15702
+ "process.inspect.bundle_exec_metadata"
15703
+ ])
15704
+ ];
15705
+ }
15706
+ const bundleExecInner = decodeBundleExecInner(innerHead, innerArgs, segment);
15707
+ if (bundleExecInner) {
15708
+ return bundleExecInner;
15709
+ }
15710
+ }
15711
+ return null;
15712
+ }
15713
+ if ((head === "ruby" || head === "yarn") && args.length >= 1 && isMetadataOnlyArgv(args)) {
15714
+ return [processRequirement(head, "inspect", segment, ["process.inspect.runtime_metadata"])];
15715
+ }
15716
+ if (head === "make" && args.includes("-n")) {
15717
+ return [processRequirement(head, "inspect", segment, ["process.inspect.make_dry_run"])];
15657
15718
  }
15719
+ const base = executableBaseName(head);
15720
+ if ((base === "rails" || base === "rake") && railsReadOnlySubcommand(args)) {
15721
+ return [processRequirement(head, "inspect", segment, ["process.inspect.rails_read_only"])];
15722
+ }
15723
+ return null;
15658
15724
  }
15659
- function pathContains(ancestor, candidate) {
15660
- const relative = path41.relative(path41.resolve(ancestor), path41.resolve(candidate));
15661
- return relative === "" || !relative.startsWith("..") && !path41.isAbsolute(relative);
15725
+
15726
+ // src/core/effect-ir/shell-lower/decoders/toolchain.ts
15727
+ function decodeTsc(args, cwd, segment) {
15728
+ const metadataOnly = args.length > 0 && args.every((arg) => ["--help", "--version", "-h", "-v"].includes(arg));
15729
+ const requirements = [];
15730
+ let hasOutput = false;
15731
+ for (let index = 0; index < args.length; index += 1) {
15732
+ const arg = args[index] ?? "";
15733
+ const inline = arg.match(/^--(?:outDir|outFile)=(.+)$/)?.[1];
15734
+ const separate = (arg === "--outDir" || arg === "--outFile") && args[index + 1] ? args[index + 1] : null;
15735
+ const output = inline ?? separate;
15736
+ if (!output) {
15737
+ continue;
15738
+ }
15739
+ hasOutput = true;
15740
+ addWriteEffects(requirements, resolvePathOperand(output, cwd), segment, ["tsc.output"]);
15741
+ if (separate) {
15742
+ index += 1;
15743
+ }
15744
+ }
15745
+ const typecheckOnly = args.includes("--noEmit") && !hasOutput;
15746
+ const inspect = metadataOnly || typecheckOnly;
15747
+ requirements.unshift(
15748
+ processRequirement("tsc", inspect ? "inspect" : "spawn", segment, [
15749
+ metadataOnly ? "process.inspect.tsc_metadata" : typecheckOnly ? "process.inspect.tsc_typecheck" : "process.known_local_spawn"
15750
+ ])
15751
+ );
15752
+ return requirements;
15662
15753
  }
15663
15754
  function decodeGo(args, segment) {
15664
15755
  if (["test", "list", "vet"].includes(args[0] ?? "")) {
@@ -15789,316 +15880,351 @@ function decodeSed(args, cwd, segment) {
15789
15880
  }
15790
15881
  files.push(arg);
15791
15882
  }
15792
- const operation = inline ? "spawn" : "inspect";
15793
- const lowered = [processRequirement("sed", operation, segment, ["process.sed"])];
15794
- for (const file of files) {
15795
- lowered.push(
15796
- requirement2(
15797
- inline ? "fs.write" : "fs.read",
15798
- inline ? "fs.write" : "fs.read",
15799
- { kind: "path", path: resolvePathOperand(file, cwd) },
15800
- segment,
15801
- [inline ? "sed.in_place_write" : "sed.file_read"]
15802
- )
15803
- );
15883
+ const operation = inline ? "spawn" : "inspect";
15884
+ const lowered = [processRequirement("sed", operation, segment, ["process.sed"])];
15885
+ for (const file of files) {
15886
+ lowered.push(
15887
+ requirement2(
15888
+ inline ? "fs.write" : "fs.read",
15889
+ inline ? "fs.write" : "fs.read",
15890
+ { kind: "path", path: resolvePathOperand(file, cwd) },
15891
+ segment,
15892
+ [inline ? "sed.in_place_write" : "sed.file_read"]
15893
+ )
15894
+ );
15895
+ }
15896
+ return lowered;
15897
+ }
15898
+ function decodeNode2(args, cwd, segment) {
15899
+ if (args.length > 0 && args.every((arg) => ["--help", "--version", "-h", "-v"].includes(arg))) {
15900
+ return [processRequirement("node", "inspect", segment, ["process.inspect.node_metadata"])];
15901
+ }
15902
+ if ((args[0] === "--check" || args[0] === "-c") && args.length === 2 && args[1]) {
15903
+ return [
15904
+ processRequirement("node", "inspect", segment, ["process.inspect.node_syntax"]),
15905
+ requirement2(
15906
+ "fs.read",
15907
+ "fs.read",
15908
+ { kind: "path", path: resolvePathOperand(args[1], cwd) },
15909
+ segment,
15910
+ ["node.syntax_source_read"]
15911
+ )
15912
+ ];
15913
+ }
15914
+ return unsupportedProcess("node", segment, "process.node_grammar_incomplete");
15915
+ }
15916
+
15917
+ // src/core/effect-ir/shell-lower/decode-process.ts
15918
+ function decodeProcessOrFilesystem(params) {
15919
+ const { tokens, head, env, cwd, repoRoot, segment } = params;
15920
+ const args = tokens.slice(1);
15921
+ if (tokens.length > 0 && tokens.every((token) => ENV_PREFIX_PATTERN2.test(token))) {
15922
+ return [];
15923
+ }
15924
+ const shellControl = decodeShellControlBuiltin(head, args);
15925
+ if (shellControl) {
15926
+ return shellControl;
15927
+ }
15928
+ if (isCommandInspection(tokens)) {
15929
+ return [processRequirement(head, "inspect", segment, ["process.inspect.command_lookup"])];
15930
+ }
15931
+ if (head === "lsof") {
15932
+ return validLsof(args) ? [processRequirement(head, "inspect", segment, ["process.inspect.lsof"])] : unsupportedProcess(head, segment, "process.lsof_grammar_incomplete");
15933
+ }
15934
+ if (head === "ps") {
15935
+ return validPs(args) ? [processRequirement(head, "inspect", segment, ["process.inspect.ps"])] : unsupportedProcess(head, segment, "process.ps_grammar_incomplete");
15936
+ }
15937
+ if (head === "test" || head === "[") {
15938
+ return validFilesystemTest(head, args) ? [processRequirement(head, "inspect", segment, ["process.inspect.filesystem_test"])] : unsupportedProcess(head, segment, "process.filesystem_test_grammar_incomplete");
15939
+ }
15940
+ if (head === "docker") {
15941
+ if (args[0] === "info") {
15942
+ return validDockerInfo(args.slice(1)) ? [processRequirement(head, "inspect", segment, ["process.inspect.docker_info"])] : unsupportedProcess(head, segment, "process.docker_info_grammar_incomplete");
15943
+ }
15944
+ if (args[0] === "compose" && ["up", "down", "start", "stop", "restart", "create", "rm"].includes(args[1] ?? "")) {
15945
+ return [processRequirement(head, "spawn", segment, ["service.local_mutation"])];
15946
+ }
15947
+ if (args[0] === "push" || args[0] === "build" && args.includes("--push") || args[0] === "buildx" && args[1] === "build" && (args.includes("--push") || args.some(
15948
+ (arg) => arg.startsWith("--output=type=registry") || arg.startsWith("-o=type=registry")
15949
+ ))) {
15950
+ return [
15951
+ processRequirement(head, "spawn", segment, ["tier0_external", "docker.remote_mutation"]),
15952
+ requirement2(
15953
+ "network.connect",
15954
+ "network.connect",
15955
+ {
15956
+ kind: "network",
15957
+ host: "registry",
15958
+ protocol: "container-registry",
15959
+ mode: "mutate",
15960
+ payload: "present"
15961
+ },
15962
+ segment,
15963
+ ["tier0_external", "docker.remote_mutation"]
15964
+ )
15965
+ ];
15966
+ }
15967
+ if (args[0] === "run" || args[0] === "create" || args[0] === "pull" || args[0] === "build" || args[0] === "buildx" && args[1] === "build") {
15968
+ return [
15969
+ processRequirement(head, "spawn", segment, ["process.docker_spawn"]),
15970
+ requirement2(
15971
+ "network.acquire",
15972
+ "network.connect",
15973
+ {
15974
+ kind: "network",
15975
+ host: "unknown",
15976
+ protocol: "container-registry",
15977
+ mode: "ambiguous",
15978
+ payload: "none"
15979
+ },
15980
+ segment,
15981
+ ["docker.image_acquisition_possible"]
15982
+ )
15983
+ ];
15984
+ }
15985
+ return [processRequirement(head, "spawn", segment, ["process.docker_spawn"])];
15986
+ }
15987
+ if (isShellHead(head)) {
15988
+ const syntax = bashSyntaxTarget(args);
15989
+ if (syntax) {
15990
+ return [
15991
+ processRequirement(head, "inspect", segment, ["process.inspect.shell_syntax"]),
15992
+ requirement2(
15993
+ "fs.read",
15994
+ "fs.read",
15995
+ { kind: "path", path: path46.resolve(cwd, syntax) },
15996
+ segment,
15997
+ ["shell.syntax_source_read"]
15998
+ )
15999
+ ];
16000
+ }
16001
+ return unsupportedProcess(head, segment, "process.shell_grammar_incomplete");
16002
+ }
16003
+ if (head === "prisma") {
16004
+ return decodePrisma(args, env, repoRoot, segment);
16005
+ }
16006
+ const runtimeMetadata = decodeRuntimeMetadataProcess(head, args, segment);
16007
+ if (runtimeMetadata) {
16008
+ return runtimeMetadata;
16009
+ }
16010
+ if (head === "ruby") {
16011
+ return decodeRuby(args, cwd, repoRoot, segment);
16012
+ }
16013
+ if (head === "rubocop" || head === "rspec") {
16014
+ const decoded = decodeBundleExecInner(head, args, segment);
16015
+ if (decoded) {
16016
+ return decoded;
16017
+ }
16018
+ }
16019
+ const dockerCompose = decodeDockerComposeRun2(head, args, segment);
16020
+ if (dockerCompose) {
16021
+ return dockerCompose;
16022
+ }
16023
+ if ((head === "npm" || head === "pnpm") && args.length === 1 && isMetadataOnlyArgv(args)) {
16024
+ return [processRequirement(head, "inspect", segment, ["process.inspect.package_manager"])];
16025
+ }
16026
+ if (head === "belay") {
16027
+ return decodeBelay(args, repoRoot, segment);
16028
+ }
16029
+ if (head === "tsc") {
16030
+ return decodeTsc(args, cwd, segment);
15804
16031
  }
15805
- return lowered;
15806
- }
15807
- function decodeNode2(args, cwd, segment) {
15808
- if (args.length > 0 && args.every((arg) => ["--help", "--version", "-h", "-v"].includes(arg))) {
15809
- return [processRequirement("node", "inspect", segment, ["process.inspect.node_metadata"])];
16032
+ if (head === "go") {
16033
+ return decodeGo(args, segment);
15810
16034
  }
15811
- if ((args[0] === "--check" || args[0] === "-c") && args.length === 2 && args[1]) {
15812
- return [
15813
- processRequirement("node", "inspect", segment, ["process.inspect.node_syntax"]),
15814
- requirement2(
15815
- "fs.read",
15816
- "fs.read",
15817
- { kind: "path", path: resolvePathOperand(args[1], cwd) },
15818
- segment,
15819
- ["node.syntax_source_read"]
15820
- )
15821
- ];
16035
+ if (head === "rsync") {
16036
+ return decodeRsync(args, cwd, segment);
15822
16037
  }
15823
- return unsupportedProcess("node", segment, "process.node_grammar_incomplete");
15824
- }
15825
- function decodePrisma(args, env, repoRoot, segment) {
15826
- const processEffect = processRequirement("prisma", "spawn", segment, ["process.prisma"]);
15827
- if (args[0] === "generate") {
15828
- return [
15829
- processEffect,
15830
- requirement2("fs.write", "fs.write", { kind: "path", path: repoRoot }, segment, [
15831
- "prisma.generate.repo_local"
15832
- ])
15833
- ];
16038
+ if (head === "sed") {
16039
+ return decodeSed(args, cwd, segment);
15834
16040
  }
15835
- const databaseMutation = args[0] === "migrate" || args[0] === "db" && args[1] === "seed" || args[0] === "seed";
15836
- if (!databaseMutation) {
15837
- return [
15838
- processEffect,
15839
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
15840
- "prisma.grammar_incomplete"
15841
- ])
15842
- ];
16041
+ if (head === "base64" && args.length === 1 && ["-d", "-D", "--decode"].includes(args[0] ?? "")) {
16042
+ return [processRequirement(head, "inspect", segment, ["process.inspect.base64_stdin"])];
15843
16043
  }
15844
- const endpoint = databaseEndpoint(env.DATABASE_URL);
15845
- if (!endpoint) {
16044
+ if (head === "node") {
16045
+ return decodeNode2(args, cwd, segment);
16046
+ }
16047
+ if (head === "vite" || head === "vite-node") {
16048
+ return [processRequirement(head, "spawn", segment, ["process.local_dev_spawn"])];
16049
+ }
16050
+ if ((head === "npm" || head === "pnpm") && args[0] === "publish" || head === "terraform" && args[0] === "apply") {
15846
16051
  return [
15847
- processEffect,
16052
+ processRequirement(head, "spawn", segment, ["tier0_external"]),
15848
16053
  requirement2(
15849
16054
  "network.connect",
15850
16055
  "network.connect",
15851
16056
  {
15852
16057
  kind: "network",
15853
- host: "unknown",
15854
- protocol: "database",
15855
- mode: "ambiguous",
16058
+ host: "control-plane",
16059
+ protocol: head,
16060
+ mode: "mutate",
15856
16061
  payload: "present"
15857
16062
  },
15858
16063
  segment,
15859
- ["prisma.database_endpoint_unknown"]
15860
- ),
15861
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
15862
- "prisma.database_endpoint_unknown"
15863
- ])
16064
+ ["tier0_external"]
16065
+ )
15864
16066
  ];
15865
16067
  }
15866
- const network = requirement2(
15867
- "network.connect",
15868
- "network.connect",
15869
- {
15870
- kind: "network",
15871
- ...endpoint,
15872
- mode: "mutate",
15873
- payload: "present"
15874
- },
15875
- segment,
15876
- [LOOPBACK_HOSTS2.has(endpoint.host) ? "prisma.database_local" : "prisma.database_remote"]
15877
- );
15878
- if (LOOPBACK_HOSTS2.has(endpoint.host)) {
15879
- return [processEffect, network];
15880
- }
15881
- return [
15882
- processEffect,
15883
- network,
15884
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
15885
- "prisma.remote_database_mutation"
15886
- ])
15887
- ];
15888
- }
15889
- function addRedirectEffects(requirements, tokens, env, context, segment) {
15890
- for (let index = 0; index < tokens.length; index += 1) {
15891
- const operator = tokens[index] ?? "";
15892
- if (!isRedirectOperator(operator) || isFdDuplication(operator)) {
15893
- continue;
15894
- }
15895
- const rawTarget = tokens[index + 1];
15896
- if (!rawTarget) {
15897
- requirements.push(
15898
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
15899
- "shell.redirect_target_missing"
15900
- ])
15901
- );
15902
- continue;
15903
- }
15904
- if (operator.includes("<<")) {
15905
- requirements.push(
15906
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
15907
- "shell.heredoc_opaque"
15908
- ])
15909
- );
15910
- index += 1;
15911
- continue;
15912
- }
15913
- const target = expandKnownVariables(rawTarget, env);
15914
- const resolved = resolvePathOperand(target, context.cwd);
15915
- if (resolved === "/dev/null") {
15916
- index += 1;
15917
- continue;
15918
- }
15919
- if (operator.includes("<")) {
15920
- requirements.push(
15921
- requirement2("fs.read", "fs.read", { kind: "path", path: resolved }, segment, [
15922
- "shell.input_redirect"
15923
- ])
15924
- );
15925
- addSecretRead(requirements, resolved, segment);
15926
- }
15927
- if (operator.includes(">")) {
15928
- addWriteEffects(requirements, resolved, segment, ["shell.output_redirect"]);
15929
- }
15930
- index += 1;
15931
- }
15932
- }
15933
- function addSubstitutionEffects(requirements, command, context, segment, signals) {
15934
- const inners = structuralSubstitutionInners(command);
15935
- for (const inner of inners) {
15936
- signals.add("command_substitution");
15937
- if (context.depth >= MAX_LOWER_DEPTH) {
15938
- requirements.push(
15939
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
15940
- "shell.substitution_depth_exceeded"
15941
- ])
15942
- );
15943
- continue;
15944
- }
15945
- const nestedSegments = lowerTopLevelSegments(inner, {
15946
- ...context,
15947
- command: inner,
15948
- depth: context.depth + 1
15949
- });
15950
- for (const nested of nestedSegments) {
15951
- requirements.push(
15952
- ...nested.requirements.map((entry) => {
15953
- const outerProvenance = {
15954
- segment,
15955
- innerCommand: inner
15956
- };
15957
- const provenance = {
15958
- ...entry.provenance,
15959
- segment,
15960
- innerCommand: entry.provenance.innerCommand ?? inner
15961
- };
15962
- return withProvenances(entry, provenance, outerProvenance);
15963
- })
15964
- );
15965
- }
15966
- }
15967
- }
15968
- function extractEnvironment(tokens, inherited) {
15969
- const env = { ...inherited };
15970
- const changedNames = /* @__PURE__ */ new Set();
15971
- if (tokens[0] === "env") {
15972
- let index = 1;
15973
- let malformed = false;
15974
- while (index < tokens.length) {
15975
- const option = tokens[index] ?? "";
15976
- if (option === "-u" || option === "--unset") {
15977
- const name = tokens[index + 1] ?? "";
15978
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
15979
- malformed = true;
15980
- index += 1;
15981
- break;
15982
- }
15983
- delete env[name];
15984
- changedNames.add(name);
15985
- index += 2;
15986
- continue;
15987
- }
15988
- if (option.startsWith("--unset=")) {
15989
- const name = option.slice("--unset=".length);
15990
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
15991
- malformed = true;
15992
- index += 1;
15993
- break;
15994
- }
15995
- delete env[name];
15996
- changedNames.add(name);
15997
- index += 1;
15998
- continue;
15999
- }
16000
- if (option === "-i" || option === "--ignore-environment") {
16001
- for (const name of Object.keys(env)) {
16002
- delete env[name];
16003
- }
16004
- changedNames.add("*");
16005
- index += 1;
16006
- continue;
16007
- }
16008
- if (option === "--") {
16009
- index += 1;
16010
- break;
16011
- }
16012
- if (option.startsWith("-")) {
16013
- malformed = true;
16014
- index += 1;
16015
- break;
16016
- }
16017
- break;
16018
- }
16019
- while (index < tokens.length) {
16020
- const token = tokens[index] ?? "";
16021
- const match = ENV_PREFIX_PATTERN2.exec(token);
16022
- if (!match) {
16023
- break;
16024
- }
16025
- const [, name, value] = match;
16026
- if (name) {
16027
- env[name] = expandKnownVariables(value ?? "", env);
16028
- changedNames.add(name);
16029
- }
16030
- index += 1;
16031
- }
16032
- const commandTokens = tokens.slice(index);
16033
- return {
16034
- env,
16035
- commandTokens,
16036
- malformed: malformed || commandTokens.length === 0,
16037
- changedNames
16038
- };
16039
- }
16040
- for (const token of tokens) {
16041
- const match = ENV_PREFIX_PATTERN2.exec(token);
16042
- if (!match) {
16043
- break;
16068
+ if (head === "cd") {
16069
+ return [];
16070
+ }
16071
+ if (head === "cp" || head === "mv") {
16072
+ return decodeCopyMove(head, args, cwd, segment);
16073
+ }
16074
+ if (head === "rm") {
16075
+ return decodeRm(args, cwd, repoRoot, segment);
16076
+ }
16077
+ if (head === "vitest" || head === "eslint" || head === "biome" || head === "belay") {
16078
+ return [processRequirement(head, "spawn", segment, ["process.known_local_spawn"])];
16079
+ }
16080
+ if (head === "pwd" || head === "which" || head === "whoami") {
16081
+ return [processRequirement(head, "inspect", segment, ["process.pure_inspection"])];
16082
+ }
16083
+ const readOperands = filesystemReadOperands(head, args);
16084
+ if (readOperands !== null) {
16085
+ const lowered = [processRequirement(head, "inspect", segment, ["process.filesystem_inspect"])];
16086
+ for (const operand of readOperands) {
16087
+ const resolved = resolvePathOperand(operand, cwd);
16088
+ lowered.push(
16089
+ requirement2("fs.read", "fs.read", { kind: "path", path: resolved }, segment, [
16090
+ "filesystem.read"
16091
+ ])
16092
+ );
16093
+ addSecretRead(lowered, resolved, segment);
16044
16094
  }
16045
- const [, name, value] = match;
16046
- if (name) {
16047
- env[name] = expandKnownVariables(value ?? "", env);
16048
- changedNames.add(name);
16095
+ return lowered;
16096
+ }
16097
+ const writeOperands = filesystemWriteOperands(head, args);
16098
+ if (writeOperands !== null) {
16099
+ const lowered = [processRequirement(head, "spawn", segment, ["process.filesystem_mutation"])];
16100
+ for (const operand of writeOperands) {
16101
+ addWriteEffects(lowered, resolvePathOperand(operand, cwd), segment, ["filesystem.write"]);
16049
16102
  }
16103
+ return lowered;
16050
16104
  }
16051
- return { env, malformed: false, changedNames };
16105
+ if (head === "printf" || head === "echo" || head === "true" || head === "false" || head === ":") {
16106
+ return [];
16107
+ }
16108
+ if (!head) {
16109
+ return [
16110
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
16111
+ "shell.segment_empty"
16112
+ ])
16113
+ ];
16114
+ }
16115
+ return unsupportedProcess(head, segment, "process.grammar_unknown");
16052
16116
  }
16053
- function expandKnownVariables(token, env) {
16054
- return token.replace(
16055
- /\$(?:\{([A-Za-z_][A-Za-z0-9_]*)\}|([A-Za-z_][A-Za-z0-9_]*))/g,
16056
- (raw, a, b) => {
16057
- const value = env[a ?? b];
16058
- return value === void 0 ? raw : value;
16117
+
16118
+ // src/core/effect-ir/shell-lower/decoders/decode-package-exec.ts
16119
+ function decodePackageExec(peel, cwd, repoRoot, segment) {
16120
+ if (peel.opaque) {
16121
+ return [
16122
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
16123
+ peel.reason,
16124
+ ...peel.signals
16125
+ ])
16126
+ ];
16127
+ }
16128
+ if (peel.reason === "npx_wrapper_readonly") {
16129
+ return [];
16130
+ }
16131
+ const binHead = peel.innerTokens[0] ?? "";
16132
+ const metadataOnly = packageExecInnerIsMetadata(peel);
16133
+ const local = !peel.forceAcquire ? resolveLocalBin(binHead, cwd, repoRoot) : null;
16134
+ if (local) {
16135
+ return metadataOnly ? [
16136
+ requirement2(
16137
+ "process.exec",
16138
+ "process.exec",
16139
+ { kind: "executable", command: local.path, operation: "inspect" },
16140
+ segment,
16141
+ ["package_exec.delegate", "package_exec.local_bin_resolved"]
16142
+ )
16143
+ ] : [];
16144
+ }
16145
+ const networkResources = peel.acquisitionSpecs.map((spec) => classifyPackageAcquisitionSpec(spec)).filter((source) => source.kind !== "local").map(
16146
+ (source) => source.kind === "registry" ? {
16147
+ kind: "network",
16148
+ host: "registry.npmjs.org",
16149
+ protocol: "registry",
16150
+ mode: "read",
16151
+ payload: "none"
16152
+ } : {
16153
+ kind: "network",
16154
+ host: source.host,
16155
+ ...source.port ? { port: source.port } : {},
16156
+ protocol: source.protocol,
16157
+ mode: "read",
16158
+ payload: "none"
16059
16159
  }
16060
16160
  );
16061
- }
16062
- function stripRedirects(tokens) {
16063
- const stripped = [];
16064
- for (let index = 0; index < tokens.length; index += 1) {
16065
- const token = tokens[index] ?? "";
16066
- if (isFdDuplication(token)) {
16067
- continue;
16068
- }
16069
- if (!isRedirectOperator(token)) {
16070
- stripped.push(token);
16071
- continue;
16072
- }
16073
- index += 1;
16161
+ if (networkResources.length === 0) {
16162
+ networkResources.push({
16163
+ kind: "network",
16164
+ host: "registry.npmjs.org",
16165
+ protocol: "registry",
16166
+ mode: "read",
16167
+ payload: "none"
16168
+ });
16074
16169
  }
16075
- return stripped;
16170
+ return [
16171
+ ...networkResources.map(
16172
+ (resource) => requirement2("network.acquire", "network.connect", resource, segment, [
16173
+ "package_acquire_possible"
16174
+ ])
16175
+ ),
16176
+ requirement2(
16177
+ "fs.write",
16178
+ "fs.write",
16179
+ { kind: "package-cache", manager: peel.launcher === "pnpm" ? "pnpm" : "npm" },
16180
+ segment,
16181
+ ["package_cache_write"]
16182
+ ),
16183
+ ...metadataOnly ? [
16184
+ requirement2(
16185
+ "process.exec",
16186
+ "process.exec",
16187
+ { kind: "executable", command: binHead || peel.launcher, operation: "inspect" },
16188
+ segment,
16189
+ ["package_exec.delegate"]
16190
+ )
16191
+ ] : []
16192
+ ];
16076
16193
  }
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;
16088
- }
16089
- index += 1;
16194
+ function packageExecInnerIsMetadata(peel) {
16195
+ const args = peel.innerTokens.slice(1);
16196
+ return args.length > 0 && args.every((arg) => ["--help", "--version", "-V", "-h", "-v"].includes(arg));
16197
+ }
16198
+
16199
+ // src/core/effect-ir/shell-lower/segment.ts
16200
+ init_shell_tokenizer();
16201
+ import path47 from "node:path";
16202
+ var DYNAMIC_SHELL_VALUE_PATTERN = /(?:\$\(|`|\$(?:\d+|[@*#?$!-]|\{[^}]*\}|[A-Za-z_][A-Za-z0-9_]*))/;
16203
+ var SHELL_GLOB_PATTERN = /[*?[]/;
16204
+ function requiresKnownCwd(requirementValue) {
16205
+ if (requirementValue.action === "fs.write" || requirementValue.action === "git.ref.write" || requirementValue.action === "control_plane.write") {
16206
+ return true;
16090
16207
  }
16091
- return stripped;
16208
+ return requirementValue.action === "process.exec" && requirementValue.resource.kind === "executable" && requirementValue.resource.operation === "spawn";
16092
16209
  }
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
- }
16210
+ function joinNestedOpacity(outer, nested) {
16211
+ const nestedOpacity = nested.completeness === "partial" && nested.opacity === "transparent" ? "recursive" : nested.opacity;
16212
+ return joinEffectOpacity(outer, nestedOpacity);
16213
+ }
16214
+ function startsLocalPostgresService(command) {
16215
+ const tokens = tokenizeShell(command);
16216
+ return path47.basename(tokens[0] ?? "") === "docker" && tokens[1] === "compose" && ["up", "start", "restart"].includes(tokens[2] ?? "") && tokens.includes("postgres");
16217
+ }
16218
+ function resolveCdTransition(command, currentCwd) {
16219
+ const tokens = tokenizeShell(command);
16220
+ if (path47.basename(tokens[0] ?? "") !== "cd") {
16221
+ return null;
16100
16222
  }
16101
- return [];
16223
+ const target = tokens[1] ?? "~";
16224
+ if (!target || target === "-" || target.includes("$") || target.includes("`")) {
16225
+ return { cwd: currentCwd, known: false };
16226
+ }
16227
+ return { cwd: resolvePathOperand(target, currentCwd), known: true };
16102
16228
  }
16103
16229
  function shellSegment(commandRedacted, segmentHead, requirements, opacity, signals) {
16104
16230
  const normalizedRequirements = requirements.flatMap((entry) => {
@@ -16115,69 +16241,19 @@ function shellSegment(commandRedacted, segmentHead, requirements, opacity, signa
16115
16241
  )
16116
16242
  ];
16117
16243
  }
16118
- return [entry];
16119
- });
16120
- const partial = normalizedRequirements.some(
16121
- (entry) => entry.tag === "indeterminate" || entry.action === "indeterminate"
16122
- );
16123
- return {
16124
- commandRedacted,
16125
- segmentHead,
16126
- requirements: normalizedRequirements,
16127
- completeness: partial ? "partial" : "complete",
16128
- opacity,
16129
- signals: [...signals].sort()
16130
- };
16131
- }
16132
- var DYNAMIC_SHELL_VALUE_PATTERN = /(?:\$\(|`|\$(?:\d+|[@*#?$!-]|\{[^}]*\}|[A-Za-z_][A-Za-z0-9_]*))/;
16133
- var SHELL_GLOB_PATTERN = /[*?[]/;
16134
- function effectChangingEnvironmentSignals(head, env, changedNames) {
16135
- if (head === "curl" && (env.CURL_HOME || changedNames.has("CURL_HOME") || changedNames.has("HOME") || changedNames.has("XDG_CONFIG_HOME"))) {
16136
- return ["egress.curl.environment_config_override"];
16137
- }
16138
- if (head === "wget" && (env.WGETRC || changedNames.has("WGETRC") || changedNames.has("HOME"))) {
16139
- return ["egress.wget.environment_config_override"];
16140
- }
16141
- if (head !== "git") {
16142
- return [];
16143
- }
16144
- const hazardousNames = /* @__PURE__ */ new Set([
16145
- "GIT_ASKPASS",
16146
- "GIT_ALTERNATE_OBJECT_DIRECTORIES",
16147
- "GIT_CEILING_DIRECTORIES",
16148
- "GIT_COMMON_DIR",
16149
- "GIT_CONFIG",
16150
- "GIT_CONFIG_GLOBAL",
16151
- "GIT_CONFIG_SYSTEM",
16152
- "GIT_DIR",
16153
- "GIT_DISCOVERY_ACROSS_FILESYSTEM",
16154
- "GIT_EDITOR",
16155
- "GIT_EXEC_PATH",
16156
- "GIT_EXTERNAL_DIFF",
16157
- "GIT_INDEX_FILE",
16158
- "GIT_NAMESPACE",
16159
- "GIT_OBJECT_DIRECTORY",
16160
- "GIT_PAGER",
16161
- "GIT_PROXY_COMMAND",
16162
- "GIT_QUARANTINE_PATH",
16163
- "GIT_SEQUENCE_EDITOR",
16164
- "GIT_SHALLOW_FILE",
16165
- "GIT_SSH",
16166
- "GIT_SSH_COMMAND",
16167
- "GIT_TEMPLATE_DIR",
16168
- "GIT_WORK_TREE",
16169
- "SSH_ASKPASS"
16170
- ]);
16171
- const overridden = changedNames.has("HOME") || changedNames.has("XDG_CONFIG_HOME") || Object.entries(env).some(([name, value]) => {
16172
- if (!value) {
16173
- return false;
16174
- }
16175
- if (hazardousNames.has(name) || /^GIT_CONFIG_(?:KEY|VALUE)_\d+$/.test(name) || /^GIT_TRACE/.test(name)) {
16176
- return true;
16177
- }
16178
- return name === "GIT_CONFIG_COUNT" && value !== "0";
16244
+ return [entry];
16179
16245
  });
16180
- return overridden ? ["git.environment_execution_or_config_override"] : [];
16246
+ const partial = normalizedRequirements.some(
16247
+ (entry) => entry.tag === "indeterminate" || entry.action === "indeterminate"
16248
+ );
16249
+ return {
16250
+ commandRedacted,
16251
+ segmentHead,
16252
+ requirements: normalizedRequirements,
16253
+ completeness: partial ? "partial" : "complete",
16254
+ opacity,
16255
+ signals: [...signals].sort()
16256
+ };
16181
16257
  }
16182
16258
  function dynamicResourceSignal(resource) {
16183
16259
  const expanded = (value) => Boolean(value && DYNAMIC_SHELL_VALUE_PATTERN.test(value));
@@ -16197,362 +16273,502 @@ function dynamicResourceSignal(resource) {
16197
16273
  return null;
16198
16274
  }
16199
16275
  }
16200
- function processRequirement(command, operation, segment, signals) {
16201
- return requirement2(
16202
- "process.exec",
16203
- "process.exec",
16204
- { kind: "executable", command, operation },
16205
- segment,
16206
- signals
16207
- );
16208
- }
16209
- function unsupportedProcess(command, segment, signal) {
16210
- return [
16211
- processRequirement(command, "spawn", segment, [signal]),
16212
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [signal])
16213
- ];
16276
+
16277
+ // src/core/effect-ir/shell-lower.ts
16278
+ var MAX_LOWER_DEPTH = 8;
16279
+ function lowerShellEffectPlan(params) {
16280
+ const context = { ...params, depth: 0 };
16281
+ const segments = lowerTopLevelSegments(params.command, context);
16282
+ return buildShellEffectPlan({
16283
+ inputFingerprint: params.inputFingerprint,
16284
+ segments,
16285
+ signals: pipeToShell(params.command) ? ["pipe_to_shell"] : []
16286
+ });
16214
16287
  }
16215
- function addWriteEffects(requirements, resolved, segment, signals) {
16216
- requirements.push(
16217
- requirement2("fs.write", "fs.write", { kind: "path", path: resolved }, segment, signals)
16218
- );
16219
- if (CONTROL_PLANE_PATH_PATTERN.test(resolved)) {
16220
- requirements.push(
16221
- requirement2(
16222
- "control_plane.write",
16223
- "control_plane.write",
16224
- { kind: "path", path: resolved },
16225
- segment,
16226
- [...signals, "protected_path"]
16288
+ function lowerTopLevelSegments(command, context) {
16289
+ const commands = splitStructuralShellSegments(command);
16290
+ if (commands.length === 0 && command.trim()) {
16291
+ commands.push(command.trim());
16292
+ }
16293
+ const pipedToShell = pipeToShell(command);
16294
+ const lowered = [];
16295
+ let cwd = context.cwd;
16296
+ let cwdKnown = true;
16297
+ let inferredEnv = { ...context.env };
16298
+ for (const segment of commands) {
16299
+ let result = lowerSegment(segment, {
16300
+ ...context,
16301
+ cwd,
16302
+ env: inferredEnv,
16303
+ command: segment,
16304
+ depth: context.depth,
16305
+ inputFingerprint: context.inputFingerprint,
16306
+ ...pipedToShell && isShellHead(parseSegment(segment).head) ? { pipeToShellSegment: true } : {}
16307
+ });
16308
+ if (!cwdKnown) {
16309
+ const requiresCwd = result.requirements.some(requiresKnownCwd);
16310
+ const requirements = [...result.requirements];
16311
+ if (requiresCwd) {
16312
+ requirements.push(
16313
+ requirement2(
16314
+ "indeterminate",
16315
+ "indeterminate",
16316
+ { kind: "unknown" },
16317
+ result.commandRedacted,
16318
+ ["shell.cwd_unknown"]
16319
+ )
16320
+ );
16321
+ }
16322
+ result = {
16323
+ ...result,
16324
+ requirements,
16325
+ ...requiresCwd ? {
16326
+ completeness: "partial",
16327
+ opacity: joinEffectOpacity(result.opacity, "opaque")
16328
+ } : {},
16329
+ signals: [.../* @__PURE__ */ new Set([...result.signals, "shell.cwd_unknown"])]
16330
+ };
16331
+ }
16332
+ lowered.push(result);
16333
+ if (!inferredEnv.DATABASE_URL && startsLocalPostgresService(segment)) {
16334
+ inferredEnv = {
16335
+ ...inferredEnv,
16336
+ DATABASE_URL: "postgresql://127.0.0.1:5432/local"
16337
+ };
16338
+ }
16339
+ const nextCwd = resolveCdTransition(segment, cwd);
16340
+ if (nextCwd) {
16341
+ cwd = nextCwd.cwd;
16342
+ cwdKnown = nextCwd.known;
16343
+ }
16344
+ }
16345
+ if (commands.length > 1 && /[\r\n]/.test(command) && lowered.some(
16346
+ (segment) => segment.requirements.some((entry) => entry.action === "network.connect")
16347
+ )) {
16348
+ lowered.push(
16349
+ shellSegment(
16350
+ "[multiline network boundary]",
16351
+ "shell-boundary",
16352
+ [
16353
+ requirement2(
16354
+ "indeterminate",
16355
+ "indeterminate",
16356
+ { kind: "unknown" },
16357
+ "[multiline network boundary]",
16358
+ ["shell.multiline_network_boundary"]
16359
+ )
16360
+ ],
16361
+ "opaque",
16362
+ /* @__PURE__ */ new Set(["shell.multiline_network_boundary"])
16227
16363
  )
16228
16364
  );
16229
16365
  }
16366
+ return lowered;
16230
16367
  }
16231
- function addSecretRead(requirements, resolved, segment) {
16232
- if (SECRET_PATH_PATTERN.test(resolved)) {
16368
+ function lowerSegment(command, context) {
16369
+ const commandRedacted = redactCommand(command);
16370
+ const lexed = lexShell(command);
16371
+ const rawTokens = lexed.tokens.map((token) => token.value);
16372
+ const environment = extractEnvironment(rawTokens, context.env);
16373
+ const env = environment.env;
16374
+ const parsed = parseSegment(command);
16375
+ const parsedTokens = environment.commandTokens ?? parsed.tokens;
16376
+ const tokens = stripRedirects(
16377
+ parsedTokens.length === 0 && rawTokens.length > 0 && rawTokens.every((token) => ENV_PREFIX_PATTERN2.test(token)) ? rawTokens : parsedTokens
16378
+ ).map((token) => expandKnownVariables(token, env));
16379
+ const decoderTokens = alignStructuredTokens(
16380
+ stripStructuredRedirects(lexed.tokens),
16381
+ stripRedirects(parsedTokens)
16382
+ );
16383
+ const head = path48.basename(tokens[0] ?? parsed.head);
16384
+ let opacity = segmentOpacity(command);
16385
+ const signals = /* @__PURE__ */ new Set();
16386
+ const requirements = [];
16387
+ if (!lexed.complete) {
16233
16388
  requirements.push(
16234
- requirement2("secret.read", "secret.read", { kind: "path", path: resolved }, segment, [
16235
- "secret_path_read"
16389
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
16390
+ "shell.grammar_incomplete"
16236
16391
  ])
16237
16392
  );
16393
+ signals.add("shell.grammar_incomplete");
16394
+ opacity = joinEffectOpacity(opacity, "unparseable");
16238
16395
  }
16239
- }
16240
- function filesystemReadOperands(head, args) {
16241
- switch (head) {
16242
- case "ls": {
16243
- const operands = args.filter((arg) => arg && !arg.startsWith("-"));
16244
- return operands.length > 0 ? operands : ["."];
16245
- }
16246
- case "find":
16247
- return findReadOperands(args);
16248
- case "cat":
16249
- case "head":
16250
- case "tail":
16251
- case "less":
16252
- case "stat":
16253
- case "file":
16254
- case "du":
16255
- case "wc":
16256
- return args.filter((arg) => arg && !arg.startsWith("-"));
16257
- case "grep":
16258
- case "rg":
16259
- return args.slice(1).filter((arg) => arg && !arg.startsWith("-"));
16260
- case "jq":
16261
- return args.slice(1).filter((arg) => arg && !arg.startsWith("-"));
16262
- default:
16263
- return null;
16264
- }
16265
- }
16266
- function findReadOperands(args) {
16267
- const mutatingPrimaries = /* @__PURE__ */ new Set([
16268
- "-delete",
16269
- "-exec",
16270
- "-execdir",
16271
- "-fls",
16272
- "-fprint",
16273
- "-fprintf",
16274
- "-ok",
16275
- "-okdir"
16276
- ]);
16277
- if (args.some(
16278
- (arg) => mutatingPrimaries.has(arg) || [...mutatingPrimaries].some((primary) => arg.startsWith(`${primary}=`))
16279
- )) {
16280
- return null;
16281
- }
16282
- const paths = [];
16283
- for (const arg of args) {
16284
- if (arg.startsWith("-") || arg === "!" || arg === "(") {
16285
- break;
16286
- }
16287
- paths.push(arg);
16288
- }
16289
- return paths.length > 0 ? paths : ["."];
16290
- }
16291
- function filesystemWriteOperands(head, args) {
16292
- const positional = args.filter((arg) => arg && !arg.startsWith("-"));
16293
- switch (head) {
16294
- case "touch":
16295
- case "mkdir":
16296
- case "rm":
16297
- case "truncate":
16298
- case "chmod":
16299
- return positional;
16300
- case "cp":
16301
- case "mv":
16302
- return positional.length > 0 ? [positional[positional.length - 1] ?? ""] : [];
16303
- case "tee":
16304
- return positional;
16305
- default:
16306
- return null;
16396
+ addRedirectEffects(requirements, rawTokens, env, context, commandRedacted);
16397
+ addSubstitutionEffects(requirements, command, context, commandRedacted, signals);
16398
+ if (environment.malformed) {
16399
+ requirements.push(
16400
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
16401
+ "shell.env_wrapper_incomplete"
16402
+ ])
16403
+ );
16404
+ opacity = joinEffectOpacity(opacity, "opaque");
16307
16405
  }
16308
- }
16309
- function validLsof(args) {
16310
- const flagsWithoutValues = /* @__PURE__ */ new Set(["-a", "-b", "-l", "-n", "-P", "-R", "-t", "-V"]);
16311
- const flagsWithValues = /* @__PURE__ */ new Set(["-c", "-d", "-F", "-g", "-p", "-T", "-u", "+d", "+D"]);
16312
- for (let index = 0; index < args.length; index += 1) {
16313
- const arg = args[index] ?? "";
16314
- if (flagsWithoutValues.has(arg) || arg === "-i") {
16315
- continue;
16316
- }
16317
- if (flagsWithValues.has(arg)) {
16318
- if (!args[index + 1] || isOptionToken(args[index + 1] ?? "")) {
16319
- return false;
16320
- }
16321
- index += 1;
16322
- continue;
16323
- }
16324
- if (/^-[ablnPRtV]+$/.test(arg) && [...arg.slice(1)].every((flag) => "ablnPRtV".includes(flag)) || /^-i(?:TCP|UDP)?(?::\d+(?:-\d+)?)?$/.test(arg) || /^-s(?:TCP|UDP):[A-Za-z]+$/.test(arg) || /^-(?:c|d|F|g|p|T|u).+$/.test(arg) || /^\+(?:d|D).+$/.test(arg)) {
16325
- continue;
16326
- }
16327
- return false;
16406
+ if (parsed.encounteredXargs) {
16407
+ requirements.push(
16408
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
16409
+ "shell.xargs_stdin_dynamic"
16410
+ ])
16411
+ );
16412
+ signals.add("shell.xargs_stdin_dynamic");
16413
+ opacity = joinEffectOpacity(opacity, "opaque");
16328
16414
  }
16329
- return true;
16330
- }
16331
- function validPs(args) {
16332
- const bareForms = /* @__PURE__ */ new Set(["aux", "ax", "x", "u"]);
16333
- const flagsWithoutValues = /* @__PURE__ */ new Set(["-A", "-a", "-d", "-e", "-f", "-j", "-l", "-T", "-x"]);
16334
- const flagsWithValues = /* @__PURE__ */ new Set([
16335
- "--format",
16336
- "--group",
16337
- "--pid",
16338
- "--ppid",
16339
- "--sort",
16340
- "--tty",
16341
- "--user"
16342
- ]);
16343
- for (let index = 0; index < args.length; index += 1) {
16344
- const arg = args[index] ?? "";
16345
- if (bareForms.has(arg) || flagsWithoutValues.has(arg)) {
16346
- continue;
16347
- }
16348
- if (flagsWithValues.has(arg)) {
16349
- if (!args[index + 1] || isOptionToken(args[index + 1] ?? "")) {
16350
- return false;
16351
- }
16352
- index += 1;
16353
- continue;
16415
+ if (context.depth > MAX_LOWER_DEPTH) {
16416
+ requirements.push(
16417
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
16418
+ "shell.lower_depth_exceeded"
16419
+ ])
16420
+ );
16421
+ return shellSegment(
16422
+ commandRedacted,
16423
+ head,
16424
+ requirements,
16425
+ joinEffectOpacity(opacity, "opaque"),
16426
+ signals
16427
+ );
16428
+ }
16429
+ const packageExec = peelPackageExecArgv(tokens);
16430
+ if (packageExec) {
16431
+ requirements.push(
16432
+ ...decodePackageExec(packageExec, context.cwd, context.repoRoot, commandRedacted)
16433
+ );
16434
+ for (const signal of packageExec.signals) {
16435
+ signals.add(signal);
16354
16436
  }
16355
- if (/^(?:--format|--group|--pid|--ppid|--sort|--tty|--user)=.+$/.test(arg)) {
16356
- continue;
16437
+ if (packageExec.opaque) {
16438
+ opacity = joinEffectOpacity(opacity, "opaque");
16357
16439
  }
16358
- if (/^-[^-]/.test(arg)) {
16359
- const noValue = /* @__PURE__ */ new Set(["A", "a", "d", "e", "f", "j", "l", "T", "x"]);
16360
- const withValue = /* @__PURE__ */ new Set(["G", "g", "N", "o", "p", "t", "U", "u"]);
16361
- const characters = arg.slice(1);
16362
- let valid = true;
16363
- for (let optionIndex = 0; optionIndex < characters.length; optionIndex += 1) {
16364
- const option = characters[optionIndex] ?? "";
16365
- if (noValue.has(option)) {
16366
- continue;
16367
- }
16368
- if (withValue.has(option)) {
16369
- if (optionIndex === characters.length - 1) {
16370
- if (!args[index + 1] || isOptionToken(args[index + 1] ?? "")) {
16371
- return false;
16372
- }
16373
- index += 1;
16374
- }
16375
- break;
16440
+ const innerRecipe = packageExecInnerIsMetadata(packageExec) ? null : innerRecipeFromPeel(packageExec);
16441
+ if (innerRecipe) {
16442
+ const nested = lowerTopLevelSegments(innerRecipe, {
16443
+ ...context,
16444
+ command: innerRecipe,
16445
+ env,
16446
+ depth: context.depth + 1
16447
+ });
16448
+ for (const nestedSegment of nested) {
16449
+ requirements.push(
16450
+ ...nestedSegment.requirements.map(
16451
+ (entry) => withInnerProvenance(entry, innerRecipe, packageExec.launcher, commandRedacted)
16452
+ )
16453
+ );
16454
+ for (const signal of nestedSegment.signals) {
16455
+ signals.add(signal);
16376
16456
  }
16377
- valid = false;
16378
- break;
16379
- }
16380
- if (valid) {
16381
- continue;
16457
+ opacity = joinNestedOpacity(opacity, nestedSegment);
16382
16458
  }
16383
16459
  }
16384
- return false;
16385
- }
16386
- return true;
16387
- }
16388
- function validFilesystemTest(head, args) {
16389
- const operands = head === "[" && args.at(-1) === "]" ? args.slice(0, -1) : args;
16390
- if (head === "[" && args.at(-1) !== "]") {
16391
- return false;
16460
+ return shellSegment(commandRedacted, head, requirements, opacity, signals);
16392
16461
  }
16393
- return operands.length === 2 && (/* @__PURE__ */ new Set(["-b", "-c", "-d", "-e", "-f", "-h", "-L", "-r", "-s", "-w", "-x"])).has(
16394
- operands[0] ?? ""
16395
- ) && Boolean(operands[1]);
16396
- }
16397
- function validDockerInfo(args) {
16398
- for (let index = 0; index < args.length; index += 1) {
16399
- const arg = args[index] ?? "";
16400
- if (arg === "--help") {
16401
- continue;
16402
- }
16403
- if (arg === "--format" || arg === "-f") {
16404
- if (!args[index + 1] || isOptionToken(args[index + 1] ?? "")) {
16405
- return false;
16462
+ const recursive = decodeRecursiveInvocationTokens(decoderTokens);
16463
+ if (recursive.kind === "static" && opacity !== "opaque" && opacity !== "unparseable") {
16464
+ requirements.push(
16465
+ processRequirement(recursive.interpreter, "spawn", commandRedacted, [
16466
+ "shell.recursive_wrapper",
16467
+ "dynamic_shell_evaluation"
16468
+ ])
16469
+ );
16470
+ if (recursive.script !== "") {
16471
+ const nested = lowerTopLevelSegments(recursive.script, {
16472
+ ...context,
16473
+ command: recursive.script,
16474
+ env,
16475
+ depth: context.depth + 1
16476
+ });
16477
+ for (const nestedSegment of nested) {
16478
+ requirements.push(
16479
+ ...nestedSegment.requirements.map(
16480
+ (entry) => withInnerProvenance(entry, recursive.script, head, commandRedacted)
16481
+ )
16482
+ );
16483
+ for (const signal of nestedSegment.signals) signals.add(signal);
16406
16484
  }
16407
- index += 1;
16408
- continue;
16409
16485
  }
16410
- if (arg.startsWith("--format=")) {
16411
- if (arg.slice("--format=".length).length === 0) {
16412
- return false;
16486
+ signals.add("shell.recursive_wrapper");
16487
+ signals.add("dynamic_shell_evaluation");
16488
+ return shellSegment(commandRedacted, head, requirements, "recursive", signals);
16489
+ }
16490
+ if (recursive.kind === "dynamic" || recursive.kind === "indeterminate") {
16491
+ const recursiveSignals = [
16492
+ recursive.signal,
16493
+ ...recursive.kind === "dynamic" ? ["dynamic_shell_evaluation"] : []
16494
+ ];
16495
+ requirements.push(
16496
+ processRequirement(recursive.interpreter, "spawn", commandRedacted, recursiveSignals),
16497
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
16498
+ ...recursiveSignals
16499
+ ])
16500
+ );
16501
+ for (const signal of recursiveSignals) signals.add(signal);
16502
+ return shellSegment(
16503
+ commandRedacted,
16504
+ head,
16505
+ requirements,
16506
+ joinEffectOpacity(opacity, "opaque"),
16507
+ signals
16508
+ );
16509
+ }
16510
+ const compose = decodeDockerComposeRun(decoderTokens);
16511
+ if (compose.kind === "recursive" && opacity !== "opaque" && opacity !== "unparseable") {
16512
+ requirements.push(
16513
+ processRequirement(head, "spawn", commandRedacted, ["process.docker_compose_run"])
16514
+ );
16515
+ if (compose.script !== "") {
16516
+ const nested = lowerTopLevelSegments(compose.script, {
16517
+ ...context,
16518
+ command: compose.script,
16519
+ env,
16520
+ depth: context.depth + 1
16521
+ });
16522
+ for (const nestedSegment of nested) {
16523
+ requirements.push(
16524
+ ...nestedSegment.requirements.map(
16525
+ (entry) => withInnerProvenance(entry, compose.script, head, commandRedacted)
16526
+ )
16527
+ );
16528
+ for (const signal of nestedSegment.signals) signals.add(signal);
16529
+ opacity = joinNestedOpacity(opacity, nestedSegment);
16413
16530
  }
16414
- continue;
16415
16531
  }
16416
- return false;
16532
+ signals.add("process.docker_compose_run");
16533
+ return shellSegment(commandRedacted, head, requirements, "recursive", signals);
16417
16534
  }
16418
- return true;
16419
- }
16420
- function bashSyntaxTarget(args) {
16421
- let syntaxOnly = false;
16422
- let script = null;
16423
- const shortFlags = new Set("abefhkmnptuvxBCEHPT".split(""));
16424
- const longFlags = /* @__PURE__ */ new Set([
16425
- "--debugger",
16426
- "--dump-po-strings",
16427
- "--dump-strings",
16428
- "--help",
16429
- "--login",
16430
- "--noediting",
16431
- "--noprofile",
16432
- "--norc",
16433
- "--posix",
16434
- "--restricted",
16435
- "--verbose",
16436
- "--version"
16437
- ]);
16438
- for (let index = 0; index < args.length; index += 1) {
16439
- const arg = args[index] ?? "";
16440
- if (arg === "-c" || arg === "-lc") {
16441
- return null;
16442
- }
16443
- if (arg === "-O" || arg === "+O" || arg === "--rcfile" || arg === "--init-file") {
16444
- if (!args[index + 1] || isOptionToken(args[index + 1] ?? "")) {
16445
- return null;
16535
+ if (compose.kind === "dynamic" || compose.kind === "indeterminate") {
16536
+ requirements.push(
16537
+ processRequirement(head, "spawn", commandRedacted, ["process.docker_compose_run"]),
16538
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
16539
+ compose.signal
16540
+ ])
16541
+ );
16542
+ signals.add("process.docker_compose_run");
16543
+ signals.add(compose.signal);
16544
+ return shellSegment(
16545
+ commandRedacted,
16546
+ head,
16547
+ requirements,
16548
+ joinEffectOpacity(opacity, "opaque"),
16549
+ signals
16550
+ );
16551
+ }
16552
+ const launcher = resolveLauncherRecipe({
16553
+ tokens,
16554
+ cwd: context.cwd,
16555
+ repoRoot: context.repoRoot,
16556
+ depth: context.depth
16557
+ });
16558
+ if (launcher) {
16559
+ signals.add(`launcher.${launcher.reason}`);
16560
+ requirements.push(
16561
+ requirement2(
16562
+ "process.exec",
16563
+ "process.exec",
16564
+ { kind: "executable", command: head, operation: "spawn" },
16565
+ commandRedacted,
16566
+ ["launcher.invoke"]
16567
+ )
16568
+ );
16569
+ for (const recipe of launcher.recipes) {
16570
+ const nested = lowerTopLevelSegments(recipe, {
16571
+ ...context,
16572
+ command: recipe,
16573
+ env,
16574
+ depth: context.depth + 1
16575
+ });
16576
+ for (const nestedSegment of nested) {
16577
+ requirements.push(
16578
+ ...nestedSegment.requirements.map(
16579
+ (entry) => withInnerProvenance(entry, recipe, head, commandRedacted)
16580
+ )
16581
+ );
16582
+ for (const signal of nestedSegment.signals) {
16583
+ signals.add(signal);
16584
+ }
16585
+ opacity = joinNestedOpacity(opacity, nestedSegment);
16446
16586
  }
16447
- index += 1;
16448
- continue;
16449
16587
  }
16450
- if (arg.startsWith("-O") && arg.length > 2 || arg.startsWith("+O") && arg.length > 2 || arg.startsWith("--rcfile=") && arg.length > "--rcfile=".length || arg.startsWith("--init-file=") && arg.length > "--init-file=".length) {
16451
- continue;
16588
+ if (launcher.opaque || launcher.recipes.length === 0) {
16589
+ requirements.push(
16590
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
16591
+ `launcher.${launcher.reason}`
16592
+ ])
16593
+ );
16594
+ opacity = joinEffectOpacity(opacity, "opaque");
16452
16595
  }
16453
- if (longFlags.has(arg)) {
16454
- continue;
16596
+ return shellSegment(commandRedacted, head, requirements, opacity, signals);
16597
+ }
16598
+ const egress = decodeEgressEffects({
16599
+ tokens,
16600
+ cwd: context.cwd,
16601
+ segment: commandRedacted
16602
+ });
16603
+ if (egress) {
16604
+ requirements.push(...egress);
16605
+ for (const signal of egress.flatMap((entry) => entry.evidence.signals)) {
16606
+ signals.add(signal);
16455
16607
  }
16456
- if (/^[+-][A-Za-z]+$/.test(arg)) {
16457
- const options = arg.slice(1);
16458
- if ([...options].some((option) => !shortFlags.has(option))) {
16459
- return null;
16608
+ } else {
16609
+ const git = decodeGitEffects({
16610
+ tokens,
16611
+ cwd: context.cwd,
16612
+ repoRoot: context.repoRoot,
16613
+ segment: commandRedacted
16614
+ });
16615
+ if (git) {
16616
+ requirements.push(...git);
16617
+ for (const signal of git.flatMap((entry) => entry.evidence.signals)) {
16618
+ signals.add(signal);
16460
16619
  }
16461
- syntaxOnly ||= options.includes("n");
16462
- continue;
16463
- }
16464
- if (arg === "--") {
16465
- script = args[index + 1] ?? null;
16466
- break;
16467
- }
16468
- if (arg.startsWith("-") || arg.startsWith("+")) {
16469
- return null;
16620
+ } else {
16621
+ const processRequirements = decodeProcessOrFilesystem({
16622
+ tokens,
16623
+ head,
16624
+ env,
16625
+ cwd: context.cwd,
16626
+ repoRoot: context.repoRoot,
16627
+ segment: commandRedacted
16628
+ });
16629
+ if (isGrammarUnknownOnly(processRequirements, head)) {
16630
+ const argvDelegate = peelArgvDelegateArgv(tokens);
16631
+ if (argvDelegate && !argvDelegate.opaque && shouldApplyArgvDelegate(head, argvDelegate.innerTokens, context.depth)) {
16632
+ const innerRecipe = innerRecipeFromArgvDelegate(argvDelegate);
16633
+ if (innerRecipe) {
16634
+ requirements.push(
16635
+ processRequirement(head, "inspect", commandRedacted, [
16636
+ "process.argv_delegate",
16637
+ ...argvDelegate.signals
16638
+ ])
16639
+ );
16640
+ for (const signal of argvDelegate.signals) {
16641
+ signals.add(signal);
16642
+ }
16643
+ const nested = lowerTopLevelSegments(innerRecipe, {
16644
+ ...context,
16645
+ command: innerRecipe,
16646
+ env,
16647
+ depth: context.depth + 1
16648
+ });
16649
+ for (const nestedSegment of nested) {
16650
+ requirements.push(
16651
+ ...nestedSegment.requirements.map(
16652
+ (entry) => withInnerProvenance(entry, innerRecipe, head, commandRedacted)
16653
+ )
16654
+ );
16655
+ for (const signal of nestedSegment.signals) {
16656
+ signals.add(signal);
16657
+ }
16658
+ opacity = joinNestedOpacity(opacity, nestedSegment);
16659
+ }
16660
+ return shellSegment(commandRedacted, head, requirements, opacity, signals);
16661
+ }
16662
+ }
16663
+ if (argvDelegate?.opaque) {
16664
+ requirements.push(
16665
+ processRequirement(head, "spawn", commandRedacted, [
16666
+ "process.argv_delegate",
16667
+ argvDelegate.reason,
16668
+ ...argvDelegate.signals
16669
+ ]),
16670
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
16671
+ argvDelegate.reason,
16672
+ "process.argv_delegate_opaque"
16673
+ ])
16674
+ );
16675
+ for (const signal of argvDelegate.signals) {
16676
+ signals.add(signal);
16677
+ }
16678
+ signals.add("process.argv_delegate_opaque");
16679
+ return shellSegment(
16680
+ commandRedacted,
16681
+ head,
16682
+ requirements,
16683
+ joinEffectOpacity(opacity, "opaque"),
16684
+ signals
16685
+ );
16686
+ }
16687
+ }
16688
+ requirements.push(...processRequirements);
16470
16689
  }
16471
- script = arg;
16472
- break;
16473
- }
16474
- return syntaxOnly ? script : null;
16475
- }
16476
- function isOptionToken(value) {
16477
- return value.startsWith("-") || value.startsWith("+");
16478
- }
16479
- function databaseEndpoint(raw) {
16480
- if (!raw || raw.includes("$")) {
16481
- return null;
16482
16690
  }
16483
- try {
16484
- const url = new URL(raw);
16485
- if (!url.hostname || !url.protocol) {
16486
- return null;
16691
+ const environmentSignals = effectChangingEnvironmentSignals(head, env, environment.changedNames);
16692
+ if (environmentSignals.length > 0) {
16693
+ requirements.push(
16694
+ requirement2(
16695
+ "indeterminate",
16696
+ "indeterminate",
16697
+ { kind: "unknown" },
16698
+ commandRedacted,
16699
+ environmentSignals
16700
+ )
16701
+ );
16702
+ opacity = joinEffectOpacity(opacity, "opaque");
16703
+ for (const signal of environmentSignals) {
16704
+ signals.add(signal);
16487
16705
  }
16488
- return {
16489
- host: url.hostname.toLowerCase(),
16490
- protocol: url.protocol.slice(0, -1),
16491
- ...url.port ? { port: Number(url.port) } : {}
16492
- };
16493
- } catch {
16494
- return null;
16495
16706
  }
16496
- }
16497
- function resolvePathOperand(operand, cwd) {
16498
- if (operand === "~") {
16499
- return process.env.HOME ?? operand;
16707
+ if (context.pipeToShellSegment) {
16708
+ signals.add("pipe_to_shell");
16709
+ ensureRequirement(
16710
+ requirements,
16711
+ requirement2(
16712
+ "process.exec",
16713
+ "process.exec",
16714
+ { kind: "executable", command: head || "sh", operation: "spawn" },
16715
+ commandRedacted,
16716
+ ["pipe_to_shell"]
16717
+ )
16718
+ );
16719
+ ensureRequirement(
16720
+ requirements,
16721
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
16722
+ "pipe_to_shell"
16723
+ ])
16724
+ );
16725
+ opacity = joinEffectOpacity(opacity, "opaque");
16500
16726
  }
16501
- if (operand.startsWith("~/")) {
16502
- return path41.join(process.env.HOME ?? "~", operand.slice(2));
16727
+ if (opacity === "unparseable" || opacity === "opaque") {
16728
+ ensureRequirement(
16729
+ requirements,
16730
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
16731
+ `shell.${opacity}`
16732
+ ])
16733
+ );
16503
16734
  }
16504
- return path41.resolve(cwd, operand);
16505
- }
16506
- function isShellHead(head) {
16507
- return head === "bash" || head === "sh" || head === "zsh" || head === "dash" || head === "fish";
16508
- }
16509
- function pipeToShell(command) {
16510
- return /(?:^|[|;&]\s*)(?:bash|sh|zsh|dash|fish)(?:\s|$)/.test(command) && /\|/.test(command);
16511
- }
16512
- function withInnerProvenance(requirementValue, innerCommand, launcher, outerSegment) {
16513
- const provenance = {
16514
- ...requirementValue.provenance,
16515
- segment: outerSegment,
16516
- innerCommand: requirementValue.provenance.innerCommand ?? innerCommand,
16517
- ...launcher === "npm" || launcher === "pnpm" ? { launcher, phase: "delegate" } : {}
16518
- };
16519
- const launcherProvenance = {
16520
- segment: outerSegment,
16521
- innerCommand,
16522
- ...launcher === "npm" || launcher === "pnpm" ? { launcher, phase: "delegate" } : {}
16523
- };
16524
- return withProvenances(requirementValue, provenance, launcherProvenance);
16525
- }
16526
- function withProvenances(requirementValue, provenance, contributing) {
16527
- const provenances = [
16528
- ...requirementValue.provenances ?? [requirementValue.provenance],
16529
- contributing,
16530
- provenance
16531
- ].filter(
16532
- (candidate, index, all) => all.findIndex((entry) => JSON.stringify(entry) === JSON.stringify(candidate)) === index
16533
- );
16534
- return { ...requirementValue, provenance, provenances };
16735
+ return shellSegment(commandRedacted, head, requirements, opacity, signals);
16535
16736
  }
16536
- function ensureRequirement(requirements, candidate) {
16537
- if (!requirements.some(
16538
- (entry) => entry.action === candidate.action && JSON.stringify(entry.resource) === JSON.stringify(candidate.resource)
16539
- )) {
16540
- requirements.push(candidate);
16737
+ function addSubstitutionEffects(requirements, command, context, segment, signals) {
16738
+ const inners = structuralSubstitutionInners(command);
16739
+ for (const inner of inners) {
16740
+ signals.add("command_substitution");
16741
+ if (context.depth >= MAX_LOWER_DEPTH) {
16742
+ requirements.push(
16743
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
16744
+ "shell.substitution_depth_exceeded"
16745
+ ])
16746
+ );
16747
+ continue;
16748
+ }
16749
+ const nestedSegments = lowerTopLevelSegments(inner, {
16750
+ ...context,
16751
+ command: inner,
16752
+ depth: context.depth + 1
16753
+ });
16754
+ for (const nested of nestedSegments) {
16755
+ requirements.push(
16756
+ ...nested.requirements.map((entry) => {
16757
+ const outerProvenance = {
16758
+ segment,
16759
+ innerCommand: inner
16760
+ };
16761
+ const provenance = {
16762
+ ...entry.provenance,
16763
+ segment,
16764
+ innerCommand: entry.provenance.innerCommand ?? inner
16765
+ };
16766
+ return withProvenances(entry, provenance, outerProvenance);
16767
+ })
16768
+ );
16769
+ }
16541
16770
  }
16542
16771
  }
16543
- function requirement2(tag, action, resource, segment, signals) {
16544
- return {
16545
- tag,
16546
- action,
16547
- resource,
16548
- evidence: {
16549
- level: tag === "indeterminate" ? "indeterminate" : "certain",
16550
- signals: [...new Set(signals)].sort(),
16551
- basis: ["shell_semantic_lowering"]
16552
- },
16553
- provenance: { segment }
16554
- };
16555
- }
16556
16772
 
16557
16773
  // src/core/verdict/verdict.ts
16558
16774
  init_path_utils();
@@ -16772,6 +16988,13 @@ function policyAuth(options) {
16772
16988
  function scrubPayload(value, options) {
16773
16989
  return scrubValue(value, options.scrubOptions);
16774
16990
  }
16991
+ function fingerprintPayload(payload, options) {
16992
+ const toolInput = fingerprintToolInputSource(payload);
16993
+ if (Object.keys(toolInput).length > 0) {
16994
+ return toolInput;
16995
+ }
16996
+ return scrubPayload(payload.tool_input ?? {}, options);
16997
+ }
16775
16998
  function extractFilePath(payload) {
16776
16999
  const toolInput = payload.tool_input;
16777
17000
  if (!toolInput || typeof toolInput !== "object") {
@@ -16946,11 +17169,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
16946
17169
  verdict: "deny_pending_approval",
16947
17170
  reason: "tool_shell_missing_command",
16948
17171
  summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
16949
- fingerprint: toolFingerprint(
16950
- toolName,
16951
- scrubPayload(payload.tool_input ?? {}, options),
16952
- repoRoot
16953
- ),
17172
+ fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
16954
17173
  assessment: {
16955
17174
  reversibility: "irreversible",
16956
17175
  external: false,
@@ -16964,11 +17183,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
16964
17183
  verdict: "allow_flagged",
16965
17184
  reason: "tool_shell_missing_command",
16966
17185
  summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
16967
- fingerprint: toolFingerprint(
16968
- toolName,
16969
- scrubPayload(payload.tool_input ?? {}, options),
16970
- repoRoot
16971
- ),
17186
+ fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
16972
17187
  assessment: {
16973
17188
  reversibility: "recoverable_with_cost",
16974
17189
  external: false,
@@ -16992,11 +17207,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
16992
17207
  verdict: "deny_pending_approval",
16993
17208
  reason: "file_mutation_missing_path",
16994
17209
  summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
16995
- fingerprint: toolFingerprint(
16996
- toolName,
16997
- scrubPayload(payload.tool_input ?? {}, options),
16998
- repoRoot
16999
- ),
17210
+ fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17000
17211
  assessment: {
17001
17212
  reversibility: "irreversible",
17002
17213
  external: false,
@@ -17010,11 +17221,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17010
17221
  verdict: "allow_flagged",
17011
17222
  reason: "file_mutation_missing_path",
17012
17223
  summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17013
- fingerprint: toolFingerprint(
17014
- toolName,
17015
- scrubPayload(payload.tool_input ?? {}, options),
17016
- repoRoot
17017
- ),
17224
+ fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17018
17225
  assessment: {
17019
17226
  reversibility: "recoverable_with_cost",
17020
17227
  external: false,
@@ -17025,7 +17232,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17025
17232
  };
17026
17233
  }
17027
17234
  const signals = [];
17028
- const resolvedPath = path42.isAbsolute(filePath) ? filePath : path42.resolve(cwd, filePath);
17235
+ const resolvedPath = path49.isAbsolute(filePath) ? filePath : path49.resolve(cwd, filePath);
17029
17236
  const hitsProtectedRoot = protectedRoots.some((root) => pathWithinRoot(root, resolvedPath));
17030
17237
  if (hitsProtectedRoot) {
17031
17238
  signals.push("control_plane_path");
@@ -17164,11 +17371,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17164
17371
  verdict: "deny_pending_approval",
17165
17372
  reason: "apply_patch_missing_path",
17166
17373
  summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17167
- fingerprint: toolFingerprint(
17168
- toolName,
17169
- scrubPayload(payload.tool_input ?? {}, options),
17170
- repoRoot
17171
- ),
17374
+ fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17172
17375
  assessment: {
17173
17376
  reversibility: "irreversible",
17174
17377
  external: false,
@@ -17182,11 +17385,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17182
17385
  verdict: "allow_flagged",
17183
17386
  reason: "apply_patch_missing_path",
17184
17387
  summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17185
- fingerprint: toolFingerprint(
17186
- toolName,
17187
- scrubPayload(payload.tool_input ?? {}, options),
17188
- repoRoot
17189
- ),
17388
+ fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17190
17389
  assessment: {
17191
17390
  reversibility: "recoverable_with_cost",
17192
17391
  external: false,
@@ -17217,11 +17416,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17217
17416
  verdict: "allow_flagged",
17218
17417
  reason: sawDelete ? "file_delete" : "file_mutation",
17219
17418
  summary: targets.map((target) => target.path).join(", "),
17220
- fingerprint: toolFingerprint(
17221
- toolName,
17222
- scrubPayload(payload.tool_input ?? {}, options),
17223
- repoRoot
17224
- ),
17419
+ fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17225
17420
  assessment: {
17226
17421
  reversibility: "recoverable_with_cost",
17227
17422
  external: false,
@@ -17235,11 +17430,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17235
17430
  verdict: "allow",
17236
17431
  reason: "unclassified_tool",
17237
17432
  summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17238
- fingerprint: toolFingerprint(
17239
- toolName,
17240
- scrubPayload(payload.tool_input ?? {}, options),
17241
- repoRoot
17242
- ),
17433
+ fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17243
17434
  assessment: {
17244
17435
  reversibility: "reversible",
17245
17436
  external: false,
@@ -17627,7 +17818,7 @@ function hashDecisionConfig(config) {
17627
17818
  init_fingerprint2();
17628
17819
 
17629
17820
  // src/version.ts
17630
- var PACKAGE_VERSION = "0.9.3";
17821
+ var PACKAGE_VERSION = "0.9.4";
17631
17822
 
17632
17823
  // src/runtime-provenance.ts
17633
17824
  function resolveRuntimeArtifactHash(artifactHash) {
@@ -17702,7 +17893,7 @@ init_path_utils();
17702
17893
  import { randomUUID as randomUUID5 } from "node:crypto";
17703
17894
  import { existsSync as existsSync15 } from "node:fs";
17704
17895
  import { mkdir as mkdir11, readdir as readdir3, readFile as readFile12, rename as rename3, rm as rm6 } from "node:fs/promises";
17705
- import path47 from "node:path";
17896
+ import path54 from "node:path";
17706
17897
 
17707
17898
  // src/core/recovery/artifact-store.ts
17708
17899
  init_fingerprint2();
@@ -17710,7 +17901,7 @@ init_path_utils();
17710
17901
  import { randomUUID as randomUUID4 } from "node:crypto";
17711
17902
  import { existsSync as existsSync13 } from "node:fs";
17712
17903
  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";
17713
- import path44 from "node:path";
17904
+ import path51 from "node:path";
17714
17905
 
17715
17906
  // src/core/recovery/snapshot-node.ts
17716
17907
  init_fingerprint2();
@@ -17730,25 +17921,25 @@ import {
17730
17921
  symlink as symlink3,
17731
17922
  writeFile as writeFile6
17732
17923
  } from "node:fs/promises";
17733
- import path43 from "node:path";
17924
+ import path50 from "node:path";
17734
17925
  var RECOVERY_UNSUPPORTED_FILE_KIND = "recovery_unsupported_file_kind";
17735
17926
  function validRecoveryRelativePath(relativePath) {
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}`);
17927
+ if (!relativePath || relativePath.includes("\0") || path50.isAbsolute(relativePath)) return false;
17928
+ const normalized = path50.normalize(relativePath);
17929
+ return normalized !== "." && normalized !== ".." && !normalized.startsWith(`..${path50.sep}`);
17739
17930
  }
17740
17931
  async function assertRecoverySafeTarget(resourceRoot, relativePath) {
17741
17932
  if (!validRecoveryRelativePath(relativePath)) throw new Error("recovery_path_escape");
17742
17933
  const root = canonicalPath(resourceRoot);
17743
- const target = path43.resolve(root, relativePath);
17744
- const relative = path43.relative(root, target);
17745
- if (relative === ".." || relative.startsWith(`..${path43.sep}`) || path43.isAbsolute(relative)) {
17934
+ const target = path50.resolve(root, relativePath);
17935
+ const relative = path50.relative(root, target);
17936
+ if (relative === ".." || relative.startsWith(`..${path50.sep}`) || path50.isAbsolute(relative)) {
17746
17937
  throw new Error("recovery_path_escape");
17747
17938
  }
17748
17939
  let current = root;
17749
- const parentParts = path43.relative(root, path43.dirname(target)).split(path43.sep).filter(Boolean);
17940
+ const parentParts = path50.relative(root, path50.dirname(target)).split(path50.sep).filter(Boolean);
17750
17941
  for (const part of parentParts) {
17751
- current = path43.join(current, part);
17942
+ current = path50.join(current, part);
17752
17943
  if (!existsSync12(current)) break;
17753
17944
  const info = await lstat6(current);
17754
17945
  if (info.isSymbolicLink()) throw new Error("recovery_symlink_escape");
@@ -17804,7 +17995,7 @@ async function captureRecoverySnapshot(filePath, options) {
17804
17995
  let blob;
17805
17996
  if (options?.blobDir) {
17806
17997
  await mkdir9(options.blobDir, { recursive: true, mode: 448 });
17807
- const blobPath = path43.join(options.blobDir, hash);
17998
+ const blobPath = path50.join(options.blobDir, hash);
17808
17999
  if (!existsSync12(blobPath)) {
17809
18000
  await writeFile6(blobPath, content, { mode: 384 });
17810
18001
  await fsyncPath(blobPath);
@@ -17859,7 +18050,7 @@ async function validateRecoverySnapshot(params) {
17859
18050
  if (record.blob !== `blobs/${record.hash}`) throw new Error(params.corruptReason);
17860
18051
  let content;
17861
18052
  try {
17862
- content = await readFile8(path43.join(params.artifactDir, record.blob));
18053
+ content = await readFile8(path50.join(params.artifactDir, record.blob));
17863
18054
  } catch {
17864
18055
  throw new Error(params.corruptReason);
17865
18056
  }
@@ -17887,13 +18078,13 @@ var RECOVERY_STATES = /* @__PURE__ */ new Set([
17887
18078
  ]);
17888
18079
  var STAGING_STALE_MS = 5 * 6e4;
17889
18080
  function checkpointsRoot(stateDir) {
17890
- return path44.join(stateDir, "recovery", "checkpoints");
18081
+ return path51.join(stateDir, "recovery", "checkpoints");
17891
18082
  }
17892
18083
  function checkpointDir(stateDir, checkpointId) {
17893
18084
  if (!/^cp_[a-f0-9]{24}$/.test(checkpointId)) {
17894
18085
  throw new Error("invalid_recovery_checkpoint_id");
17895
18086
  }
17896
- return path44.join(checkpointsRoot(stateDir), checkpointId);
18087
+ return path51.join(checkpointsRoot(stateDir), checkpointId);
17897
18088
  }
17898
18089
  async function fsyncPath2(filePath) {
17899
18090
  const handle = await open5(filePath, "r");
@@ -17904,13 +18095,13 @@ async function fsyncPath2(filePath) {
17904
18095
  }
17905
18096
  }
17906
18097
  async function atomicWriteJson(filePath, value) {
17907
- await mkdir10(path44.dirname(filePath), { recursive: true, mode: 448 });
18098
+ await mkdir10(path51.dirname(filePath), { recursive: true, mode: 448 });
17908
18099
  const temporary = `${filePath}.tmp-${randomUUID4()}`;
17909
18100
  await writeFile7(temporary, `${JSON.stringify(value, null, 2)}
17910
18101
  `, { mode: 384 });
17911
18102
  await fsyncPath2(temporary);
17912
18103
  await rename2(temporary, filePath);
17913
- await fsyncPath2(path44.dirname(filePath));
18104
+ await fsyncPath2(path51.dirname(filePath));
17914
18105
  }
17915
18106
  async function writeRecoveryState(artifactDir, state, manifestHash, detail) {
17916
18107
  const value = {
@@ -17920,13 +18111,13 @@ async function writeRecoveryState(artifactDir, state, manifestHash, detail) {
17920
18111
  manifestHash,
17921
18112
  ...detail ? { detail } : {}
17922
18113
  };
17923
- await atomicWriteJson(path44.join(artifactDir, "state.json"), value);
18114
+ await atomicWriteJson(path51.join(artifactDir, "state.json"), value);
17924
18115
  }
17925
18116
  async function directorySize(root) {
17926
18117
  if (!existsSync13(root)) return 0;
17927
18118
  let total = 0;
17928
18119
  for (const entry of await readdir2(root, { withFileTypes: true })) {
17929
- const entryPath = path44.join(root, entry.name);
18120
+ const entryPath = path51.join(root, entry.name);
17930
18121
  if (entry.isDirectory()) total += await directorySize(entryPath);
17931
18122
  else total += (await lstat7(entryPath)).size;
17932
18123
  }
@@ -17971,9 +18162,9 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
17971
18162
  let rawManifest;
17972
18163
  let state;
17973
18164
  try {
17974
- rawManifest = JSON.parse(await readFile9(path44.join(artifactDir, "manifest.json"), "utf8"));
18165
+ rawManifest = JSON.parse(await readFile9(path51.join(artifactDir, "manifest.json"), "utf8"));
17975
18166
  state = JSON.parse(
17976
- await readFile9(path44.join(artifactDir, "state.json"), "utf8")
18167
+ await readFile9(path51.join(artifactDir, "state.json"), "utf8")
17977
18168
  );
17978
18169
  } catch {
17979
18170
  throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
@@ -17989,10 +18180,10 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
17989
18180
  }
17990
18181
  const entryPaths = /* @__PURE__ */ new Set();
17991
18182
  for (const entry of manifest.entries) {
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))) {
18183
+ 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(path51.normalize(entry.path))) {
17993
18184
  throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
17994
18185
  }
17995
- entryPaths.add(path44.normalize(entry.path));
18186
+ entryPaths.add(path51.normalize(entry.path));
17996
18187
  for (const [side, snapshot] of [
17997
18188
  ["before", entry.before],
17998
18189
  ["after", entry.after]
@@ -18006,7 +18197,7 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
18006
18197
  });
18007
18198
  }
18008
18199
  }
18009
- const receiptPath = path44.join(artifactDir, "receipt.json");
18200
+ const receiptPath = path51.join(artifactDir, "receipt.json");
18010
18201
  let receipt;
18011
18202
  if (["applied", "restoring", "restored", "conflict"].includes(state.state) || existsSync13(receiptPath)) {
18012
18203
  receipt = await readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash);
@@ -18016,7 +18207,7 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
18016
18207
  async function readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash) {
18017
18208
  let rawReceipt;
18018
18209
  try {
18019
- rawReceipt = JSON.parse(await readFile9(path44.join(artifactDir, "receipt.json"), "utf8"));
18210
+ rawReceipt = JSON.parse(await readFile9(path51.join(artifactDir, "receipt.json"), "utf8"));
18020
18211
  } catch {
18021
18212
  throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
18022
18213
  }
@@ -18039,7 +18230,7 @@ async function readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHas
18039
18230
  return receipt;
18040
18231
  }
18041
18232
  async function ensureRecoveryReceipt(artifactDir, manifest, manifestHash) {
18042
- const receiptPath = path44.join(artifactDir, "receipt.json");
18233
+ const receiptPath = path51.join(artifactDir, "receipt.json");
18043
18234
  if (existsSync13(receiptPath)) {
18044
18235
  return readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash);
18045
18236
  }
@@ -18064,7 +18255,7 @@ async function artifactRepoRoot(stateDir, checkpointId) {
18064
18255
  const artifactDir = checkpointDir(stateDir, checkpointId);
18065
18256
  try {
18066
18257
  const manifest = JSON.parse(
18067
- await readFile9(path44.join(artifactDir, "manifest.json"), "utf8")
18258
+ await readFile9(path51.join(artifactDir, "manifest.json"), "utf8")
18068
18259
  );
18069
18260
  if (typeof manifest.repoRoot === "string" && manifest.repoRoot) {
18070
18261
  return canonicalPath(manifest.repoRoot);
@@ -18072,7 +18263,7 @@ async function artifactRepoRoot(stateDir, checkpointId) {
18072
18263
  } catch {
18073
18264
  }
18074
18265
  try {
18075
- const owner = JSON.parse(await readFile9(path44.join(artifactDir, "owner.json"), "utf8"));
18266
+ const owner = JSON.parse(await readFile9(path51.join(artifactDir, "owner.json"), "utf8"));
18076
18267
  return typeof owner.repoRoot === "string" && owner.repoRoot ? canonicalPath(owner.repoRoot) : null;
18077
18268
  } catch {
18078
18269
  return null;
@@ -18092,10 +18283,10 @@ async function cleanupOrphanedStaging(stateDir) {
18092
18283
  const now = Date.now();
18093
18284
  for (const entry of await readdir2(root, { withFileTypes: true })) {
18094
18285
  if (!entry.isDirectory() || !/^\.tmp-cp_[a-f0-9]{24}$/.test(entry.name)) continue;
18095
- const stagingPath = path44.join(root, entry.name);
18286
+ const stagingPath = path51.join(root, entry.name);
18096
18287
  let stale = false;
18097
18288
  try {
18098
- const owner = JSON.parse(await readFile9(path44.join(stagingPath, "owner.json"), "utf8"));
18289
+ const owner = JSON.parse(await readFile9(path51.join(stagingPath, "owner.json"), "utf8"));
18099
18290
  const pid = typeof owner.pid === "number" ? owner.pid : Number.NaN;
18100
18291
  const createdAt = typeof owner.createdAt === "string" ? Date.parse(owner.createdAt) : NaN;
18101
18292
  let alive = false;
@@ -18137,7 +18328,7 @@ async function markRecoveryCheckpointApplied(stateDir, checkpoint) {
18137
18328
  init_fingerprint2();
18138
18329
  import { existsSync as existsSync14 } from "node:fs";
18139
18330
  import { readFile as readFile10 } from "node:fs/promises";
18140
- import path45 from "node:path";
18331
+ import path52 from "node:path";
18141
18332
  async function matchRecoverySide(resourceRoot, entries, side) {
18142
18333
  for (const entry of entries) {
18143
18334
  const target = await assertRecoverySafeTarget(resourceRoot, entry.path);
@@ -18152,7 +18343,7 @@ async function reconcileRecoveryCheckpoint(stateDir, checkpointId) {
18152
18343
  } catch {
18153
18344
  const artifactDir = checkpointDir(stateDir, checkpointId);
18154
18345
  if (existsSync14(artifactDir)) {
18155
- const manifestPath = path45.join(artifactDir, "manifest.json");
18346
+ const manifestPath = path52.join(artifactDir, "manifest.json");
18156
18347
  const hash = existsSync14(manifestPath) ? hashValue(await readFile10(manifestPath, "utf8")) : "unavailable";
18157
18348
  await writeRecoveryState(artifactDir, "corrupt", hash, RECOVERY_CHECKPOINT_CORRUPT);
18158
18349
  }
@@ -18189,7 +18380,7 @@ async function reconcileRecoveryCheckpoint(stateDir, checkpointId) {
18189
18380
  // src/core/recovery/resource-identity.ts
18190
18381
  init_fingerprint2();
18191
18382
  import { lstat as lstat8, readFile as readFile11, realpath as realpath3 } from "node:fs/promises";
18192
- import path46 from "node:path";
18383
+ import path53 from "node:path";
18193
18384
  async function currentRecoveryResourceIdentity(resourceRoot, resourceKind) {
18194
18385
  const resolvedRoot = await realpath3(resourceRoot);
18195
18386
  if (resourceKind === "directory") {
@@ -18197,13 +18388,13 @@ async function currentRecoveryResourceIdentity(resourceRoot, resourceKind) {
18197
18388
  if (!rootInfo.isDirectory()) throw new Error("recovery_repo_identity_unavailable");
18198
18389
  return hashValue(`${resolvedRoot}\0${rootInfo.dev}:${rootInfo.ino}:${rootInfo.birthtimeMs}`);
18199
18390
  }
18200
- const dotGit = path46.join(resolvedRoot, ".git");
18391
+ const dotGit = path53.join(resolvedRoot, ".git");
18201
18392
  const gitInfo = await lstat8(dotGit);
18202
18393
  let gitMetadataPath = dotGit;
18203
18394
  if (gitInfo.isFile()) {
18204
18395
  const marker = (await readFile11(dotGit, "utf8")).trim();
18205
18396
  if (!marker.startsWith("gitdir:")) throw new Error("recovery_repo_identity_unavailable");
18206
- gitMetadataPath = path46.resolve(resolvedRoot, marker.slice("gitdir:".length).trim());
18397
+ gitMetadataPath = path53.resolve(resolvedRoot, marker.slice("gitdir:".length).trim());
18207
18398
  } else if (!gitInfo.isDirectory()) {
18208
18399
  throw new Error("recovery_repo_identity_unavailable");
18209
18400
  }
@@ -18276,16 +18467,16 @@ async function prepareRecoveryCheckpoint(params) {
18276
18467
  throw new Error(RECOVERY_CHECKPOINT_QUOTA);
18277
18468
  }
18278
18469
  const checkpointId = `cp_${randomUUID5().replaceAll("-", "").slice(0, 24)}`;
18279
- const temporary = path47.join(checkpointsRoot(params.stateDir), `.tmp-${checkpointId}`);
18470
+ const temporary = path54.join(checkpointsRoot(params.stateDir), `.tmp-${checkpointId}`);
18280
18471
  const finalDir = checkpointDir(params.stateDir, checkpointId);
18281
18472
  await mkdir11(temporary, { recursive: true, mode: 448 });
18282
- await atomicWriteJson(path47.join(temporary, "owner.json"), {
18473
+ await atomicWriteJson(path54.join(temporary, "owner.json"), {
18283
18474
  version: 1,
18284
18475
  pid: process.pid,
18285
18476
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
18286
18477
  repoRoot: canonicalPath(params.repoRoot)
18287
18478
  });
18288
- await mkdir11(path47.join(temporary, "blobs"), { recursive: true, mode: 448 });
18479
+ await mkdir11(path54.join(temporary, "blobs"), { recursive: true, mode: 448 });
18289
18480
  try {
18290
18481
  const entries = [];
18291
18482
  const protectedRoots = (params.protectedRoots ?? []).map(canonicalPath);
@@ -18294,8 +18485,8 @@ async function prepareRecoveryCheckpoint(params) {
18294
18485
  )) {
18295
18486
  const target = await assertRecoverySafeTarget(params.repoRoot, change.relativePath);
18296
18487
  if (protectedRoots.some((root) => {
18297
- const relative = path47.relative(root, target);
18298
- return relative === "" || relative !== ".." && !relative.startsWith(`..${path47.sep}`) && !path47.isAbsolute(relative);
18488
+ const relative = path54.relative(root, target);
18489
+ return relative === "" || relative !== ".." && !relative.startsWith(`..${path54.sep}`) && !path54.isAbsolute(relative);
18299
18490
  })) {
18300
18491
  throw new Error("recovery_protected_path");
18301
18492
  }
@@ -18307,7 +18498,7 @@ async function prepareRecoveryCheckpoint(params) {
18307
18498
  entries.push({
18308
18499
  path: change.relativePath,
18309
18500
  before: await captureRecoverySnapshot(baseline, {
18310
- blobDir: path47.join(temporary, "blobs")
18501
+ blobDir: path54.join(temporary, "blobs")
18311
18502
  }),
18312
18503
  after: withoutRecoveryBlob(await captureRecoverySnapshot(source))
18313
18504
  });
@@ -18344,7 +18535,7 @@ async function prepareRecoveryCheckpoint(params) {
18344
18535
  entries
18345
18536
  };
18346
18537
  const manifestHash = hashValue(canonicalStringify(manifest));
18347
- await atomicWriteJson(path47.join(temporary, "manifest.json"), manifest);
18538
+ await atomicWriteJson(path54.join(temporary, "manifest.json"), manifest);
18348
18539
  await writeRecoveryState(temporary, "prepared", manifestHash);
18349
18540
  await fsyncPath2(temporary);
18350
18541
  const projectedBytes = await recoveryCheckpointStorageBytes(params.stateDir, params.repoRoot);
@@ -18387,7 +18578,7 @@ async function listRecoveryCheckpoints(stateDir, repoRoot) {
18387
18578
  } catch {
18388
18579
  try {
18389
18580
  const raw = JSON.parse(
18390
- await readFile12(path47.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
18581
+ await readFile12(path54.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
18391
18582
  );
18392
18583
  rootFromArtifact = typeof raw.repoRoot === "string" && raw.repoRoot ? raw.repoRoot : void 0;
18393
18584
  } catch {
@@ -18421,7 +18612,7 @@ async function listRecoveryCheckpoints(stateDir, repoRoot) {
18421
18612
  } catch {
18422
18613
  try {
18423
18614
  const manifest = JSON.parse(
18424
- await readFile12(path47.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
18615
+ await readFile12(path54.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
18425
18616
  );
18426
18617
  if (manifest.checkpointId !== id || ![1, 2].includes(manifest.version)) continue;
18427
18618
  if (repoRoot && canonicalPath(manifest.repoRoot) !== canonicalPath(repoRoot)) continue;
@@ -18451,7 +18642,7 @@ async function recoveryCheckpointStorageBytes(stateDir, repoRoot) {
18451
18642
  let total = 0;
18452
18643
  for (const entry of await readdir3(root, { withFileTypes: true })) {
18453
18644
  if (!entry.isDirectory()) continue;
18454
- const entryPath = path47.join(root, entry.name);
18645
+ const entryPath = path54.join(root, entry.name);
18455
18646
  if (/^cp_[a-f0-9]{24}$/.test(entry.name)) {
18456
18647
  if (await artifactRepoRoot(stateDir, entry.name) === expected) {
18457
18648
  total += await directorySize(entryPath);
@@ -18460,7 +18651,7 @@ async function recoveryCheckpointStorageBytes(stateDir, repoRoot) {
18460
18651
  }
18461
18652
  if (/^\.tmp-cp_[a-f0-9]{24}$/.test(entry.name)) {
18462
18653
  try {
18463
- const owner = JSON.parse(await readFile12(path47.join(entryPath, "owner.json"), "utf8"));
18654
+ const owner = JSON.parse(await readFile12(path54.join(entryPath, "owner.json"), "utf8"));
18464
18655
  if (typeof owner.repoRoot === "string" && canonicalPath(owner.repoRoot) === expected) {
18465
18656
  total += await directorySize(entryPath);
18466
18657
  }
@@ -18504,14 +18695,14 @@ init_scrub();
18504
18695
  // src/core/transactional/file-checkpoint-backend.ts
18505
18696
  import { cp, lstat as lstat11, mkdir as mkdir13, mkdtemp as mkdtemp5, readdir as readdir6, rm as rm9, writeFile as writeFile10 } from "node:fs/promises";
18506
18697
  import os5 from "node:os";
18507
- import path51 from "node:path";
18698
+ import path58 from "node:path";
18508
18699
 
18509
18700
  // src/core/transactional/file-checkpoint-git.ts
18510
18701
  init_path_utils();
18511
18702
  import { spawn as spawn8 } from "node:child_process";
18512
18703
  import { createHash as createHash13 } from "node:crypto";
18513
18704
  import { copyFile as copyFile3, lstat as lstat9, readdir as readdir4, readFile as readFile13 } from "node:fs/promises";
18514
- import path48 from "node:path";
18705
+ import path55 from "node:path";
18515
18706
  var FILE_CHECKPOINT_GIT_METADATA_CHANGED = "file_checkpoint_git_metadata_changed";
18516
18707
  var FILE_CHECKPOINT_SOURCE_CHANGED = "file_checkpoint_source_changed";
18517
18708
  var FILE_CHECKPOINT_CWD_OUTSIDE_ROOT = "file_checkpoint_cwd_outside_root";
@@ -18545,7 +18736,7 @@ function rethrowStableFileCheckpointError(error) {
18545
18736
  }
18546
18737
  async function rootGitMetadataPresent(repoRoot) {
18547
18738
  try {
18548
- await lstat9(path48.join(repoRoot, ".git"));
18739
+ await lstat9(path55.join(repoRoot, ".git"));
18549
18740
  return true;
18550
18741
  } catch {
18551
18742
  return false;
@@ -18594,10 +18785,10 @@ function execGit2(repoRoot, args) {
18594
18785
  }
18595
18786
  async function resolveGitPath(repoRoot, gitPath) {
18596
18787
  const trimmed = gitPath.trim();
18597
- if (path48.isAbsolute(trimmed)) {
18788
+ if (path55.isAbsolute(trimmed)) {
18598
18789
  return trimmed;
18599
18790
  }
18600
- return path48.join(repoRoot, trimmed);
18791
+ return path55.join(repoRoot, trimmed);
18601
18792
  }
18602
18793
  async function cloneBareWorktreeCopy(sourceRoot, destinationRoot) {
18603
18794
  await execGit2(sourceRoot, [
@@ -18639,7 +18830,7 @@ async function copyGitIndexState(sourceRoot, destinationRoot) {
18639
18830
  destinationRoot,
18640
18831
  await execGit2(destinationRoot, ["rev-parse", "--git-dir"])
18641
18832
  );
18642
- const destinationShared = path48.join(destinationGitDir, path48.basename(sourceShared));
18833
+ const destinationShared = path55.join(destinationGitDir, path55.basename(sourceShared));
18643
18834
  try {
18644
18835
  await copyFile3(sourceShared, destinationShared);
18645
18836
  } catch (error) {
@@ -18648,7 +18839,7 @@ async function copyGitIndexState(sourceRoot, destinationRoot) {
18648
18839
  }
18649
18840
  async function readGitFile(gitDir, relativePath) {
18650
18841
  try {
18651
- return await readFile13(path48.join(gitDir, relativePath));
18842
+ return await readFile13(path55.join(gitDir, relativePath));
18652
18843
  } catch {
18653
18844
  return null;
18654
18845
  }
@@ -18672,8 +18863,8 @@ async function hashResolvedGitPath(repoRoot, gitDir, gitPath, hash) {
18672
18863
  repoRoot,
18673
18864
  await execGit2(repoRoot, ["rev-parse", "--git-path", gitPath])
18674
18865
  );
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);
18866
+ const relative = path55.resolve(resolved).startsWith(path55.resolve(gitDir)) ? path55.relative(gitDir, resolved) : resolved;
18867
+ const content = typeof relative === "string" && !relative.startsWith("..") && !path55.isAbsolute(relative) ? await readGitFile(gitDir, relative) : await readAbsoluteGitFile(resolved);
18677
18868
  if (content !== null) {
18678
18869
  hashGitFileContent(hash, gitPath, content);
18679
18870
  }
@@ -18683,13 +18874,13 @@ async function hashResolvedGitPath(repoRoot, gitDir, gitPath, hash) {
18683
18874
  async function hashGitTree(gitDir, relativeDir, hash) {
18684
18875
  let names;
18685
18876
  try {
18686
- names = await readdir4(path48.join(gitDir, relativeDir));
18877
+ names = await readdir4(path55.join(gitDir, relativeDir));
18687
18878
  } catch {
18688
18879
  return;
18689
18880
  }
18690
18881
  for (const name of names.sort()) {
18691
- const relativePath = relativeDir ? path48.join(relativeDir, name) : name;
18692
- const absolutePath = path48.join(gitDir, relativePath);
18882
+ const relativePath = relativeDir ? path55.join(relativeDir, name) : name;
18883
+ const absolutePath = path55.join(gitDir, relativePath);
18693
18884
  let childNames = null;
18694
18885
  try {
18695
18886
  childNames = await readdir4(absolutePath);
@@ -18711,7 +18902,7 @@ async function hashGitTree(gitDir, relativeDir, hash) {
18711
18902
  }
18712
18903
  async function computeGitMetadataFingerprint(repoRoot) {
18713
18904
  const gitDirRel = (await execGit2(repoRoot, ["rev-parse", "--git-dir"])).trim();
18714
- const gitDir = path48.isAbsolute(gitDirRel) ? gitDirRel : path48.join(repoRoot, gitDirRel);
18905
+ const gitDir = path55.isAbsolute(gitDirRel) ? gitDirRel : path55.join(repoRoot, gitDirRel);
18715
18906
  const hash = createHash13("sha256");
18716
18907
  for (const file of [
18717
18908
  "HEAD",
@@ -18734,8 +18925,8 @@ async function computeGitMetadataFingerprint(repoRoot) {
18734
18925
  const sharedIndex = (await execGit2(repoRoot, ["rev-parse", "--shared-index-path"])).trim();
18735
18926
  if (sharedIndex) {
18736
18927
  const resolved = await resolveGitPath(repoRoot, sharedIndex);
18737
- const relative = path48.relative(gitDir, resolved);
18738
- const content = relative && !relative.startsWith("..") && !path48.isAbsolute(relative) ? await readGitFile(gitDir, relative) : await readAbsoluteGitFile(resolved);
18928
+ const relative = path55.relative(gitDir, resolved);
18929
+ const content = relative && !relative.startsWith("..") && !path55.isAbsolute(relative) ? await readGitFile(gitDir, relative) : await readAbsoluteGitFile(resolved);
18739
18930
  if (content !== null) {
18740
18931
  hashGitFileContent(hash, "shared-index", content);
18741
18932
  }
@@ -18746,7 +18937,7 @@ async function computeGitMetadataFingerprint(repoRoot) {
18746
18937
  await hashResolvedGitPath(repoRoot, gitDir, gitPath, hash);
18747
18938
  }
18748
18939
  try {
18749
- const rootGitPath = path48.join(repoRoot, ".git");
18940
+ const rootGitPath = path55.join(repoRoot, ".git");
18750
18941
  const rootGitInfo = await lstat9(rootGitPath);
18751
18942
  if (rootGitInfo.isFile()) {
18752
18943
  const content = await readAbsoluteGitFile(rootGitPath);
@@ -18762,14 +18953,14 @@ async function computeGitMetadataFingerprint(repoRoot) {
18762
18953
  function resolveExecutionCwdRelative(resourceRoot, cwd) {
18763
18954
  const resolvedCwd = canonicalPath(cwd);
18764
18955
  const resourceCanonical = canonicalPath(resourceRoot);
18765
- const relative = path48.relative(resourceCanonical, resolvedCwd);
18956
+ const relative = path55.relative(resourceCanonical, resolvedCwd);
18766
18957
  if (relative === "" || relative === ".") {
18767
18958
  return "";
18768
18959
  }
18769
- if (relative.startsWith("..") || path48.isAbsolute(relative)) {
18960
+ if (relative.startsWith("..") || path55.isAbsolute(relative)) {
18770
18961
  throw new Error(FILE_CHECKPOINT_CWD_OUTSIDE_ROOT);
18771
18962
  }
18772
- return relative.split(path48.sep).join("/");
18963
+ return relative.split(path55.sep).join("/");
18773
18964
  }
18774
18965
 
18775
18966
  // src/core/transactional/file-checkpoint-isolation.ts
@@ -18791,7 +18982,7 @@ function fileCheckpointIsolationReason(context) {
18791
18982
 
18792
18983
  // src/core/transactional/file-checkpoint-staging.ts
18793
18984
  import { readdir as readdir5, readFile as readFile14, rm as rm7, writeFile as writeFile8 } from "node:fs/promises";
18794
- import path49 from "node:path";
18985
+ import path56 from "node:path";
18795
18986
  function isOwnerProcessAlive(pid) {
18796
18987
  try {
18797
18988
  process.kill(pid, 0);
@@ -18801,12 +18992,12 @@ function isOwnerProcessAlive(pid) {
18801
18992
  }
18802
18993
  }
18803
18994
  async function writeOwnerMarker(stagingRoot, marker) {
18804
- await writeFile8(path49.join(stagingRoot, "owner.json"), `${JSON.stringify(marker)}
18995
+ await writeFile8(path56.join(stagingRoot, "owner.json"), `${JSON.stringify(marker)}
18805
18996
  `, "utf8");
18806
18997
  }
18807
18998
  async function readOwnerMarker(stagingRoot) {
18808
18999
  try {
18809
- const raw = await readFile14(path49.join(stagingRoot, "owner.json"), "utf8");
19000
+ const raw = await readFile14(path56.join(stagingRoot, "owner.json"), "utf8");
18810
19001
  return JSON.parse(raw.trim());
18811
19002
  } catch {
18812
19003
  return null;
@@ -18824,7 +19015,7 @@ async function collectDeadOwnerStaging(parentDir) {
18824
19015
  if (!name.startsWith("belay-file-checkpoint-")) {
18825
19016
  continue;
18826
19017
  }
18827
- const stagingRoot = path49.join(parentDir, name);
19018
+ const stagingRoot = path56.join(parentDir, name);
18828
19019
  const marker = await readOwnerMarker(stagingRoot);
18829
19020
  if (!marker) {
18830
19021
  dead.push(stagingRoot);
@@ -18856,7 +19047,7 @@ import {
18856
19047
  writeFile as writeFile9
18857
19048
  } from "node:fs/promises";
18858
19049
  import os4 from "node:os";
18859
- import path50 from "node:path";
19050
+ import path57 from "node:path";
18860
19051
  var FILE_CHECKPOINT_COPY_FAILED = "file_checkpoint_copy_failed";
18861
19052
  async function chmodSafe2(target, mode) {
18862
19053
  try {
@@ -18866,7 +19057,7 @@ async function chmodSafe2(target, mode) {
18866
19057
  }
18867
19058
  }
18868
19059
  async function copyRegularFile(sourcePath, destinationPath, mode, strategy) {
18869
- await mkdir12(path50.dirname(destinationPath), { recursive: true });
19060
+ await mkdir12(path57.dirname(destinationPath), { recursive: true });
18870
19061
  if (strategy === "clonefile" && fsConstants2.COPYFILE_FICLONE !== void 0) {
18871
19062
  try {
18872
19063
  await copyFile4(sourcePath, destinationPath, fsConstants2.COPYFILE_FICLONE);
@@ -18892,7 +19083,7 @@ async function copyNode(sourceRoot, destinationRoot, relativePath, strategy) {
18892
19083
  return strategy;
18893
19084
  }
18894
19085
  if (info.isSymbolicLink()) {
18895
- await mkdir12(path50.dirname(destinationPath), { recursive: true });
19086
+ await mkdir12(path57.dirname(destinationPath), { recursive: true });
18896
19087
  await symlink4(await readlink5(sourcePath), destinationPath);
18897
19088
  return strategy;
18898
19089
  }
@@ -18948,9 +19139,9 @@ async function mapWithConcurrency(items, concurrency, worker) {
18948
19139
  async function probeFileCloneStrategy() {
18949
19140
  let tempDir = null;
18950
19141
  try {
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");
19142
+ tempDir = await mkdtemp4(path57.join(os4.tmpdir(), "belay-clone-probe-"));
19143
+ const source = path57.join(tempDir, "source.txt");
19144
+ const destination = path57.join(tempDir, "dest.txt");
18954
19145
  await writeFile9(source, "probe\n");
18955
19146
  if (fsConstants2.COPYFILE_FICLONE_FORCE !== void 0) {
18956
19147
  try {
@@ -19103,11 +19294,11 @@ async function protectedRootState(root) {
19103
19294
  return `directory:${node.hash}:${index.treeHash}`;
19104
19295
  }
19105
19296
  function executionProtectedRoot(resourceRoot, executionRoot, protectedRoot) {
19106
- const relative = path51.relative(path51.resolve(resourceRoot), path51.resolve(protectedRoot));
19107
- if (relative === "" || relative.startsWith("..") || path51.isAbsolute(relative)) {
19297
+ const relative = path58.relative(path58.resolve(resourceRoot), path58.resolve(protectedRoot));
19298
+ if (relative === "" || relative.startsWith("..") || path58.isAbsolute(relative)) {
19108
19299
  return null;
19109
19300
  }
19110
- return path51.join(executionRoot, relative);
19301
+ return path58.join(executionRoot, relative);
19111
19302
  }
19112
19303
  async function captureProtectedRootStates(resourceRoot, executionRoot, protectedRoots) {
19113
19304
  const states = /* @__PURE__ */ new Map();
@@ -19132,15 +19323,15 @@ async function directoryByteSize(root, deadlineMs) {
19132
19323
  }
19133
19324
  let total = 0;
19134
19325
  for (const name of await readdir6(root)) {
19135
- total += await directoryByteSize(path51.join(root, name), deadlineMs);
19326
+ total += await directoryByteSize(path58.join(root, name), deadlineMs);
19136
19327
  }
19137
19328
  return total;
19138
19329
  }
19139
19330
  async function copyGitMetadataDirectory(sourceRoot, destinationRoot) {
19140
19331
  const gitDirRel = (await execGit2(sourceRoot, ["rev-parse", "--git-dir"])).trim();
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");
19332
+ const sourceGitDir = path58.isAbsolute(gitDirRel) ? gitDirRel : path58.join(sourceRoot, gitDirRel);
19333
+ const relativeGitDir = path58.relative(path58.resolve(sourceRoot), path58.resolve(sourceGitDir));
19334
+ const destinationGitDir = relativeGitDir && !relativeGitDir.startsWith("..") ? path58.join(destinationRoot, relativeGitDir) : path58.join(destinationRoot, ".git");
19144
19335
  await cp(sourceGitDir, destinationGitDir, { recursive: true, force: true });
19145
19336
  }
19146
19337
  async function prepareDirtyGitSnapshot(context) {
@@ -19149,7 +19340,7 @@ async function prepareDirtyGitSnapshot(context) {
19149
19340
  const quotas = context.fileCheckpoint;
19150
19341
  const deadlineMs = Date.now() + quotas.prepareTimeoutMs;
19151
19342
  await removeDeadOwnerStaging(os5.tmpdir());
19152
- const stagingRoot = await mkdtemp5(path51.join(os5.tmpdir(), "belay-file-checkpoint-"));
19343
+ const stagingRoot = await mkdtemp5(path58.join(os5.tmpdir(), "belay-file-checkpoint-"));
19153
19344
  await writeOwnerMarker(stagingRoot, {
19154
19345
  version: 1,
19155
19346
  pid: process.pid,
@@ -19157,8 +19348,8 @@ async function prepareDirtyGitSnapshot(context) {
19157
19348
  resourceRoot: context.repoRoot,
19158
19349
  backend: "file_checkpoint"
19159
19350
  });
19160
- const baselineRoot = path51.join(stagingRoot, "baseline");
19161
- const executionRoot = path51.join(stagingRoot, "execution");
19351
+ const baselineRoot = path58.join(stagingRoot, "baseline");
19352
+ const executionRoot = path58.join(stagingRoot, "execution");
19162
19353
  try {
19163
19354
  resolveExecutionCwdRelative(context.repoRoot, context.cwd);
19164
19355
  const sourceGitMetadataFingerprint = await computeGitMetadataFingerprint(context.repoRoot);
@@ -19192,7 +19383,7 @@ async function prepareDirtyGitSnapshot(context) {
19192
19383
  throw new Error(FILE_CHECKPOINT_SOURCE_CHANGED);
19193
19384
  }
19194
19385
  await writeFile10(
19195
- path51.join(stagingRoot, "baseline-index.json"),
19386
+ path58.join(stagingRoot, "baseline-index.json"),
19196
19387
  `${JSON.stringify(baselineIndex)}
19197
19388
  `,
19198
19389
  "utf8"
@@ -19242,7 +19433,7 @@ async function prepareNonGitSnapshot(context) {
19242
19433
  const quotas = context.fileCheckpoint;
19243
19434
  const deadlineMs = Date.now() + quotas.prepareTimeoutMs;
19244
19435
  await removeDeadOwnerStaging(os5.tmpdir());
19245
- const stagingRoot = await mkdtemp5(path51.join(os5.tmpdir(), "belay-file-checkpoint-"));
19436
+ const stagingRoot = await mkdtemp5(path58.join(os5.tmpdir(), "belay-file-checkpoint-"));
19246
19437
  await writeOwnerMarker(stagingRoot, {
19247
19438
  version: 1,
19248
19439
  pid: process.pid,
@@ -19250,8 +19441,8 @@ async function prepareNonGitSnapshot(context) {
19250
19441
  resourceRoot: context.repoRoot,
19251
19442
  backend: "file_checkpoint"
19252
19443
  });
19253
- const baselineRoot = path51.join(stagingRoot, "baseline");
19254
- const executionRoot = path51.join(stagingRoot, "execution");
19444
+ const baselineRoot = path58.join(stagingRoot, "baseline");
19445
+ const executionRoot = path58.join(stagingRoot, "execution");
19255
19446
  try {
19256
19447
  resolveExecutionCwdRelative(context.repoRoot, context.cwd);
19257
19448
  const resourceIdentity = await currentRecoveryResourceIdentity(context.repoRoot, "directory");
@@ -19280,7 +19471,7 @@ async function prepareNonGitSnapshot(context) {
19280
19471
  throw new Error(FILE_CHECKPOINT_SOURCE_CHANGED);
19281
19472
  }
19282
19473
  await writeFile10(
19283
- path51.join(stagingRoot, "baseline-index.json"),
19474
+ path58.join(stagingRoot, "baseline-index.json"),
19284
19475
  `${JSON.stringify(baselineIndex)}
19285
19476
  `,
19286
19477
  "utf8"
@@ -19633,10 +19824,10 @@ async function selectTransactionalBackend(context) {
19633
19824
  }
19634
19825
 
19635
19826
  // src/core/transactional/diff-evaluator.ts
19636
- import path52 from "node:path";
19827
+ import path59 from "node:path";
19637
19828
  init_path_utils();
19638
19829
  function categorizeChange(change, ctx) {
19639
- const absolutePath = canonicalPath(path52.join(ctx.repoRoot, change.relativePath));
19830
+ const absolutePath = canonicalPath(path59.join(ctx.repoRoot, change.relativePath));
19640
19831
  if (!pathWithinRoot(ctx.repoRoot, absolutePath)) {
19641
19832
  return "repo_outside";
19642
19833
  }
@@ -20230,7 +20421,7 @@ async function notifyDeny(config, event) {
20230
20421
  init_path_utils();
20231
20422
 
20232
20423
  // src/adapters/layouts/protected-paths.ts
20233
- import path53 from "node:path";
20424
+ import path60 from "node:path";
20234
20425
  function protectedArtifactRoots(layout, repoRoot, controlPlaneDir) {
20235
20426
  const roots = [
20236
20427
  layout.configPath(repoRoot),
@@ -20242,7 +20433,7 @@ function protectedArtifactRoots(layout, repoRoot, controlPlaneDir) {
20242
20433
  if (controlPlaneDir) {
20243
20434
  roots.push(controlPlaneDir);
20244
20435
  }
20245
- return roots.map((entry) => path53.resolve(entry));
20436
+ return roots.map((entry) => path60.resolve(entry));
20246
20437
  }
20247
20438
 
20248
20439
  // src/adapters/shared/gate-runtime.ts
@@ -20295,8 +20486,8 @@ function createDefaultGateRuntimeDeps() {
20295
20486
  return loadJsonFile(configPath, {});
20296
20487
  },
20297
20488
  async appendAudit(ctx, event) {
20298
- const auditPath = path54.join(ctx.repoRoot, ctx.config.audit.logPath);
20299
- await mkdir14(path54.dirname(auditPath), { recursive: true });
20489
+ const auditPath = path61.join(ctx.repoRoot, ctx.config.audit.logPath);
20490
+ await mkdir14(path61.dirname(auditPath), { recursive: true });
20300
20491
  const provenance = auditProvenance(ctx.config);
20301
20492
  const record = {
20302
20493
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -20329,7 +20520,7 @@ function createDefaultGateRuntimeDeps() {
20329
20520
  };
20330
20521
  },
20331
20522
  async writeApprovals(filePath, state) {
20332
- await mkdir14(path54.dirname(filePath), { recursive: true });
20523
+ await mkdir14(path61.dirname(filePath), { recursive: true });
20333
20524
  await writeFile11(filePath, `${JSON.stringify(compactApprovals(state), null, 2)}
20334
20525
  `, "utf8");
20335
20526
  },
@@ -20491,7 +20682,7 @@ function deriveWorkspaceRootScopeHint(params) {
20491
20682
  if (!targetPath) {
20492
20683
  return void 0;
20493
20684
  }
20494
- const candidateRoot = canonicalPath(path54.dirname(targetPath));
20685
+ const candidateRoot = canonicalPath(path61.dirname(targetPath));
20495
20686
  const validation = validateTrustedWorkspaceRootCandidate({
20496
20687
  candidatePath: candidateRoot,
20497
20688
  repoRoot: action.repoRoot,
@@ -21101,6 +21292,7 @@ async function gateDecisionToVerdict(ctx, deps, kind, result, auditExtras = {})
21101
21292
  event: auditEvent,
21102
21293
  sourceEvent,
21103
21294
  kind,
21295
+ repoLabel: path61.basename(ctx.repoRoot),
21104
21296
  ...auditExtras.toolInvocationCorrelationId ? { toolInvocationCorrelationId: auditExtras.toolInvocationCorrelationId } : {},
21105
21297
  fingerprint: result.fingerprint,
21106
21298
  summary: result.normalizedCommand ?? result.summary ?? "",
@@ -21571,18 +21763,18 @@ async function appendObservedAudit(ctx, deps, eventName, payload) {
21571
21763
 
21572
21764
  // src/adapters/shared/repo-root.ts
21573
21765
  import { existsSync as existsSync17 } from "node:fs";
21574
- import path55 from "node:path";
21766
+ import path62 from "node:path";
21575
21767
  function belayConfigPath(current, adapterName) {
21576
21768
  if (adapterName === "cursor") {
21577
- return path55.join(current, ".cursor", "belay.config.json");
21769
+ return path62.join(current, ".cursor", "belay.config.json");
21578
21770
  }
21579
21771
  if (adapterName === "claude") {
21580
- return path55.join(current, ".claude", "belay.config.json");
21772
+ return path62.join(current, ".claude", "belay.config.json");
21581
21773
  }
21582
- return path55.join(current, ".codex", "belay.config.json");
21774
+ return path62.join(current, ".codex", "belay.config.json");
21583
21775
  }
21584
21776
  function markerMatches(current, marker, layout) {
21585
- const markerPath = path55.join(current, marker);
21777
+ const markerPath = path62.join(current, marker);
21586
21778
  if (!existsSync17(markerPath)) {
21587
21779
  return false;
21588
21780
  }
@@ -21592,16 +21784,16 @@ function markerMatches(current, marker, layout) {
21592
21784
  return true;
21593
21785
  }
21594
21786
  function findRepoRoot(startPath, layout) {
21595
- let current = path55.resolve(startPath);
21787
+ let current = path62.resolve(startPath);
21596
21788
  while (true) {
21597
21789
  for (const marker of layout.repoRootMarkers) {
21598
21790
  if (markerMatches(current, marker, layout)) {
21599
21791
  return current;
21600
21792
  }
21601
21793
  }
21602
- const parent = path55.dirname(current);
21794
+ const parent = path62.dirname(current);
21603
21795
  if (parent === current) {
21604
- return path55.resolve(startPath);
21796
+ return path62.resolve(startPath);
21605
21797
  }
21606
21798
  current = parent;
21607
21799
  }
@@ -21635,7 +21827,7 @@ function resolveCodexActionCwd(payload, fallbackCwd = process2.cwd(), options =
21635
21827
  const nestedCwd = options.includeToolInputCwd ? nonEmptyPathString(toolInput?.working_directory) ?? nonEmptyPathString(toolInput?.cwd) : void 0;
21636
21828
  const payloadCwd = nonEmptyPathString(payload.cwd);
21637
21829
  const workspaceRoot = Array.isArray(payload.workspace_roots) ? payload.workspace_roots.map(nonEmptyPathString).find(Boolean) : void 0;
21638
- return path56.resolve(nestedCwd ?? payloadCwd ?? workspaceRoot ?? fallbackCwd);
21830
+ return path63.resolve(nestedCwd ?? payloadCwd ?? workspaceRoot ?? fallbackCwd);
21639
21831
  }
21640
21832
  async function loadRuntimeContext(cwd) {
21641
21833
  const repoRoot = findRepoRoot(cwd, codexLayout);