@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
@@ -3580,7 +3580,7 @@ var init_config_io = __esm({
3580
3580
 
3581
3581
  // src/adapters/cursor/runtime-entry.ts
3582
3582
  init_approval();
3583
- import path58 from "node:path";
3583
+ import path65 from "node:path";
3584
3584
  import process2 from "node:process";
3585
3585
 
3586
3586
  // src/core/approval-repo-lookup.ts
@@ -3640,7 +3640,7 @@ init_approval_replay();
3640
3640
  import { randomUUID as randomUUID6 } from "node:crypto";
3641
3641
  import { existsSync as existsSync16 } from "node:fs";
3642
3642
  import { mkdir as mkdir14, readFile as readFile16, writeFile as writeFile11 } from "node:fs/promises";
3643
- import path55 from "node:path";
3643
+ import path62 from "node:path";
3644
3644
 
3645
3645
  // src/core/approval-service.ts
3646
3646
  init_config_io();
@@ -6647,31 +6647,44 @@ import { randomUUID as randomUUID3 } from "node:crypto";
6647
6647
  import path22 from "node:path";
6648
6648
 
6649
6649
  // src/core/glob.ts
6650
+ init_approval();
6651
+ function globPatternToRegexSource(pattern) {
6652
+ let result = "";
6653
+ let index = 0;
6654
+ while (index < pattern.length) {
6655
+ const char = pattern[index];
6656
+ if (char === "*" && pattern[index + 1] === "*") {
6657
+ index += 2;
6658
+ if (pattern[index] === "/") {
6659
+ result += "(?:.*/)?";
6660
+ index += 1;
6661
+ } else {
6662
+ result += ".*";
6663
+ }
6664
+ continue;
6665
+ }
6666
+ if (char === "*") {
6667
+ result += "[^/]*";
6668
+ index += 1;
6669
+ continue;
6670
+ }
6671
+ result += escapeRegex(char);
6672
+ index += 1;
6673
+ }
6674
+ return result;
6675
+ }
6676
+ function globPatternMatches(pattern, normalized, baseName) {
6677
+ const regex = new RegExp(`^${globPatternToRegexSource(pattern)}$`);
6678
+ return regex.test(normalized) || regex.test(baseName);
6679
+ }
6650
6680
  function matchesSensitivePath(filePath, patterns) {
6651
6681
  const normalized = filePath.replaceAll("\\", "/");
6652
6682
  const segments = normalized.split("/");
6653
6683
  const baseName = segments.at(-1) ?? normalized;
6654
6684
  for (const pattern of patterns) {
6655
6685
  const normalizedPattern = pattern.replaceAll("\\", "/");
6656
- if (normalizedPattern.includes("**")) {
6657
- const parts = normalizedPattern.split("**").map((part) => part.replace(/^\/+|\/+$/g, ""));
6658
- const prefix = parts[0]?.replace(/\/+$/, "") ?? "";
6659
- const suffix = parts[1]?.replace(/^\/+/, "") ?? "";
6660
- if (prefix && !normalized.startsWith(prefix)) {
6661
- continue;
6662
- }
6663
- if (suffix && !normalized.includes(suffix)) {
6664
- continue;
6665
- }
6666
- if (prefix || suffix) {
6667
- return true;
6668
- }
6669
- }
6670
6686
  if (normalizedPattern.includes("*")) {
6671
- const regex = new RegExp(
6672
- `^${normalizedPattern.replaceAll(".", "\\.").replaceAll("*", ".*")}$`
6673
- );
6674
- if (regex.test(normalized) || regex.test(baseName)) {
6687
+ if (globPatternMatches(normalizedPattern, normalized, baseName)) {
6675
6688
  return true;
6676
6689
  }
6677
6690
  continue;
@@ -11073,6 +11086,16 @@ function subagentFingerprintSource(payload, scrubOptions) {
11073
11086
  }
11074
11087
  return scrubValue(payload, scrubOptions);
11075
11088
  }
11089
+ function fingerprintToolInputSource(payload) {
11090
+ const toolInput = payload.tool_input;
11091
+ if (!toolInput || typeof toolInput !== "object") {
11092
+ return {};
11093
+ }
11094
+ return redactToolInvocationId(
11095
+ toolInput,
11096
+ typeof payload.tool_use_id === "string" ? payload.tool_use_id : void 0
11097
+ );
11098
+ }
11076
11099
  function fingerprintReplayPayload(kind, payload, scrubOptions) {
11077
11100
  if (!payload) {
11078
11101
  return void 0;
@@ -11182,7 +11205,7 @@ function classifySubagent(payload, repoRoot, options = {}, config) {
11182
11205
  }
11183
11206
 
11184
11207
  // src/core/classify-tool.ts
11185
- import path43 from "node:path";
11208
+ import path50 from "node:path";
11186
11209
  init_fingerprint2();
11187
11210
  init_path_utils();
11188
11211
  init_scrub();
@@ -11301,11 +11324,8 @@ function worstEffectDecision(decisions) {
11301
11324
  }
11302
11325
 
11303
11326
  // src/core/effect-ir/shell-lower.ts
11304
- init_git_resource_identity();
11305
- init_path_utils();
11306
11327
  init_shell_tokenizer();
11307
- import { lstatSync as lstatSync2, realpathSync as realpathSync4 } from "node:fs";
11308
- import path42 from "node:path";
11328
+ import path49 from "node:path";
11309
11329
 
11310
11330
  // src/core/verdict/docker-compose-run.ts
11311
11331
  import path37 from "node:path";
@@ -11645,6 +11665,17 @@ function decodeDockerComposeRun(tokens) {
11645
11665
 
11646
11666
  // src/core/verdict/egress-classify.ts
11647
11667
  import path38 from "node:path";
11668
+ var EGRESS_TOOL_HEADS = /* @__PURE__ */ new Set([
11669
+ "aws",
11670
+ "curl",
11671
+ "gh",
11672
+ "gcloud",
11673
+ "heroku",
11674
+ "kubectl",
11675
+ "netlify",
11676
+ "vercel",
11677
+ "wget"
11678
+ ]);
11648
11679
  var CURL_EFFECT_NEUTRAL_FLAGS = /* @__PURE__ */ new Set([
11649
11680
  "-f",
11650
11681
  "-L",
@@ -11678,6 +11709,9 @@ var GH_READ_COMMANDS = /* @__PURE__ */ new Set([
11678
11709
  "workflow list",
11679
11710
  "workflow view"
11680
11711
  ]);
11712
+ function isEgressToolHead(head) {
11713
+ return EGRESS_TOOL_HEADS.has(head);
11714
+ }
11681
11715
  function decodeEgressEffects(params) {
11682
11716
  const head = path38.basename(params.tokens[0] ?? "");
11683
11717
  if (head !== "curl" && head !== "wget" && head !== "gh") {
@@ -13940,7 +13974,7 @@ function extractBalancedParenContent(command, start) {
13940
13974
  index += 1;
13941
13975
  continue;
13942
13976
  }
13943
- if (char === "\\" && (inSingle || inDouble)) {
13977
+ if (char === "\\" && inDouble) {
13944
13978
  escaping = true;
13945
13979
  index += 1;
13946
13980
  continue;
@@ -14018,7 +14052,9 @@ function hasUnclosedQuote(command) {
14018
14052
  continue;
14019
14053
  }
14020
14054
  if (char === "\\") {
14021
- escaping = true;
14055
+ if (quote !== "'") {
14056
+ escaping = true;
14057
+ }
14022
14058
  continue;
14023
14059
  }
14024
14060
  if (quote) {
@@ -14535,6 +14571,50 @@ function redactCommand(command) {
14535
14571
  return command.replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [REDACTED]").replace(/sk-[A-Za-z0-9]{8,}/g, "sk-[REDACTED]").trim();
14536
14572
  }
14537
14573
 
14574
+ // src/core/effect-ir/argv-delegate.ts
14575
+ function innerRecipeFromArgvDelegate(peel) {
14576
+ if (peel.opaque || peel.innerTokens.length === 0) {
14577
+ return null;
14578
+ }
14579
+ return peel.innerTokens.join(" ");
14580
+ }
14581
+ function peelArgvDelegateArgv(tokens) {
14582
+ const launcher = tokens[0] ?? "";
14583
+ if (!launcher || tokens.length < 2) {
14584
+ return null;
14585
+ }
14586
+ const signals = ["process.argv_delegate"];
14587
+ let index = 1;
14588
+ while (index < tokens.length) {
14589
+ const token = tokens[index] ?? "";
14590
+ if (token === "--") {
14591
+ index += 1;
14592
+ break;
14593
+ }
14594
+ if (token.startsWith("-")) {
14595
+ return {
14596
+ launcher,
14597
+ innerTokens: [],
14598
+ opaque: true,
14599
+ reason: "argv_delegate_wrapper_options",
14600
+ signals: [...signals, "argv_delegate_wrapper_options"]
14601
+ };
14602
+ }
14603
+ break;
14604
+ }
14605
+ const innerTokens = tokens.slice(index);
14606
+ if (innerTokens.length === 0) {
14607
+ return null;
14608
+ }
14609
+ return {
14610
+ launcher,
14611
+ innerTokens,
14612
+ opaque: false,
14613
+ reason: "argv_delegate_peel",
14614
+ signals
14615
+ };
14616
+ }
14617
+
14538
14618
  // src/core/effect-ir/shell-build.ts
14539
14619
  function buildShellEffectPlan(params) {
14540
14620
  const nodes = params.segments.map(buildSegmentNode);
@@ -14592,717 +14672,631 @@ function mergeNodes(nodes) {
14592
14672
  return { kind: "merge", children: nodes };
14593
14673
  }
14594
14674
 
14595
- // src/core/effect-ir/shell-lower.ts
14596
- var MAX_LOWER_DEPTH = 8;
14597
- var ENV_PREFIX_PATTERN2 = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
14598
- var LOOPBACK_HOSTS2 = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1", "0:0:0:0:0:0:0:1"]);
14675
+ // src/core/effect-ir/shell-lower/argv-delegate-gate.ts
14676
+ import path42 from "node:path";
14677
+ var ARGV_DELEGATE_INNER_BLOCKLIST = /* @__PURE__ */ new Set([
14678
+ "sudo",
14679
+ "env",
14680
+ "command",
14681
+ "builtin",
14682
+ "exec",
14683
+ "time",
14684
+ "nice",
14685
+ "nohup",
14686
+ "stdbuf",
14687
+ "setsid",
14688
+ "("
14689
+ ]);
14690
+ function shouldApplyArgvDelegate(head, innerTokens, depth) {
14691
+ if (depth > 0 || isEgressToolHead(head) || head === "bundle") {
14692
+ return false;
14693
+ }
14694
+ if (ARGV_DELEGATE_INNER_BLOCKLIST.has(head)) {
14695
+ return false;
14696
+ }
14697
+ const innerHead = path42.basename(innerTokens[0] ?? "");
14698
+ if (ARGV_DELEGATE_INNER_BLOCKLIST.has(innerHead)) {
14699
+ return false;
14700
+ }
14701
+ return innerTokens.length >= 2;
14702
+ }
14703
+ function isGrammarUnknownOnly(requirements, head) {
14704
+ if (requirements.length !== 2) {
14705
+ return false;
14706
+ }
14707
+ const [spawn9, indeterminate] = requirements;
14708
+ 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");
14709
+ }
14710
+
14711
+ // src/core/effect-ir/shell-lower/augment.ts
14712
+ init_shell_tokenizer();
14713
+
14714
+ // src/core/effect-ir/shell-lower/requirement.ts
14599
14715
  var SECRET_PATH_PATTERN = /(?:^|[/\\])(?:\.env(?:\..*)?|credentials?|secrets?|id_rsa)(?:$|[/\\])/i;
14600
14716
  var CONTROL_PLANE_PATH_PATTERN = /^(?:\/etc(?:\/|$)|\/var\/run(?:\/|$)|.*(?:^|[/\\])\.(?:git|ssh|cursor|claude)(?:[/\\]|$))/;
14601
- function lowerShellEffectPlan(params) {
14602
- const context = { ...params, depth: 0 };
14603
- const segments = lowerTopLevelSegments(params.command, context);
14604
- return buildShellEffectPlan({
14605
- inputFingerprint: params.inputFingerprint,
14606
- segments,
14607
- signals: pipeToShell(params.command) ? ["pipe_to_shell"] : []
14608
- });
14717
+ function requirement2(tag, action, resource, segment, signals) {
14718
+ return {
14719
+ tag,
14720
+ action,
14721
+ resource,
14722
+ evidence: {
14723
+ level: tag === "indeterminate" ? "indeterminate" : "certain",
14724
+ signals: [...new Set(signals)].sort(),
14725
+ basis: ["shell_semantic_lowering"]
14726
+ },
14727
+ provenance: { segment }
14728
+ };
14609
14729
  }
14610
- function lowerTopLevelSegments(command, context) {
14611
- const commands = splitStructuralShellSegments(command);
14612
- if (commands.length === 0 && command.trim()) {
14613
- commands.push(command.trim());
14614
- }
14615
- const pipedToShell = pipeToShell(command);
14616
- const lowered = [];
14617
- let cwd = context.cwd;
14618
- let cwdKnown = true;
14619
- let inferredEnv = { ...context.env };
14620
- for (const segment of commands) {
14621
- let result = lowerSegment(segment, {
14622
- ...context,
14623
- cwd,
14624
- env: inferredEnv,
14625
- command: segment,
14626
- depth: context.depth,
14627
- inputFingerprint: context.inputFingerprint,
14628
- ...pipedToShell && isShellHead(parseSegment(segment).head) ? { pipeToShellSegment: true } : {}
14629
- });
14630
- if (!cwdKnown) {
14631
- const requiresCwd = result.requirements.some(requiresKnownCwd);
14632
- const requirements = [...result.requirements];
14633
- if (requiresCwd) {
14634
- requirements.push(
14635
- requirement2(
14636
- "indeterminate",
14637
- "indeterminate",
14638
- { kind: "unknown" },
14639
- result.commandRedacted,
14640
- ["shell.cwd_unknown"]
14641
- )
14642
- );
14643
- }
14644
- result = {
14645
- ...result,
14646
- requirements,
14647
- ...requiresCwd ? {
14648
- completeness: "partial",
14649
- opacity: joinEffectOpacity(result.opacity, "opaque")
14650
- } : {},
14651
- signals: [.../* @__PURE__ */ new Set([...result.signals, "shell.cwd_unknown"])]
14652
- };
14653
- }
14654
- lowered.push(result);
14655
- if (!inferredEnv.DATABASE_URL && startsLocalPostgresService(segment)) {
14656
- inferredEnv = {
14657
- ...inferredEnv,
14658
- DATABASE_URL: "postgresql://127.0.0.1:5432/local"
14659
- };
14660
- }
14661
- const nextCwd = resolveCdTransition(segment, cwd);
14662
- if (nextCwd) {
14663
- cwd = nextCwd.cwd;
14664
- cwdKnown = nextCwd.known;
14665
- }
14666
- }
14667
- if (commands.length > 1 && /[\r\n]/.test(command) && lowered.some(
14668
- (segment) => segment.requirements.some((entry) => entry.action === "network.connect")
14669
- )) {
14670
- lowered.push(
14671
- shellSegment(
14672
- "[multiline network boundary]",
14673
- "shell-boundary",
14674
- [
14675
- requirement2(
14676
- "indeterminate",
14677
- "indeterminate",
14678
- { kind: "unknown" },
14679
- "[multiline network boundary]",
14680
- ["shell.multiline_network_boundary"]
14681
- )
14682
- ],
14683
- "opaque",
14684
- /* @__PURE__ */ new Set(["shell.multiline_network_boundary"])
14730
+ function processRequirement(command, operation, segment, signals) {
14731
+ return requirement2(
14732
+ "process.exec",
14733
+ "process.exec",
14734
+ { kind: "executable", command, operation },
14735
+ segment,
14736
+ signals
14737
+ );
14738
+ }
14739
+ function unsupportedProcess(command, segment, signal) {
14740
+ return [
14741
+ processRequirement(command, "spawn", segment, [signal]),
14742
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [signal])
14743
+ ];
14744
+ }
14745
+ function addWriteEffects(requirements, resolved, segment, signals) {
14746
+ requirements.push(
14747
+ requirement2("fs.write", "fs.write", { kind: "path", path: resolved }, segment, signals)
14748
+ );
14749
+ if (CONTROL_PLANE_PATH_PATTERN.test(resolved)) {
14750
+ requirements.push(
14751
+ requirement2(
14752
+ "control_plane.write",
14753
+ "control_plane.write",
14754
+ { kind: "path", path: resolved },
14755
+ segment,
14756
+ [...signals, "protected_path"]
14685
14757
  )
14686
14758
  );
14687
14759
  }
14688
- return lowered;
14689
- }
14690
- function startsLocalPostgresService(command) {
14691
- const tokens = tokenizeShell(command);
14692
- return path42.basename(tokens[0] ?? "") === "docker" && tokens[1] === "compose" && ["up", "start", "restart"].includes(tokens[2] ?? "") && tokens.includes("postgres");
14693
14760
  }
14694
- function resolveCdTransition(command, currentCwd) {
14695
- const tokens = tokenizeShell(command);
14696
- if (path42.basename(tokens[0] ?? "") !== "cd") {
14697
- return null;
14698
- }
14699
- const target = tokens[1] ?? "~";
14700
- if (!target || target === "-" || target.includes("$") || target.includes("`")) {
14701
- return { cwd: currentCwd, known: false };
14761
+ function addSecretRead(requirements, resolved, segment) {
14762
+ if (SECRET_PATH_PATTERN.test(resolved)) {
14763
+ requirements.push(
14764
+ requirement2("secret.read", "secret.read", { kind: "path", path: resolved }, segment, [
14765
+ "secret_path_read"
14766
+ ])
14767
+ );
14702
14768
  }
14703
- return { cwd: resolvePathOperand(target, currentCwd), known: true };
14704
14769
  }
14705
- function requiresKnownCwd(requirementValue) {
14706
- if (requirementValue.action === "fs.write" || requirementValue.action === "git.ref.write" || requirementValue.action === "control_plane.write") {
14707
- return true;
14770
+ function ensureRequirement(requirements, candidate) {
14771
+ if (!requirements.some(
14772
+ (entry) => entry.action === candidate.action && JSON.stringify(entry.resource) === JSON.stringify(candidate.resource)
14773
+ )) {
14774
+ requirements.push(candidate);
14708
14775
  }
14709
- return requirementValue.action === "process.exec" && requirementValue.resource.kind === "executable" && requirementValue.resource.operation === "spawn";
14710
- }
14711
- function joinNestedOpacity(outer, nested) {
14712
- const nestedOpacity = nested.completeness === "partial" && nested.opacity === "transparent" ? "recursive" : nested.opacity;
14713
- return joinEffectOpacity(outer, nestedOpacity);
14714
14776
  }
14715
- function lowerSegment(command, context) {
14716
- const commandRedacted = redactCommand(command);
14717
- const lexed = lexShell(command);
14718
- const rawTokens = lexed.tokens.map((token) => token.value);
14719
- const environment = extractEnvironment(rawTokens, context.env);
14720
- const env = environment.env;
14721
- const parsed = parseSegment(command);
14722
- const parsedTokens = environment.commandTokens ?? parsed.tokens;
14723
- const tokens = stripRedirects(
14724
- parsedTokens.length === 0 && rawTokens.length > 0 && rawTokens.every((token) => ENV_PREFIX_PATTERN2.test(token)) ? rawTokens : parsedTokens
14725
- ).map((token) => expandKnownVariables(token, env));
14726
- const decoderTokens = alignStructuredTokens(
14727
- stripStructuredRedirects(lexed.tokens),
14728
- stripRedirects(parsedTokens)
14777
+ function withProvenances(requirementValue, provenance, contributing) {
14778
+ const provenances = [
14779
+ ...requirementValue.provenances ?? [requirementValue.provenance],
14780
+ contributing,
14781
+ provenance
14782
+ ].filter(
14783
+ (candidate, index, all) => all.findIndex((entry) => JSON.stringify(entry) === JSON.stringify(candidate)) === index
14729
14784
  );
14730
- const head = path42.basename(tokens[0] ?? parsed.head);
14731
- let opacity = segmentOpacity(command);
14732
- const signals = /* @__PURE__ */ new Set();
14733
- const requirements = [];
14734
- if (!lexed.complete) {
14735
- requirements.push(
14736
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
14737
- "shell.grammar_incomplete"
14738
- ])
14739
- );
14740
- signals.add("shell.grammar_incomplete");
14741
- opacity = joinEffectOpacity(opacity, "unparseable");
14742
- }
14743
- addRedirectEffects(requirements, rawTokens, env, context, commandRedacted);
14744
- addSubstitutionEffects(requirements, command, context, commandRedacted, signals);
14745
- if (environment.malformed) {
14746
- requirements.push(
14747
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
14748
- "shell.env_wrapper_incomplete"
14749
- ])
14750
- );
14751
- opacity = joinEffectOpacity(opacity, "opaque");
14752
- }
14753
- if (parsed.encounteredXargs) {
14754
- requirements.push(
14755
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
14756
- "shell.xargs_stdin_dynamic"
14757
- ])
14758
- );
14759
- signals.add("shell.xargs_stdin_dynamic");
14760
- opacity = joinEffectOpacity(opacity, "opaque");
14761
- }
14762
- if (context.depth > MAX_LOWER_DEPTH) {
14763
- requirements.push(
14764
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
14765
- "shell.lower_depth_exceeded"
14766
- ])
14767
- );
14768
- return shellSegment(
14769
- commandRedacted,
14770
- head,
14771
- requirements,
14772
- joinEffectOpacity(opacity, "opaque"),
14773
- signals
14774
- );
14775
- }
14776
- const packageExec = peelPackageExecArgv(tokens);
14777
- if (packageExec) {
14778
- requirements.push(
14779
- ...decodePackageExec(packageExec, context.cwd, context.repoRoot, commandRedacted)
14780
- );
14781
- for (const signal of packageExec.signals) {
14782
- signals.add(signal);
14783
- }
14784
- if (packageExec.opaque) {
14785
- opacity = joinEffectOpacity(opacity, "opaque");
14786
- }
14787
- const innerRecipe = packageExecInnerIsMetadata(packageExec) ? null : innerRecipeFromPeel(packageExec);
14788
- if (innerRecipe) {
14789
- const nested = lowerTopLevelSegments(innerRecipe, {
14790
- ...context,
14791
- command: innerRecipe,
14792
- env,
14793
- depth: context.depth + 1
14794
- });
14795
- for (const nestedSegment of nested) {
14796
- requirements.push(
14797
- ...nestedSegment.requirements.map(
14798
- (entry) => withInnerProvenance(entry, innerRecipe, packageExec.launcher, commandRedacted)
14799
- )
14800
- );
14801
- for (const signal of nestedSegment.signals) {
14802
- signals.add(signal);
14785
+ return { ...requirementValue, provenance, provenances };
14786
+ }
14787
+ function withInnerProvenance(requirementValue, innerCommand, launcher, outerSegment) {
14788
+ const provenance = {
14789
+ ...requirementValue.provenance,
14790
+ segment: outerSegment,
14791
+ innerCommand: requirementValue.provenance.innerCommand ?? innerCommand,
14792
+ ...launcher === "npm" || launcher === "pnpm" ? { launcher, phase: "delegate" } : {}
14793
+ };
14794
+ const launcherProvenance = {
14795
+ segment: outerSegment,
14796
+ innerCommand,
14797
+ ...launcher === "npm" || launcher === "pnpm" ? { launcher, phase: "delegate" } : {}
14798
+ };
14799
+ return withProvenances(requirementValue, provenance, launcherProvenance);
14800
+ }
14801
+
14802
+ // src/core/effect-ir/shell-lower/tokens.ts
14803
+ init_shell_tokenizer();
14804
+ import path43 from "node:path";
14805
+ var ENV_PREFIX_PATTERN2 = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
14806
+ var LOOPBACK_HOSTS2 = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1", "0:0:0:0:0:0:0:1"]);
14807
+ var METADATA_ONLY_FLAGS = /* @__PURE__ */ new Set(["--version", "-v", "-V", "--help", "-h"]);
14808
+ function extractEnvironment(tokens, inherited) {
14809
+ const env = { ...inherited };
14810
+ const changedNames = /* @__PURE__ */ new Set();
14811
+ if (tokens[0] === "env") {
14812
+ let index = 1;
14813
+ let malformed = false;
14814
+ while (index < tokens.length) {
14815
+ const option = tokens[index] ?? "";
14816
+ if (option === "-u" || option === "--unset") {
14817
+ const name = tokens[index + 1] ?? "";
14818
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
14819
+ malformed = true;
14820
+ index += 1;
14821
+ break;
14803
14822
  }
14804
- opacity = joinNestedOpacity(opacity, nestedSegment);
14823
+ delete env[name];
14824
+ changedNames.add(name);
14825
+ index += 2;
14826
+ continue;
14805
14827
  }
14806
- }
14807
- return shellSegment(commandRedacted, head, requirements, opacity, signals);
14808
- }
14809
- const recursive = decodeRecursiveInvocationTokens(decoderTokens);
14810
- if (recursive.kind === "static" && opacity !== "opaque" && opacity !== "unparseable") {
14811
- requirements.push(
14812
- processRequirement(recursive.interpreter, "spawn", commandRedacted, [
14813
- "shell.recursive_wrapper",
14814
- "dynamic_shell_evaluation"
14815
- ])
14816
- );
14817
- if (recursive.script !== "") {
14818
- const nested = lowerTopLevelSegments(recursive.script, {
14819
- ...context,
14820
- command: recursive.script,
14821
- env,
14822
- depth: context.depth + 1
14823
- });
14824
- for (const nestedSegment of nested) {
14825
- requirements.push(
14826
- ...nestedSegment.requirements.map(
14827
- (entry) => withInnerProvenance(entry, recursive.script, head, commandRedacted)
14828
- )
14829
- );
14830
- for (const signal of nestedSegment.signals) signals.add(signal);
14828
+ if (option.startsWith("--unset=")) {
14829
+ const name = option.slice("--unset=".length);
14830
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
14831
+ malformed = true;
14832
+ index += 1;
14833
+ break;
14834
+ }
14835
+ delete env[name];
14836
+ changedNames.add(name);
14837
+ index += 1;
14838
+ continue;
14831
14839
  }
14832
- }
14833
- signals.add("shell.recursive_wrapper");
14834
- signals.add("dynamic_shell_evaluation");
14835
- return shellSegment(commandRedacted, head, requirements, "recursive", signals);
14836
- }
14837
- if (recursive.kind === "dynamic" || recursive.kind === "indeterminate") {
14838
- const recursiveSignals = [
14839
- recursive.signal,
14840
- ...recursive.kind === "dynamic" ? ["dynamic_shell_evaluation"] : []
14841
- ];
14842
- requirements.push(
14843
- processRequirement(recursive.interpreter, "spawn", commandRedacted, recursiveSignals),
14844
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
14845
- ...recursiveSignals
14846
- ])
14847
- );
14848
- for (const signal of recursiveSignals) signals.add(signal);
14849
- return shellSegment(
14850
- commandRedacted,
14851
- head,
14852
- requirements,
14853
- joinEffectOpacity(opacity, "opaque"),
14854
- signals
14855
- );
14856
- }
14857
- const compose = decodeDockerComposeRun(decoderTokens);
14858
- if (compose.kind === "recursive" && opacity !== "opaque" && opacity !== "unparseable") {
14859
- requirements.push(
14860
- processRequirement(head, "spawn", commandRedacted, ["process.docker_compose_run"])
14861
- );
14862
- if (compose.script !== "") {
14863
- const nested = lowerTopLevelSegments(compose.script, {
14864
- ...context,
14865
- command: compose.script,
14866
- env,
14867
- depth: context.depth + 1
14868
- });
14869
- for (const nestedSegment of nested) {
14870
- requirements.push(
14871
- ...nestedSegment.requirements.map(
14872
- (entry) => withInnerProvenance(entry, compose.script, head, commandRedacted)
14873
- )
14874
- );
14875
- for (const signal of nestedSegment.signals) signals.add(signal);
14876
- opacity = joinNestedOpacity(opacity, nestedSegment);
14840
+ if (option === "-i" || option === "--ignore-environment") {
14841
+ for (const name of Object.keys(env)) {
14842
+ delete env[name];
14843
+ }
14844
+ changedNames.add("*");
14845
+ index += 1;
14846
+ continue;
14877
14847
  }
14848
+ if (option === "--") {
14849
+ index += 1;
14850
+ break;
14851
+ }
14852
+ if (option.startsWith("-")) {
14853
+ malformed = true;
14854
+ index += 1;
14855
+ break;
14856
+ }
14857
+ break;
14878
14858
  }
14879
- signals.add("process.docker_compose_run");
14880
- return shellSegment(commandRedacted, head, requirements, "recursive", signals);
14881
- }
14882
- if (compose.kind === "dynamic" || compose.kind === "indeterminate") {
14883
- requirements.push(
14884
- processRequirement(head, "spawn", commandRedacted, ["process.docker_compose_run"]),
14885
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
14886
- compose.signal
14887
- ])
14888
- );
14889
- signals.add("process.docker_compose_run");
14890
- signals.add(compose.signal);
14891
- return shellSegment(
14892
- commandRedacted,
14893
- head,
14894
- requirements,
14895
- joinEffectOpacity(opacity, "opaque"),
14896
- signals
14897
- );
14898
- }
14899
- const launcher = resolveLauncherRecipe({
14900
- tokens,
14901
- cwd: context.cwd,
14902
- repoRoot: context.repoRoot,
14903
- depth: context.depth
14904
- });
14905
- if (launcher) {
14906
- signals.add(`launcher.${launcher.reason}`);
14907
- requirements.push(
14908
- requirement2(
14909
- "process.exec",
14910
- "process.exec",
14911
- { kind: "executable", command: head, operation: "spawn" },
14912
- commandRedacted,
14913
- ["launcher.invoke"]
14914
- )
14915
- );
14916
- for (const recipe of launcher.recipes) {
14917
- const nested = lowerTopLevelSegments(recipe, {
14918
- ...context,
14919
- command: recipe,
14920
- env,
14921
- depth: context.depth + 1
14922
- });
14923
- for (const nestedSegment of nested) {
14924
- requirements.push(
14925
- ...nestedSegment.requirements.map(
14926
- (entry) => withInnerProvenance(entry, recipe, head, commandRedacted)
14927
- )
14928
- );
14929
- for (const signal of nestedSegment.signals) {
14930
- signals.add(signal);
14931
- }
14932
- opacity = joinNestedOpacity(opacity, nestedSegment);
14859
+ while (index < tokens.length) {
14860
+ const token = tokens[index] ?? "";
14861
+ const match = ENV_PREFIX_PATTERN2.exec(token);
14862
+ if (!match) {
14863
+ break;
14864
+ }
14865
+ const [, name, value] = match;
14866
+ if (name) {
14867
+ env[name] = expandKnownVariables(value ?? "", env);
14868
+ changedNames.add(name);
14933
14869
  }
14870
+ index += 1;
14934
14871
  }
14935
- if (launcher.opaque || launcher.recipes.length === 0) {
14936
- requirements.push(
14937
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
14938
- `launcher.${launcher.reason}`
14939
- ])
14940
- );
14941
- opacity = joinEffectOpacity(opacity, "opaque");
14942
- }
14943
- return shellSegment(commandRedacted, head, requirements, opacity, signals);
14872
+ const commandTokens = tokens.slice(index);
14873
+ return {
14874
+ env,
14875
+ commandTokens,
14876
+ malformed: malformed || commandTokens.length === 0,
14877
+ changedNames
14878
+ };
14944
14879
  }
14945
- const egress = decodeEgressEffects({
14946
- tokens,
14947
- cwd: context.cwd,
14948
- segment: commandRedacted
14949
- });
14950
- if (egress) {
14951
- requirements.push(...egress);
14952
- for (const signal of egress.flatMap((entry) => entry.evidence.signals)) {
14953
- signals.add(signal);
14880
+ for (const token of tokens) {
14881
+ const match = ENV_PREFIX_PATTERN2.exec(token);
14882
+ if (!match) {
14883
+ break;
14954
14884
  }
14955
- } else {
14956
- const git = decodeGitEffects({
14957
- tokens,
14958
- cwd: context.cwd,
14959
- repoRoot: context.repoRoot,
14960
- segment: commandRedacted
14961
- });
14962
- if (git) {
14963
- requirements.push(...git);
14964
- for (const signal of git.flatMap((entry) => entry.evidence.signals)) {
14965
- signals.add(signal);
14966
- }
14967
- } else {
14968
- requirements.push(
14969
- ...decodeProcessOrFilesystem({
14970
- tokens,
14971
- head,
14972
- env,
14973
- cwd: context.cwd,
14974
- repoRoot: context.repoRoot,
14975
- segment: commandRedacted
14976
- })
14977
- );
14885
+ const [, name, value] = match;
14886
+ if (name) {
14887
+ env[name] = expandKnownVariables(value ?? "", env);
14888
+ changedNames.add(name);
14978
14889
  }
14979
14890
  }
14980
- const environmentSignals = effectChangingEnvironmentSignals(head, env, environment.changedNames);
14981
- if (environmentSignals.length > 0) {
14982
- requirements.push(
14983
- requirement2(
14984
- "indeterminate",
14985
- "indeterminate",
14986
- { kind: "unknown" },
14987
- commandRedacted,
14988
- environmentSignals
14989
- )
14990
- );
14991
- opacity = joinEffectOpacity(opacity, "opaque");
14992
- for (const signal of environmentSignals) {
14993
- signals.add(signal);
14891
+ return { env, malformed: false, changedNames };
14892
+ }
14893
+ function expandKnownVariables(token, env) {
14894
+ return token.replace(
14895
+ /\$(?:\{([A-Za-z_][A-Za-z0-9_]*)\}|([A-Za-z_][A-Za-z0-9_]*))/g,
14896
+ (raw, a, b) => {
14897
+ const value = env[a ?? b];
14898
+ return value === void 0 ? raw : value;
14994
14899
  }
14900
+ );
14901
+ }
14902
+ function stripRedirects(tokens) {
14903
+ const stripped = [];
14904
+ for (let index = 0; index < tokens.length; index += 1) {
14905
+ const token = tokens[index] ?? "";
14906
+ if (isFdDuplication(token)) {
14907
+ continue;
14908
+ }
14909
+ if (!isRedirectOperator(token)) {
14910
+ stripped.push(token);
14911
+ continue;
14912
+ }
14913
+ index += 1;
14995
14914
  }
14996
- if (context.pipeToShellSegment) {
14997
- signals.add("pipe_to_shell");
14998
- ensureRequirement(
14999
- requirements,
15000
- requirement2(
15001
- "process.exec",
15002
- "process.exec",
15003
- { kind: "executable", command: head || "sh", operation: "spawn" },
15004
- commandRedacted,
15005
- ["pipe_to_shell"]
15006
- )
15007
- );
15008
- ensureRequirement(
15009
- requirements,
15010
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
15011
- "pipe_to_shell"
15012
- ])
15013
- );
15014
- opacity = joinEffectOpacity(opacity, "opaque");
15015
- }
15016
- if (opacity === "unparseable" || opacity === "opaque") {
15017
- ensureRequirement(
15018
- requirements,
15019
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
15020
- `shell.${opacity}`
15021
- ])
15022
- );
15023
- }
15024
- return shellSegment(commandRedacted, head, requirements, opacity, signals);
14915
+ return stripped;
15025
14916
  }
15026
- function decodePackageExec(peel, cwd, repoRoot, segment) {
15027
- if (peel.opaque) {
15028
- return [
15029
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
15030
- peel.reason,
15031
- ...peel.signals
15032
- ])
15033
- ];
14917
+ function stripStructuredRedirects(tokens) {
14918
+ const stripped = [];
14919
+ for (let index = 0; index < tokens.length; index += 1) {
14920
+ const token = tokens[index];
14921
+ if (!token) continue;
14922
+ if (isFdDuplication(token.value)) {
14923
+ continue;
14924
+ }
14925
+ if (!isRedirectOperator(token.value)) {
14926
+ stripped.push(token);
14927
+ continue;
14928
+ }
14929
+ index += 1;
15034
14930
  }
15035
- if (peel.reason === "npx_wrapper_readonly") {
15036
- return [];
14931
+ return stripped;
14932
+ }
14933
+ function alignStructuredTokens(tokens, values) {
14934
+ if (values.length === 0) return [];
14935
+ for (let start = tokens.length - values.length; start >= 0; start -= 1) {
14936
+ const candidate = tokens.slice(start);
14937
+ if (candidate.length === values.length && candidate.every((token, index) => token.value === values[index])) {
14938
+ return candidate;
14939
+ }
15037
14940
  }
15038
- const binHead = peel.innerTokens[0] ?? "";
15039
- const metadataOnly = packageExecInnerIsMetadata(peel);
15040
- const local = !peel.forceAcquire ? resolveLocalBin(binHead, cwd, repoRoot) : null;
15041
- if (local) {
15042
- return metadataOnly ? [
15043
- requirement2(
15044
- "process.exec",
15045
- "process.exec",
15046
- { kind: "executable", command: local.path, operation: "inspect" },
15047
- segment,
15048
- ["package_exec.delegate", "package_exec.local_bin_resolved"]
15049
- )
15050
- ] : [];
14941
+ return [];
14942
+ }
14943
+ function resolvePathOperand(operand, cwd) {
14944
+ if (operand === "~") {
14945
+ return process.env.HOME ?? operand;
15051
14946
  }
15052
- const networkResources = peel.acquisitionSpecs.map((spec) => classifyPackageAcquisitionSpec(spec)).filter((source) => source.kind !== "local").map(
15053
- (source) => source.kind === "registry" ? {
15054
- kind: "network",
15055
- host: "registry.npmjs.org",
15056
- protocol: "registry",
15057
- mode: "read",
15058
- payload: "none"
15059
- } : {
15060
- kind: "network",
15061
- host: source.host,
15062
- ...source.port ? { port: source.port } : {},
15063
- protocol: source.protocol,
15064
- mode: "read",
15065
- payload: "none"
15066
- }
15067
- );
15068
- if (networkResources.length === 0) {
15069
- networkResources.push({
15070
- kind: "network",
15071
- host: "registry.npmjs.org",
15072
- protocol: "registry",
15073
- mode: "read",
15074
- payload: "none"
15075
- });
14947
+ if (operand.startsWith("~/")) {
14948
+ return path43.join(process.env.HOME ?? "~", operand.slice(2));
15076
14949
  }
15077
- return [
15078
- ...networkResources.map(
15079
- (resource) => requirement2("network.acquire", "network.connect", resource, segment, [
15080
- "package_acquire_possible"
15081
- ])
15082
- ),
15083
- requirement2(
15084
- "fs.write",
15085
- "fs.write",
15086
- { kind: "package-cache", manager: peel.launcher === "pnpm" ? "pnpm" : "npm" },
15087
- segment,
15088
- ["package_cache_write"]
15089
- ),
15090
- ...metadataOnly ? [
15091
- requirement2(
15092
- "process.exec",
15093
- "process.exec",
15094
- { kind: "executable", command: binHead || peel.launcher, operation: "inspect" },
15095
- segment,
15096
- ["package_exec.delegate"]
15097
- )
15098
- ] : []
15099
- ];
14950
+ return path43.resolve(cwd, operand);
15100
14951
  }
15101
- function packageExecInnerIsMetadata(peel) {
15102
- const args = peel.innerTokens.slice(1);
15103
- return args.length > 0 && args.every((arg) => ["--help", "--version", "-V", "-h", "-v"].includes(arg));
14952
+ function isOptionToken(value) {
14953
+ return value.startsWith("-") || value.startsWith("+");
15104
14954
  }
15105
- var METADATA_ONLY_FLAGS = /* @__PURE__ */ new Set(["--version", "-v", "-V", "--help", "-h"]);
15106
- function isMetadataOnlyArgv(argv) {
15107
- return argv.length > 0 && argv.every((token) => METADATA_ONLY_FLAGS.has(token));
14955
+ function isShellHead(head) {
14956
+ return head === "bash" || head === "sh" || head === "zsh" || head === "dash" || head === "fish";
14957
+ }
14958
+ function pipeToShell(command) {
14959
+ return /(?:^|[|;&]\s*)(?:bash|sh|zsh|dash|fish)(?:\s|$)/.test(command) && /\|/.test(command);
15108
14960
  }
15109
14961
  function executableBaseName(head) {
15110
- return path42.basename(head);
14962
+ return path43.basename(head);
15111
14963
  }
15112
- var RAILS_READ_ONLY_SUBCOMMANDS = /* @__PURE__ */ new Set(["routes", "middleware", "stats", "about", "version"]);
15113
- function railsReadOnlySubcommand(args) {
15114
- const subcommand = args[0];
15115
- if (!subcommand || subcommand.startsWith("-")) {
15116
- return false;
15117
- }
15118
- return RAILS_READ_ONLY_SUBCOMMANDS.has(subcommand);
14964
+ function isMetadataOnlyArgv(argv) {
14965
+ return argv.length > 0 && argv.every((token) => METADATA_ONLY_FLAGS.has(token));
15119
14966
  }
15120
- function isRubyTestScript(scriptPath) {
15121
- const base = path42.basename(scriptPath);
15122
- return base.endsWith("_test.rb") || base.endsWith("_spec.rb");
14967
+ function databaseEndpoint(raw) {
14968
+ if (!raw || raw.includes("$")) {
14969
+ return null;
14970
+ }
14971
+ try {
14972
+ const url = new URL(raw);
14973
+ if (!url.hostname || !url.protocol) {
14974
+ return null;
14975
+ }
14976
+ return {
14977
+ host: url.hostname.toLowerCase(),
14978
+ protocol: url.protocol.slice(0, -1),
14979
+ ...url.port ? { port: Number(url.port) } : {}
14980
+ };
14981
+ } catch {
14982
+ return null;
14983
+ }
15123
14984
  }
15124
- function parseRubyTestInvocation(args) {
15125
- const includePaths = [];
15126
- for (let index = 0; index < args.length; index += 1) {
15127
- const arg = args[index] ?? "";
15128
- if (arg === "-e" || arg === "-r") {
15129
- return null;
14985
+
14986
+ // src/core/effect-ir/shell-lower/augment.ts
14987
+ function addRedirectEffects(requirements, tokens, env, context, segment) {
14988
+ for (let index = 0; index < tokens.length; index += 1) {
14989
+ const operator = tokens[index] ?? "";
14990
+ if (!isRedirectOperator(operator) || isFdDuplication(operator)) {
14991
+ continue;
15130
14992
  }
15131
- if (arg === "-I") {
15132
- const includePath = args[index + 1];
15133
- if (!includePath) {
15134
- return null;
15135
- }
15136
- includePaths.push(includePath);
14993
+ const rawTarget = tokens[index + 1];
14994
+ if (!rawTarget) {
14995
+ requirements.push(
14996
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
14997
+ "shell.redirect_target_missing"
14998
+ ])
14999
+ );
15000
+ continue;
15001
+ }
15002
+ if (operator.includes("<<")) {
15003
+ requirements.push(
15004
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
15005
+ "shell.heredoc_opaque"
15006
+ ])
15007
+ );
15137
15008
  index += 1;
15138
15009
  continue;
15139
15010
  }
15140
- if (arg.startsWith("-I") && arg.length > 2) {
15141
- includePaths.push(arg.slice(2));
15011
+ const target = expandKnownVariables(rawTarget, env);
15012
+ const resolved = resolvePathOperand(target, context.cwd);
15013
+ if (resolved === "/dev/null") {
15014
+ index += 1;
15142
15015
  continue;
15143
15016
  }
15144
- if (arg.startsWith("-")) {
15145
- if (arg === "-n") {
15146
- if (!args[index + 1]) {
15147
- return null;
15148
- }
15149
- index += 1;
15150
- continue;
15151
- }
15152
- if (arg.startsWith("-n")) {
15153
- continue;
15154
- }
15155
- return null;
15017
+ if (operator.includes("<")) {
15018
+ requirements.push(
15019
+ requirement2("fs.read", "fs.read", { kind: "path", path: resolved }, segment, [
15020
+ "shell.input_redirect"
15021
+ ])
15022
+ );
15023
+ addSecretRead(requirements, resolved, segment);
15156
15024
  }
15157
- if (isRubyTestScript(arg)) {
15158
- return { includePaths, scriptPath: arg };
15025
+ if (operator.includes(">")) {
15026
+ addWriteEffects(requirements, resolved, segment, ["shell.output_redirect"]);
15159
15027
  }
15160
- return null;
15028
+ index += 1;
15161
15029
  }
15162
- return null;
15163
15030
  }
15164
- function isRubocopMutating(args) {
15165
- return args.some(
15166
- (arg) => arg === "-A" || arg === "-a" || arg === "--auto-correct" || arg === "--autocorrect" || arg.startsWith("--auto-correct-all") || arg.startsWith("--autocorrect-all")
15167
- );
15031
+ function effectChangingEnvironmentSignals(head, env, changedNames) {
15032
+ if (head === "curl" && (env.CURL_HOME || changedNames.has("CURL_HOME") || changedNames.has("HOME") || changedNames.has("XDG_CONFIG_HOME"))) {
15033
+ return ["egress.curl.environment_config_override"];
15034
+ }
15035
+ if (head === "wget" && (env.WGETRC || changedNames.has("WGETRC") || changedNames.has("HOME"))) {
15036
+ return ["egress.wget.environment_config_override"];
15037
+ }
15038
+ if (head !== "git") {
15039
+ return [];
15040
+ }
15041
+ const hazardousNames = /* @__PURE__ */ new Set([
15042
+ "GIT_ASKPASS",
15043
+ "GIT_ALTERNATE_OBJECT_DIRECTORIES",
15044
+ "GIT_CEILING_DIRECTORIES",
15045
+ "GIT_COMMON_DIR",
15046
+ "GIT_CONFIG",
15047
+ "GIT_CONFIG_GLOBAL",
15048
+ "GIT_CONFIG_SYSTEM",
15049
+ "GIT_DIR",
15050
+ "GIT_DISCOVERY_ACROSS_FILESYSTEM",
15051
+ "GIT_EDITOR",
15052
+ "GIT_EXEC_PATH",
15053
+ "GIT_EXTERNAL_DIFF",
15054
+ "GIT_INDEX_FILE",
15055
+ "GIT_NAMESPACE",
15056
+ "GIT_OBJECT_DIRECTORY",
15057
+ "GIT_PAGER",
15058
+ "GIT_PROXY_COMMAND",
15059
+ "GIT_QUARANTINE_PATH",
15060
+ "GIT_SEQUENCE_EDITOR",
15061
+ "GIT_SHALLOW_FILE",
15062
+ "GIT_SSH",
15063
+ "GIT_SSH_COMMAND",
15064
+ "GIT_TEMPLATE_DIR",
15065
+ "GIT_WORK_TREE",
15066
+ "SSH_ASKPASS"
15067
+ ]);
15068
+ const overridden = changedNames.has("HOME") || changedNames.has("XDG_CONFIG_HOME") || Object.entries(env).some(([name, value]) => {
15069
+ if (!value) {
15070
+ return false;
15071
+ }
15072
+ if (hazardousNames.has(name) || /^GIT_CONFIG_(?:KEY|VALUE)_\d+$/.test(name) || /^GIT_TRACE/.test(name)) {
15073
+ return true;
15074
+ }
15075
+ return name === "GIT_CONFIG_COUNT" && value !== "0";
15076
+ });
15077
+ return overridden ? ["git.environment_execution_or_config_override"] : [];
15168
15078
  }
15169
- function decodeBundleExecInner(innerHead, innerArgs, segment) {
15170
- const innerBase = executableBaseName(innerHead);
15171
- if (innerBase === "rubocop") {
15172
- const mutating = isRubocopMutating(innerArgs);
15079
+
15080
+ // src/core/effect-ir/shell-lower/decode-process.ts
15081
+ import path47 from "node:path";
15082
+
15083
+ // src/core/effect-ir/shell-lower/decoders/belay.ts
15084
+ import path44 from "node:path";
15085
+ function decodeBelay(args, repoRoot, segment) {
15086
+ const [section, operation, key] = args;
15087
+ const judgeCommand = section === "judge" && operation !== void 0 && ["consent", "list", "status", "test", "use"].includes(operation);
15088
+ const configRead = section === "config" && (operation === void 0 || operation === "list" || operation === "get" && key?.startsWith("judge."));
15089
+ const configJudgeMutation = section === "config" && (["set", "unset"].includes(operation ?? "") && key?.startsWith("judge.") || operation === "credential" && key === "mode");
15090
+ if (judgeCommand || configRead || configJudgeMutation) {
15173
15091
  return [
15174
- processRequirement(
15175
- innerHead,
15176
- mutating ? "spawn" : "inspect",
15092
+ processRequirement("belay", "inspect", segment, [
15093
+ "belay_control_plane_command",
15094
+ configJudgeMutation ? "belay.config_judge_mutation" : "belay.config_read"
15095
+ ])
15096
+ ];
15097
+ }
15098
+ if (section === "config" && ["set", "unset", "credential"].includes(operation ?? "")) {
15099
+ return [
15100
+ requirement2(
15101
+ "control_plane.write",
15102
+ "control_plane.write",
15103
+ { kind: "path", path: path44.join(repoRoot, ".belay-control-plane") },
15177
15104
  segment,
15178
- mutating ? ["process.linter.mutating"] : ["process.inspect.linter"]
15105
+ ["belay.config_non_judge_mutation"]
15179
15106
  )
15180
15107
  ];
15181
15108
  }
15182
- if (innerBase === "rspec") {
15183
- const targetArgs = innerArgs.filter((arg) => !arg.startsWith("-"));
15184
- if (targetArgs.length === 0) {
15185
- return null;
15109
+ return [processRequirement("belay", "spawn", segment, ["process.known_local_spawn"])];
15110
+ }
15111
+
15112
+ // src/core/effect-ir/shell-lower/decoders/builtins.ts
15113
+ function decodeSetBuiltin(args) {
15114
+ let index = 0;
15115
+ while (index < args.length) {
15116
+ const arg = args[index] ?? "";
15117
+ if (arg === "--") {
15118
+ index += 1;
15119
+ continue;
15186
15120
  }
15187
- return [processRequirement(innerHead, "spawn", segment, ["process.test_runner.rspec"])];
15121
+ if (arg === "-o" || arg === "+o") {
15122
+ if (!args[index + 1]) {
15123
+ return false;
15124
+ }
15125
+ index += 2;
15126
+ continue;
15127
+ }
15128
+ if (/^[-+][A-Za-z0-9]+$/.test(arg)) {
15129
+ index += 1;
15130
+ continue;
15131
+ }
15132
+ return false;
15188
15133
  }
15189
- return null;
15134
+ return true;
15190
15135
  }
15191
- function decodeRuby(args, cwd, repoRoot, segment) {
15192
- const parsed = parseRubyTestInvocation(args);
15193
- if (!parsed) {
15194
- return unsupportedProcess("ruby", segment, "process.ruby_grammar_incomplete");
15195
- }
15196
- const scriptPath = resolvePathOperand(parsed.scriptPath, cwd);
15197
- if (!pathWithinRoot(canonicalPath(repoRoot), canonicalPath(scriptPath))) {
15198
- return unsupportedProcess("ruby", segment, "process.ruby_outside_repo");
15136
+ function decodeShellControlBuiltin(head, args) {
15137
+ if (head === "set") {
15138
+ return decodeSetBuiltin(args) ? [] : null;
15199
15139
  }
15200
- for (const includePath of parsed.includePaths) {
15201
- const resolvedInclude = resolvePathOperand(includePath, cwd);
15202
- if (!pathWithinRoot(canonicalPath(repoRoot), canonicalPath(resolvedInclude))) {
15203
- return unsupportedProcess("ruby", segment, "process.ruby_outside_repo");
15140
+ if (head === "wait") {
15141
+ if (args.length === 0 || args.every((arg) => /^\d+$/.test(arg))) {
15142
+ return [];
15204
15143
  }
15144
+ return null;
15205
15145
  }
15206
- const lowered = [
15207
- processRequirement("ruby", "spawn", segment, ["process.test_runner.minitest"]),
15208
- requirement2("fs.read", "fs.read", { kind: "path", path: scriptPath }, segment, [
15209
- "ruby.minitest_script_read"
15210
- ])
15211
- ];
15212
- for (const includePath of parsed.includePaths) {
15213
- lowered.push(
15214
- requirement2(
15215
- "fs.read",
15216
- "fs.read",
15217
- { kind: "path", path: resolvePathOperand(includePath, cwd) },
15218
- segment,
15219
- ["ruby.minitest_load_path_read"]
15220
- )
15221
- );
15146
+ if (head === "exit") {
15147
+ if (args.length === 0 || args.length === 1 && /^-?\d+$/.test(args[0] ?? "")) {
15148
+ return [];
15149
+ }
15150
+ return null;
15222
15151
  }
15223
- return lowered;
15152
+ return null;
15224
15153
  }
15225
- function decodeRuntimeMetadataProcess(head, args, segment) {
15226
- if (head === "bundle") {
15227
- if (args.length === 1 && isMetadataOnlyArgv(args)) {
15228
- return [processRequirement(head, "inspect", segment, ["process.inspect.runtime_metadata"])];
15154
+ function validLsof(args) {
15155
+ const flagsWithoutValues = /* @__PURE__ */ new Set(["-a", "-b", "-l", "-n", "-P", "-R", "-t", "-V"]);
15156
+ const flagsWithValues = /* @__PURE__ */ new Set(["-c", "-d", "-F", "-g", "-p", "-T", "-u", "+d", "+D"]);
15157
+ for (let index = 0; index < args.length; index += 1) {
15158
+ const arg = args[index] ?? "";
15159
+ if (flagsWithoutValues.has(arg) || arg === "-i") {
15160
+ continue;
15229
15161
  }
15230
- if (args[0] === "exec" && args.length >= 2) {
15231
- const innerHead = args[1] ?? "";
15232
- const innerArgs = args.slice(2);
15233
- const innerBase = executableBaseName(innerHead);
15234
- if ((innerBase === "rails" || innerBase === "rake") && railsReadOnlySubcommand(innerArgs)) {
15235
- return [
15236
- processRequirement(innerHead, "inspect", segment, ["process.inspect.rails_read_only"])
15237
- ];
15238
- }
15239
- if (isMetadataOnlyArgv(innerArgs)) {
15240
- return [
15241
- processRequirement(innerHead, "inspect", segment, [
15242
- "process.inspect.bundle_exec_metadata"
15243
- ])
15244
- ];
15245
- }
15246
- const bundleExecInner = decodeBundleExecInner(innerHead, innerArgs, segment);
15247
- if (bundleExecInner) {
15248
- return bundleExecInner;
15162
+ if (flagsWithValues.has(arg)) {
15163
+ if (!args[index + 1] || isOptionToken(args[index + 1] ?? "")) {
15164
+ return false;
15249
15165
  }
15166
+ index += 1;
15167
+ continue;
15168
+ }
15169
+ 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)) {
15170
+ continue;
15250
15171
  }
15251
- return null;
15252
- }
15253
- if ((head === "ruby" || head === "yarn") && args.length >= 1 && isMetadataOnlyArgv(args)) {
15254
- return [processRequirement(head, "inspect", segment, ["process.inspect.runtime_metadata"])];
15255
- }
15256
- if (head === "make" && args.includes("-n")) {
15257
- return [processRequirement(head, "inspect", segment, ["process.inspect.make_dry_run"])];
15258
- }
15259
- const base = executableBaseName(head);
15260
- if ((base === "rails" || base === "rake") && railsReadOnlySubcommand(args)) {
15261
- return [processRequirement(head, "inspect", segment, ["process.inspect.rails_read_only"])];
15172
+ return false;
15262
15173
  }
15263
- return null;
15174
+ return true;
15264
15175
  }
15265
- function decodeSetBuiltin(args) {
15266
- let index = 0;
15267
- while (index < args.length) {
15176
+ function validPs(args) {
15177
+ const bareForms = /* @__PURE__ */ new Set(["aux", "ax", "x", "u"]);
15178
+ const flagsWithoutValues = /* @__PURE__ */ new Set(["-A", "-a", "-d", "-e", "-f", "-j", "-l", "-T", "-x"]);
15179
+ const flagsWithValues = /* @__PURE__ */ new Set([
15180
+ "--format",
15181
+ "--group",
15182
+ "--pid",
15183
+ "--ppid",
15184
+ "--sort",
15185
+ "--tty",
15186
+ "--user"
15187
+ ]);
15188
+ for (let index = 0; index < args.length; index += 1) {
15268
15189
  const arg = args[index] ?? "";
15269
- if (arg === "--") {
15270
- index += 1;
15190
+ if (bareForms.has(arg) || flagsWithoutValues.has(arg)) {
15271
15191
  continue;
15272
15192
  }
15273
- if (arg === "-o" || arg === "+o") {
15274
- if (!args[index + 1]) {
15193
+ if (flagsWithValues.has(arg)) {
15194
+ if (!args[index + 1] || isOptionToken(args[index + 1] ?? "")) {
15275
15195
  return false;
15276
15196
  }
15277
- index += 2;
15197
+ index += 1;
15278
15198
  continue;
15279
15199
  }
15280
- if (/^[-+][A-Za-z0-9]+$/.test(arg)) {
15281
- index += 1;
15200
+ if (/^(?:--format|--group|--pid|--ppid|--sort|--tty|--user)=.+$/.test(arg)) {
15282
15201
  continue;
15283
15202
  }
15203
+ if (/^-[^-]/.test(arg)) {
15204
+ const noValue = /* @__PURE__ */ new Set(["A", "a", "d", "e", "f", "j", "l", "T", "x"]);
15205
+ const withValue = /* @__PURE__ */ new Set(["G", "g", "N", "o", "p", "t", "U", "u"]);
15206
+ const characters = arg.slice(1);
15207
+ let valid = true;
15208
+ for (let optionIndex = 0; optionIndex < characters.length; optionIndex += 1) {
15209
+ const option = characters[optionIndex] ?? "";
15210
+ if (noValue.has(option)) {
15211
+ continue;
15212
+ }
15213
+ if (withValue.has(option)) {
15214
+ if (optionIndex === characters.length - 1) {
15215
+ if (!args[index + 1] || isOptionToken(args[index + 1] ?? "")) {
15216
+ return false;
15217
+ }
15218
+ index += 1;
15219
+ }
15220
+ break;
15221
+ }
15222
+ valid = false;
15223
+ break;
15224
+ }
15225
+ if (valid) {
15226
+ continue;
15227
+ }
15228
+ }
15284
15229
  return false;
15285
15230
  }
15286
15231
  return true;
15287
15232
  }
15288
- function decodeShellControlBuiltin(head, args) {
15289
- if (head === "set") {
15290
- return decodeSetBuiltin(args) ? [] : null;
15233
+ function validFilesystemTest(head, args) {
15234
+ const operands = head === "[" && args.at(-1) === "]" ? args.slice(0, -1) : args;
15235
+ if (head === "[" && args.at(-1) !== "]") {
15236
+ return false;
15291
15237
  }
15292
- if (head === "wait") {
15293
- if (args.length === 0 || args.every((arg) => /^\d+$/.test(arg))) {
15294
- return [];
15238
+ return operands.length === 2 && (/* @__PURE__ */ new Set(["-b", "-c", "-d", "-e", "-f", "-h", "-L", "-r", "-s", "-w", "-x"])).has(
15239
+ operands[0] ?? ""
15240
+ ) && Boolean(operands[1]);
15241
+ }
15242
+ function bashSyntaxTarget(args) {
15243
+ let syntaxOnly = false;
15244
+ let script = null;
15245
+ const shortFlags = new Set("abefhkmnptuvxBCEHPT".split(""));
15246
+ const longFlags = /* @__PURE__ */ new Set([
15247
+ "--debugger",
15248
+ "--dump-po-strings",
15249
+ "--dump-strings",
15250
+ "--help",
15251
+ "--login",
15252
+ "--noediting",
15253
+ "--noprofile",
15254
+ "--norc",
15255
+ "--posix",
15256
+ "--restricted",
15257
+ "--verbose",
15258
+ "--version"
15259
+ ]);
15260
+ for (let index = 0; index < args.length; index += 1) {
15261
+ const arg = args[index] ?? "";
15262
+ if (arg === "-c" || arg === "-lc") {
15263
+ return null;
15295
15264
  }
15296
- return null;
15297
- }
15298
- if (head === "exit") {
15299
- if (args.length === 0 || args.length === 1 && /^-?\d+$/.test(args[0] ?? "")) {
15300
- return [];
15265
+ if (arg === "-O" || arg === "+O" || arg === "--rcfile" || arg === "--init-file") {
15266
+ if (!args[index + 1] || isOptionToken(args[index + 1] ?? "")) {
15267
+ return null;
15268
+ }
15269
+ index += 1;
15270
+ continue;
15301
15271
  }
15302
- return null;
15272
+ 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) {
15273
+ continue;
15274
+ }
15275
+ if (longFlags.has(arg)) {
15276
+ continue;
15277
+ }
15278
+ if (/^[+-][A-Za-z]+$/.test(arg)) {
15279
+ const options = arg.slice(1);
15280
+ if ([...options].some((option) => !shortFlags.has(option))) {
15281
+ return null;
15282
+ }
15283
+ syntaxOnly ||= options.includes("n");
15284
+ continue;
15285
+ }
15286
+ if (arg === "--") {
15287
+ script = args[index + 1] ?? null;
15288
+ break;
15289
+ }
15290
+ if (arg.startsWith("-") || arg.startsWith("+")) {
15291
+ return null;
15292
+ }
15293
+ script = arg;
15294
+ break;
15303
15295
  }
15304
- return null;
15296
+ return syntaxOnly ? script : null;
15305
15297
  }
15298
+
15299
+ // src/core/effect-ir/shell-lower/decoders/docker.ts
15306
15300
  function decodeDockerComposeRun2(head, args, segment) {
15307
15301
  let composeArgs = null;
15308
15302
  let command = head;
@@ -15320,402 +15314,499 @@ function decodeDockerComposeRun2(head, args, segment) {
15320
15314
  }
15321
15315
  return unsupportedProcess(command, segment, "process.docker_compose_grammar_incomplete");
15322
15316
  }
15323
- function decodeProcessOrFilesystem(params) {
15324
- const { tokens, head, env, cwd, repoRoot, segment } = params;
15325
- const args = tokens.slice(1);
15326
- if (tokens.length > 0 && tokens.every((token) => ENV_PREFIX_PATTERN2.test(token))) {
15327
- return [];
15328
- }
15329
- const shellControl = decodeShellControlBuiltin(head, args);
15330
- if (shellControl) {
15331
- return shellControl;
15332
- }
15333
- if (isCommandInspection(tokens)) {
15334
- return [processRequirement(head, "inspect", segment, ["process.inspect.command_lookup"])];
15335
- }
15336
- if (head === "lsof") {
15337
- return validLsof(args) ? [processRequirement(head, "inspect", segment, ["process.inspect.lsof"])] : unsupportedProcess(head, segment, "process.lsof_grammar_incomplete");
15338
- }
15339
- if (head === "ps") {
15340
- return validPs(args) ? [processRequirement(head, "inspect", segment, ["process.inspect.ps"])] : unsupportedProcess(head, segment, "process.ps_grammar_incomplete");
15341
- }
15342
- if (head === "test" || head === "[") {
15343
- return validFilesystemTest(head, args) ? [processRequirement(head, "inspect", segment, ["process.inspect.filesystem_test"])] : unsupportedProcess(head, segment, "process.filesystem_test_grammar_incomplete");
15344
- }
15345
- if (head === "docker") {
15346
- if (args[0] === "info") {
15347
- return validDockerInfo(args.slice(1)) ? [processRequirement(head, "inspect", segment, ["process.inspect.docker_info"])] : unsupportedProcess(head, segment, "process.docker_info_grammar_incomplete");
15317
+ function validDockerInfo(args) {
15318
+ for (let index = 0; index < args.length; index += 1) {
15319
+ const arg = args[index] ?? "";
15320
+ if (arg === "--help") {
15321
+ continue;
15348
15322
  }
15349
- if (args[0] === "compose" && ["up", "down", "start", "stop", "restart", "create", "rm"].includes(args[1] ?? "")) {
15350
- return [processRequirement(head, "spawn", segment, ["service.local_mutation"])];
15323
+ if (arg === "--format" || arg === "-f") {
15324
+ if (!args[index + 1] || isOptionToken(args[index + 1] ?? "")) {
15325
+ return false;
15326
+ }
15327
+ index += 1;
15328
+ continue;
15351
15329
  }
15352
- if (args[0] === "push" || args[0] === "build" && args.includes("--push") || args[0] === "buildx" && args[1] === "build" && (args.includes("--push") || args.some(
15353
- (arg) => arg.startsWith("--output=type=registry") || arg.startsWith("-o=type=registry")
15354
- ))) {
15355
- return [
15356
- processRequirement(head, "spawn", segment, ["tier0_external", "docker.remote_mutation"]),
15357
- requirement2(
15358
- "network.connect",
15359
- "network.connect",
15360
- {
15361
- kind: "network",
15362
- host: "registry",
15363
- protocol: "container-registry",
15364
- mode: "mutate",
15365
- payload: "present"
15366
- },
15367
- segment,
15368
- ["tier0_external", "docker.remote_mutation"]
15369
- )
15370
- ];
15330
+ if (arg.startsWith("--format=")) {
15331
+ if (arg.slice("--format=".length).length === 0) {
15332
+ return false;
15333
+ }
15334
+ continue;
15371
15335
  }
15372
- if (args[0] === "run" || args[0] === "create" || args[0] === "pull" || args[0] === "build" || args[0] === "buildx" && args[1] === "build") {
15373
- return [
15374
- processRequirement(head, "spawn", segment, ["process.docker_spawn"]),
15375
- requirement2(
15376
- "network.acquire",
15377
- "network.connect",
15378
- {
15379
- kind: "network",
15380
- host: "unknown",
15381
- protocol: "container-registry",
15382
- mode: "ambiguous",
15383
- payload: "none"
15384
- },
15385
- segment,
15386
- ["docker.image_acquisition_possible"]
15387
- )
15388
- ];
15336
+ return false;
15337
+ }
15338
+ return true;
15339
+ }
15340
+
15341
+ // src/core/effect-ir/shell-lower/decoders/filesystem.ts
15342
+ init_git_resource_identity();
15343
+ import { lstatSync as lstatSync2, realpathSync as realpathSync4 } from "node:fs";
15344
+ import path45 from "node:path";
15345
+ function decodeCopyMove(head, args, cwd, segment) {
15346
+ const requirements = [processRequirement(head, "spawn", segment, ["process.filesystem_mutation"])];
15347
+ const operands = [];
15348
+ let targetDirectory = null;
15349
+ let recursive = false;
15350
+ let incomplete = false;
15351
+ let optionsEnded = false;
15352
+ const neutralFlags = /* @__PURE__ */ new Set([
15353
+ "-f",
15354
+ "-i",
15355
+ "-n",
15356
+ "-p",
15357
+ "-v",
15358
+ "--force",
15359
+ "--interactive",
15360
+ "--no-clobber"
15361
+ ]);
15362
+ for (let index = 0; index < args.length; index += 1) {
15363
+ const arg = args[index] ?? "";
15364
+ if (!optionsEnded && arg === "--") {
15365
+ optionsEnded = true;
15366
+ continue;
15367
+ }
15368
+ if (!optionsEnded && (arg === "-r" || arg === "-R" || arg === "--recursive")) {
15369
+ recursive = true;
15370
+ continue;
15371
+ }
15372
+ if (!optionsEnded && (arg === "-t" || arg === "--target-directory")) {
15373
+ const value = args[index + 1];
15374
+ if (!value || value.startsWith("-")) {
15375
+ incomplete = true;
15376
+ } else {
15377
+ targetDirectory = value;
15378
+ index += 1;
15379
+ }
15380
+ continue;
15389
15381
  }
15390
- return [processRequirement(head, "spawn", segment, ["process.docker_spawn"])];
15391
- }
15392
- if (isShellHead(head)) {
15393
- const syntax = bashSyntaxTarget(args);
15394
- if (syntax) {
15395
- return [
15396
- processRequirement(head, "inspect", segment, ["process.inspect.shell_syntax"]),
15397
- requirement2(
15398
- "fs.read",
15399
- "fs.read",
15400
- { kind: "path", path: path42.resolve(cwd, syntax) },
15401
- segment,
15402
- ["shell.syntax_source_read"]
15403
- )
15404
- ];
15382
+ if (!optionsEnded && arg.startsWith("--target-directory=")) {
15383
+ targetDirectory = arg.slice("--target-directory=".length) || null;
15384
+ incomplete ||= !targetDirectory;
15385
+ continue;
15405
15386
  }
15406
- return unsupportedProcess(head, segment, "process.shell_grammar_incomplete");
15407
- }
15408
- if (head === "prisma") {
15409
- return decodePrisma(args, env, repoRoot, segment);
15410
- }
15411
- const runtimeMetadata = decodeRuntimeMetadataProcess(head, args, segment);
15412
- if (runtimeMetadata) {
15413
- return runtimeMetadata;
15414
- }
15415
- if (head === "ruby") {
15416
- return decodeRuby(args, cwd, repoRoot, segment);
15417
- }
15418
- if (head === "rubocop" || head === "rspec") {
15419
- const decoded = decodeBundleExecInner(head, args, segment);
15420
- if (decoded) {
15421
- return decoded;
15387
+ if (!optionsEnded && arg.startsWith("-")) {
15388
+ if (!neutralFlags.has(arg)) {
15389
+ incomplete = true;
15390
+ }
15391
+ continue;
15422
15392
  }
15393
+ operands.push(arg);
15423
15394
  }
15424
- const dockerCompose = decodeDockerComposeRun2(head, args, segment);
15425
- if (dockerCompose) {
15426
- return dockerCompose;
15427
- }
15428
- if ((head === "npm" || head === "pnpm") && args.length === 1 && isMetadataOnlyArgv(args)) {
15429
- return [processRequirement(head, "inspect", segment, ["process.inspect.package_manager"])];
15430
- }
15431
- if (head === "belay") {
15432
- return decodeBelay(args, repoRoot, segment);
15433
- }
15434
- if (head === "tsc") {
15435
- return decodeTsc(args, cwd, segment);
15436
- }
15437
- if (head === "go") {
15438
- return decodeGo(args, segment);
15439
- }
15440
- if (head === "rsync") {
15441
- return decodeRsync(args, cwd, segment);
15442
- }
15443
- if (head === "sed") {
15444
- return decodeSed(args, cwd, segment);
15395
+ const sources = targetDirectory ? operands : operands.slice(0, -1);
15396
+ const destination = targetDirectory ?? (operands.length >= 2 ? operands.at(-1) ?? null : null);
15397
+ if (sources.length === 0 || !destination) {
15398
+ incomplete = true;
15445
15399
  }
15446
- if (head === "base64" && args.length === 1 && ["-d", "-D", "--decode"].includes(args[0] ?? "")) {
15447
- return [processRequirement(head, "inspect", segment, ["process.inspect.base64_stdin"])];
15400
+ for (const source of sources) {
15401
+ const resolved = resolvePathOperand(source, cwd);
15402
+ requirements.push(
15403
+ requirement2("fs.read", "fs.read", { kind: "path", path: resolved }, segment, [
15404
+ `${head}.source_read`
15405
+ ])
15406
+ );
15407
+ addSecretRead(requirements, resolved, segment);
15408
+ if (head === "mv") {
15409
+ addWriteEffects(requirements, resolved, segment, ["mv.source_remove"]);
15410
+ }
15448
15411
  }
15449
- if (head === "node") {
15450
- return decodeNode2(args, cwd, segment);
15412
+ if (destination) {
15413
+ addWriteEffects(requirements, resolvePathOperand(destination, cwd), segment, [
15414
+ `${head}.destination_write`
15415
+ ]);
15451
15416
  }
15452
- if (head === "vite" || head === "vite-node") {
15453
- return [processRequirement(head, "spawn", segment, ["process.local_dev_spawn"])];
15417
+ if (recursive || incomplete) {
15418
+ requirements.push(
15419
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
15420
+ recursive ? `${head}.recursive_source` : `${head}.grammar_incomplete`
15421
+ ])
15422
+ );
15454
15423
  }
15455
- if ((head === "npm" || head === "pnpm") && args[0] === "publish" || head === "terraform" && args[0] === "apply") {
15424
+ return requirements;
15425
+ }
15426
+ function decodeRm(args, cwd, repoRoot, segment) {
15427
+ const requirements = [processRequirement("rm", "spawn", segment, ["process.filesystem_mutation"])];
15428
+ const recursive = args.some(
15429
+ (arg) => arg === "-r" || arg === "-R" || arg === "--recursive" || /^-[^-]*[rR]/.test(arg)
15430
+ );
15431
+ const operands = args.filter((arg) => arg && !arg.startsWith("-"));
15432
+ if (operands.length === 0) {
15456
15433
  return [
15457
- processRequirement(head, "spawn", segment, ["tier0_external"]),
15458
- requirement2(
15459
- "network.connect",
15460
- "network.connect",
15461
- {
15462
- kind: "network",
15463
- host: "control-plane",
15464
- protocol: head,
15465
- mode: "mutate",
15466
- payload: "present"
15467
- },
15468
- segment,
15469
- ["tier0_external"]
15470
- )
15434
+ ...requirements,
15435
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
15436
+ "rm.operand_missing"
15437
+ ])
15471
15438
  ];
15472
15439
  }
15473
- if (head === "cd") {
15474
- return [];
15475
- }
15476
- if (head === "cp" || head === "mv") {
15477
- return decodeCopyMove(head, args, cwd, segment);
15478
- }
15479
- if (head === "rm") {
15480
- return decodeRm(args, cwd, repoRoot, segment);
15440
+ const identity = inspectGitResourceIdentity(repoRoot);
15441
+ for (const operand of operands) {
15442
+ const resolved = resolvePathOperand(operand, cwd);
15443
+ addWriteEffects(requirements, resolved, segment, ["filesystem.write", "rm.remove"]);
15444
+ const finalOperandIsSymlink = isSymbolicLink(resolved);
15445
+ const canonicalResolved = canonicalRmOperand(resolved, finalOperandIsSymlink);
15446
+ const targetIdentity = recursive && !finalOperandIsSymlink ? inspectGitResourceIdentity(resolved) : { status: "absent" };
15447
+ const sameIdentityTargetRoot = identity.status === "resolved" && targetIdentity.status === "resolved" && identity.identity.commonDir === targetIdentity.identity.commonDir && pathContains(canonicalResolved, targetIdentity.identity.repositoryRoot);
15448
+ if (recursive && identity.status === "resolved" && (sameIdentityTargetRoot || [
15449
+ repoRoot,
15450
+ identity.identity.repositoryRoot,
15451
+ identity.identity.gitDir,
15452
+ identity.identity.commonDir,
15453
+ identity.identity.gitEntryPath
15454
+ ].some((metadataPath) => pathContains(canonicalResolved, metadataPath)))) {
15455
+ const gitEntryPath = targetIdentity.status === "resolved" ? targetIdentity.identity.gitEntryPath : identity.identity.gitEntryPath;
15456
+ requirements.push(
15457
+ requirement2(
15458
+ "control_plane.write",
15459
+ "control_plane.write",
15460
+ { kind: "path", path: gitEntryPath },
15461
+ segment,
15462
+ ["rm.recursive_git_boundary", "tier1_catastrophic"]
15463
+ )
15464
+ );
15465
+ }
15481
15466
  }
15482
- if (head === "vitest" || head === "eslint" || head === "biome" || head === "belay") {
15483
- return [processRequirement(head, "spawn", segment, ["process.known_local_spawn"])];
15467
+ return requirements;
15468
+ }
15469
+ function canonicalRmOperand(targetPath, finalOperandIsSymlink) {
15470
+ try {
15471
+ if (finalOperandIsSymlink) {
15472
+ return path45.join(realpathSync4.native(path45.dirname(targetPath)), path45.basename(targetPath));
15473
+ }
15474
+ return realpathSync4.native(targetPath);
15475
+ } catch {
15476
+ return path45.resolve(targetPath);
15484
15477
  }
15485
- if (head === "pwd" || head === "which" || head === "whoami") {
15486
- return [processRequirement(head, "inspect", segment, ["process.pure_inspection"])];
15478
+ }
15479
+ function isSymbolicLink(targetPath) {
15480
+ try {
15481
+ return lstatSync2(targetPath).isSymbolicLink();
15482
+ } catch {
15483
+ return false;
15487
15484
  }
15488
- const readOperands = filesystemReadOperands(head, args);
15489
- if (readOperands !== null) {
15490
- const lowered = [processRequirement(head, "inspect", segment, ["process.filesystem_inspect"])];
15491
- for (const operand of readOperands) {
15492
- const resolved = resolvePathOperand(operand, cwd);
15493
- lowered.push(
15494
- requirement2("fs.read", "fs.read", { kind: "path", path: resolved }, segment, [
15495
- "filesystem.read"
15496
- ])
15497
- );
15498
- addSecretRead(lowered, resolved, segment);
15485
+ }
15486
+ function pathContains(ancestor, candidate) {
15487
+ const relative = path45.relative(path45.resolve(ancestor), path45.resolve(candidate));
15488
+ return relative === "" || !relative.startsWith("..") && !path45.isAbsolute(relative);
15489
+ }
15490
+ function filesystemReadOperands(head, args) {
15491
+ switch (head) {
15492
+ case "ls": {
15493
+ const operands = args.filter((arg) => arg && !arg.startsWith("-"));
15494
+ return operands.length > 0 ? operands : ["."];
15499
15495
  }
15500
- return lowered;
15496
+ case "find":
15497
+ return findReadOperands(args);
15498
+ case "cat":
15499
+ case "head":
15500
+ case "tail":
15501
+ case "less":
15502
+ case "stat":
15503
+ case "file":
15504
+ case "du":
15505
+ case "wc":
15506
+ return args.filter((arg) => arg && !arg.startsWith("-"));
15507
+ case "grep":
15508
+ case "rg":
15509
+ return args.slice(1).filter((arg) => arg && !arg.startsWith("-"));
15510
+ case "jq":
15511
+ return args.slice(1).filter((arg) => arg && !arg.startsWith("-"));
15512
+ default:
15513
+ return null;
15501
15514
  }
15502
- const writeOperands = filesystemWriteOperands(head, args);
15503
- if (writeOperands !== null) {
15504
- const lowered = [processRequirement(head, "spawn", segment, ["process.filesystem_mutation"])];
15505
- for (const operand of writeOperands) {
15506
- addWriteEffects(lowered, resolvePathOperand(operand, cwd), segment, ["filesystem.write"]);
15515
+ }
15516
+ function findReadOperands(args) {
15517
+ const mutatingPrimaries = /* @__PURE__ */ new Set([
15518
+ "-delete",
15519
+ "-exec",
15520
+ "-execdir",
15521
+ "-fls",
15522
+ "-fprint",
15523
+ "-fprintf",
15524
+ "-ok",
15525
+ "-okdir"
15526
+ ]);
15527
+ if (args.some(
15528
+ (arg) => mutatingPrimaries.has(arg) || [...mutatingPrimaries].some((primary) => arg.startsWith(`${primary}=`))
15529
+ )) {
15530
+ return null;
15531
+ }
15532
+ const paths = [];
15533
+ for (const arg of args) {
15534
+ if (arg.startsWith("-") || arg === "!" || arg === "(") {
15535
+ break;
15507
15536
  }
15508
- return lowered;
15537
+ paths.push(arg);
15509
15538
  }
15510
- if (head === "printf" || head === "echo" || head === "true" || head === "false" || head === ":") {
15511
- return [];
15539
+ return paths.length > 0 ? paths : ["."];
15540
+ }
15541
+ function filesystemWriteOperands(head, args) {
15542
+ const positional = args.filter((arg) => arg && !arg.startsWith("-"));
15543
+ switch (head) {
15544
+ case "touch":
15545
+ case "mkdir":
15546
+ case "rm":
15547
+ case "truncate":
15548
+ case "chmod":
15549
+ return positional;
15550
+ case "cp":
15551
+ case "mv":
15552
+ return positional.length > 0 ? [positional[positional.length - 1] ?? ""] : [];
15553
+ case "tee":
15554
+ return positional;
15555
+ default:
15556
+ return null;
15512
15557
  }
15513
- if (!head) {
15558
+ }
15559
+
15560
+ // src/core/effect-ir/shell-lower/decoders/prisma.ts
15561
+ function decodePrisma(args, env, repoRoot, segment) {
15562
+ const processEffect = processRequirement("prisma", "spawn", segment, ["process.prisma"]);
15563
+ if (args[0] === "generate") {
15514
15564
  return [
15515
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
15516
- "shell.segment_empty"
15565
+ processEffect,
15566
+ requirement2("fs.write", "fs.write", { kind: "path", path: repoRoot }, segment, [
15567
+ "prisma.generate.repo_local"
15517
15568
  ])
15518
15569
  ];
15519
15570
  }
15520
- return unsupportedProcess(head, segment, "process.grammar_unknown");
15521
- }
15522
- function decodeBelay(args, repoRoot, segment) {
15523
- const [section, operation, key] = args;
15524
- const judgeCommand = section === "judge" && operation !== void 0 && ["consent", "list", "status", "test", "use"].includes(operation);
15525
- const configRead = section === "config" && (operation === void 0 || operation === "list" || operation === "get" && key?.startsWith("judge."));
15526
- const configJudgeMutation = section === "config" && (["set", "unset"].includes(operation ?? "") && key?.startsWith("judge.") || operation === "credential" && key === "mode");
15527
- if (judgeCommand || configRead || configJudgeMutation) {
15571
+ const databaseMutation = args[0] === "migrate" || args[0] === "db" && args[1] === "seed" || args[0] === "seed";
15572
+ if (!databaseMutation) {
15528
15573
  return [
15529
- processRequirement("belay", "inspect", segment, [
15530
- "belay_control_plane_command",
15531
- configJudgeMutation ? "belay.config_judge_mutation" : "belay.config_read"
15574
+ processEffect,
15575
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
15576
+ "prisma.grammar_incomplete"
15532
15577
  ])
15533
15578
  ];
15534
15579
  }
15535
- if (section === "config" && ["set", "unset", "credential"].includes(operation ?? "")) {
15580
+ const endpoint = databaseEndpoint(env.DATABASE_URL);
15581
+ if (!endpoint) {
15536
15582
  return [
15583
+ processEffect,
15537
15584
  requirement2(
15538
- "control_plane.write",
15539
- "control_plane.write",
15540
- { kind: "path", path: path42.join(repoRoot, ".belay-control-plane") },
15585
+ "network.connect",
15586
+ "network.connect",
15587
+ {
15588
+ kind: "network",
15589
+ host: "unknown",
15590
+ protocol: "database",
15591
+ mode: "ambiguous",
15592
+ payload: "present"
15593
+ },
15541
15594
  segment,
15542
- ["belay.config_non_judge_mutation"]
15543
- )
15595
+ ["prisma.database_endpoint_unknown"]
15596
+ ),
15597
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
15598
+ "prisma.database_endpoint_unknown"
15599
+ ])
15544
15600
  ];
15545
15601
  }
15546
- return [processRequirement("belay", "spawn", segment, ["process.known_local_spawn"])];
15602
+ const network = requirement2(
15603
+ "network.connect",
15604
+ "network.connect",
15605
+ {
15606
+ kind: "network",
15607
+ ...endpoint,
15608
+ mode: "mutate",
15609
+ payload: "present"
15610
+ },
15611
+ segment,
15612
+ [LOOPBACK_HOSTS2.has(endpoint.host) ? "prisma.database_local" : "prisma.database_remote"]
15613
+ );
15614
+ if (LOOPBACK_HOSTS2.has(endpoint.host)) {
15615
+ return [processEffect, network];
15616
+ }
15617
+ return [
15618
+ processEffect,
15619
+ network,
15620
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
15621
+ "prisma.remote_database_mutation"
15622
+ ])
15623
+ ];
15547
15624
  }
15548
- function decodeTsc(args, cwd, segment) {
15549
- const metadataOnly = args.length > 0 && args.every((arg) => ["--help", "--version", "-h", "-v"].includes(arg));
15550
- const requirements = [];
15551
- let hasOutput = false;
15625
+
15626
+ // src/core/effect-ir/shell-lower/decoders/ruby.ts
15627
+ init_path_utils();
15628
+ import path46 from "node:path";
15629
+ var RAILS_READ_ONLY_SUBCOMMANDS = /* @__PURE__ */ new Set(["routes", "middleware", "stats", "about", "version"]);
15630
+ function railsReadOnlySubcommand(args) {
15631
+ const subcommand = args[0];
15632
+ if (!subcommand || subcommand.startsWith("-")) {
15633
+ return false;
15634
+ }
15635
+ return RAILS_READ_ONLY_SUBCOMMANDS.has(subcommand);
15636
+ }
15637
+ function isRubyTestScript(scriptPath) {
15638
+ const base = path46.basename(scriptPath);
15639
+ return base.endsWith("_test.rb") || base.endsWith("_spec.rb");
15640
+ }
15641
+ function parseRubyTestInvocation(args) {
15642
+ const includePaths = [];
15552
15643
  for (let index = 0; index < args.length; index += 1) {
15553
15644
  const arg = args[index] ?? "";
15554
- const inline = arg.match(/^--(?:outDir|outFile)=(.+)$/)?.[1];
15555
- const separate = (arg === "--outDir" || arg === "--outFile") && args[index + 1] ? args[index + 1] : null;
15556
- const output = inline ?? separate;
15557
- if (!output) {
15558
- continue;
15645
+ if (arg === "-e" || arg === "-r") {
15646
+ return null;
15559
15647
  }
15560
- hasOutput = true;
15561
- addWriteEffects(requirements, resolvePathOperand(output, cwd), segment, ["tsc.output"]);
15562
- if (separate) {
15648
+ if (arg === "-I") {
15649
+ const includePath = args[index + 1];
15650
+ if (!includePath) {
15651
+ return null;
15652
+ }
15653
+ includePaths.push(includePath);
15563
15654
  index += 1;
15655
+ continue;
15656
+ }
15657
+ if (arg.startsWith("-I") && arg.length > 2) {
15658
+ includePaths.push(arg.slice(2));
15659
+ continue;
15660
+ }
15661
+ if (arg.startsWith("-")) {
15662
+ if (arg === "-n") {
15663
+ if (!args[index + 1]) {
15664
+ return null;
15665
+ }
15666
+ index += 1;
15667
+ continue;
15668
+ }
15669
+ if (arg.startsWith("-n")) {
15670
+ continue;
15671
+ }
15672
+ return null;
15673
+ }
15674
+ if (isRubyTestScript(arg)) {
15675
+ return { includePaths, scriptPath: arg };
15564
15676
  }
15677
+ return null;
15565
15678
  }
15566
- const typecheckOnly = args.includes("--noEmit") && !hasOutput;
15567
- const inspect = metadataOnly || typecheckOnly;
15568
- requirements.unshift(
15569
- processRequirement("tsc", inspect ? "inspect" : "spawn", segment, [
15570
- metadataOnly ? "process.inspect.tsc_metadata" : typecheckOnly ? "process.inspect.tsc_typecheck" : "process.known_local_spawn"
15571
- ])
15679
+ return null;
15680
+ }
15681
+ function isRubocopMutating(args) {
15682
+ return args.some(
15683
+ (arg) => arg === "-A" || arg === "-a" || arg === "--auto-correct" || arg === "--autocorrect" || arg.startsWith("--auto-correct-all") || arg.startsWith("--autocorrect-all")
15572
15684
  );
15573
- return requirements;
15574
15685
  }
15575
- function decodeCopyMove(head, args, cwd, segment) {
15576
- const requirements = [processRequirement(head, "spawn", segment, ["process.filesystem_mutation"])];
15577
- const operands = [];
15578
- let targetDirectory = null;
15579
- let recursive = false;
15580
- let incomplete = false;
15581
- let optionsEnded = false;
15582
- const neutralFlags = /* @__PURE__ */ new Set([
15583
- "-f",
15584
- "-i",
15585
- "-n",
15586
- "-p",
15587
- "-v",
15588
- "--force",
15589
- "--interactive",
15590
- "--no-clobber"
15591
- ]);
15592
- for (let index = 0; index < args.length; index += 1) {
15593
- const arg = args[index] ?? "";
15594
- if (!optionsEnded && arg === "--") {
15595
- optionsEnded = true;
15596
- continue;
15686
+ function decodeBundleExecInner(innerHead, innerArgs, segment) {
15687
+ const innerBase = executableBaseName(innerHead);
15688
+ if (innerBase === "rubocop") {
15689
+ const mutating = isRubocopMutating(innerArgs);
15690
+ return [
15691
+ processRequirement(
15692
+ innerHead,
15693
+ mutating ? "spawn" : "inspect",
15694
+ segment,
15695
+ mutating ? ["process.linter.mutating"] : ["process.inspect.linter"]
15696
+ )
15697
+ ];
15698
+ }
15699
+ if (innerBase === "rspec") {
15700
+ const targetArgs = innerArgs.filter((arg) => !arg.startsWith("-"));
15701
+ if (targetArgs.length === 0) {
15702
+ return null;
15597
15703
  }
15598
- if (!optionsEnded && (arg === "-r" || arg === "-R" || arg === "--recursive")) {
15599
- recursive = true;
15600
- continue;
15704
+ return [processRequirement(innerHead, "spawn", segment, ["process.test_runner.rspec"])];
15705
+ }
15706
+ return null;
15707
+ }
15708
+ function decodeRuby(args, cwd, repoRoot, segment) {
15709
+ const parsed = parseRubyTestInvocation(args);
15710
+ if (!parsed) {
15711
+ return unsupportedProcess("ruby", segment, "process.ruby_grammar_incomplete");
15712
+ }
15713
+ const scriptPath = resolvePathOperand(parsed.scriptPath, cwd);
15714
+ if (!pathWithinRoot(canonicalPath(repoRoot), canonicalPath(scriptPath))) {
15715
+ return unsupportedProcess("ruby", segment, "process.ruby_outside_repo");
15716
+ }
15717
+ for (const includePath of parsed.includePaths) {
15718
+ const resolvedInclude = resolvePathOperand(includePath, cwd);
15719
+ if (!pathWithinRoot(canonicalPath(repoRoot), canonicalPath(resolvedInclude))) {
15720
+ return unsupportedProcess("ruby", segment, "process.ruby_outside_repo");
15601
15721
  }
15602
- if (!optionsEnded && (arg === "-t" || arg === "--target-directory")) {
15603
- const value = args[index + 1];
15604
- if (!value || value.startsWith("-")) {
15605
- incomplete = true;
15606
- } else {
15607
- targetDirectory = value;
15608
- index += 1;
15722
+ }
15723
+ const lowered = [
15724
+ processRequirement("ruby", "spawn", segment, ["process.test_runner.minitest"]),
15725
+ requirement2("fs.read", "fs.read", { kind: "path", path: scriptPath }, segment, [
15726
+ "ruby.minitest_script_read"
15727
+ ])
15728
+ ];
15729
+ for (const includePath of parsed.includePaths) {
15730
+ lowered.push(
15731
+ requirement2(
15732
+ "fs.read",
15733
+ "fs.read",
15734
+ { kind: "path", path: resolvePathOperand(includePath, cwd) },
15735
+ segment,
15736
+ ["ruby.minitest_load_path_read"]
15737
+ )
15738
+ );
15739
+ }
15740
+ return lowered;
15741
+ }
15742
+ function decodeRuntimeMetadataProcess(head, args, segment) {
15743
+ if (head === "bundle") {
15744
+ if (args.length === 1 && isMetadataOnlyArgv(args)) {
15745
+ return [processRequirement(head, "inspect", segment, ["process.inspect.runtime_metadata"])];
15746
+ }
15747
+ if (args[0] === "exec" && args.length >= 2) {
15748
+ const innerHead = args[1] ?? "";
15749
+ const innerArgs = args.slice(2);
15750
+ const innerBase = executableBaseName(innerHead);
15751
+ if ((innerBase === "rails" || innerBase === "rake") && railsReadOnlySubcommand(innerArgs)) {
15752
+ return [
15753
+ processRequirement(innerHead, "inspect", segment, ["process.inspect.rails_read_only"])
15754
+ ];
15755
+ }
15756
+ if (isMetadataOnlyArgv(innerArgs)) {
15757
+ return [
15758
+ processRequirement(innerHead, "inspect", segment, [
15759
+ "process.inspect.bundle_exec_metadata"
15760
+ ])
15761
+ ];
15609
15762
  }
15610
- continue;
15611
- }
15612
- if (!optionsEnded && arg.startsWith("--target-directory=")) {
15613
- targetDirectory = arg.slice("--target-directory=".length) || null;
15614
- incomplete ||= !targetDirectory;
15615
- continue;
15616
- }
15617
- if (!optionsEnded && arg.startsWith("-")) {
15618
- if (!neutralFlags.has(arg)) {
15619
- incomplete = true;
15763
+ const bundleExecInner = decodeBundleExecInner(innerHead, innerArgs, segment);
15764
+ if (bundleExecInner) {
15765
+ return bundleExecInner;
15620
15766
  }
15621
- continue;
15622
15767
  }
15623
- operands.push(arg);
15624
- }
15625
- const sources = targetDirectory ? operands : operands.slice(0, -1);
15626
- const destination = targetDirectory ?? (operands.length >= 2 ? operands.at(-1) ?? null : null);
15627
- if (sources.length === 0 || !destination) {
15628
- incomplete = true;
15768
+ return null;
15629
15769
  }
15630
- for (const source of sources) {
15631
- const resolved = resolvePathOperand(source, cwd);
15632
- requirements.push(
15633
- requirement2("fs.read", "fs.read", { kind: "path", path: resolved }, segment, [
15634
- `${head}.source_read`
15635
- ])
15636
- );
15637
- addSecretRead(requirements, resolved, segment);
15638
- if (head === "mv") {
15639
- addWriteEffects(requirements, resolved, segment, ["mv.source_remove"]);
15640
- }
15770
+ if ((head === "ruby" || head === "yarn") && args.length >= 1 && isMetadataOnlyArgv(args)) {
15771
+ return [processRequirement(head, "inspect", segment, ["process.inspect.runtime_metadata"])];
15641
15772
  }
15642
- if (destination) {
15643
- addWriteEffects(requirements, resolvePathOperand(destination, cwd), segment, [
15644
- `${head}.destination_write`
15645
- ]);
15773
+ if (head === "make" && args.includes("-n")) {
15774
+ return [processRequirement(head, "inspect", segment, ["process.inspect.make_dry_run"])];
15646
15775
  }
15647
- if (recursive || incomplete) {
15648
- requirements.push(
15649
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
15650
- recursive ? `${head}.recursive_source` : `${head}.grammar_incomplete`
15651
- ])
15652
- );
15776
+ const base = executableBaseName(head);
15777
+ if ((base === "rails" || base === "rake") && railsReadOnlySubcommand(args)) {
15778
+ return [processRequirement(head, "inspect", segment, ["process.inspect.rails_read_only"])];
15653
15779
  }
15654
- return requirements;
15780
+ return null;
15655
15781
  }
15656
- function decodeRm(args, cwd, repoRoot, segment) {
15657
- const requirements = [processRequirement("rm", "spawn", segment, ["process.filesystem_mutation"])];
15658
- const recursive = args.some(
15659
- (arg) => arg === "-r" || arg === "-R" || arg === "--recursive" || /^-[^-]*[rR]/.test(arg)
15660
- );
15661
- const operands = args.filter((arg) => arg && !arg.startsWith("-"));
15662
- if (operands.length === 0) {
15663
- return [
15664
- ...requirements,
15665
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
15666
- "rm.operand_missing"
15667
- ])
15668
- ];
15669
- }
15670
- const identity = inspectGitResourceIdentity(repoRoot);
15671
- for (const operand of operands) {
15672
- const resolved = resolvePathOperand(operand, cwd);
15673
- addWriteEffects(requirements, resolved, segment, ["filesystem.write", "rm.remove"]);
15674
- const finalOperandIsSymlink = isSymbolicLink(resolved);
15675
- const canonicalResolved = canonicalRmOperand(resolved, finalOperandIsSymlink);
15676
- const targetIdentity = recursive && !finalOperandIsSymlink ? inspectGitResourceIdentity(resolved) : { status: "absent" };
15677
- const sameIdentityTargetRoot = identity.status === "resolved" && targetIdentity.status === "resolved" && identity.identity.commonDir === targetIdentity.identity.commonDir && pathContains(canonicalResolved, targetIdentity.identity.repositoryRoot);
15678
- if (recursive && identity.status === "resolved" && (sameIdentityTargetRoot || [
15679
- repoRoot,
15680
- identity.identity.repositoryRoot,
15681
- identity.identity.gitDir,
15682
- identity.identity.commonDir,
15683
- identity.identity.gitEntryPath
15684
- ].some((metadataPath) => pathContains(canonicalResolved, metadataPath)))) {
15685
- const gitEntryPath = targetIdentity.status === "resolved" ? targetIdentity.identity.gitEntryPath : identity.identity.gitEntryPath;
15686
- requirements.push(
15687
- requirement2(
15688
- "control_plane.write",
15689
- "control_plane.write",
15690
- { kind: "path", path: gitEntryPath },
15691
- segment,
15692
- ["rm.recursive_git_boundary", "tier1_catastrophic"]
15693
- )
15694
- );
15782
+
15783
+ // src/core/effect-ir/shell-lower/decoders/toolchain.ts
15784
+ function decodeTsc(args, cwd, segment) {
15785
+ const metadataOnly = args.length > 0 && args.every((arg) => ["--help", "--version", "-h", "-v"].includes(arg));
15786
+ const requirements = [];
15787
+ let hasOutput = false;
15788
+ for (let index = 0; index < args.length; index += 1) {
15789
+ const arg = args[index] ?? "";
15790
+ const inline = arg.match(/^--(?:outDir|outFile)=(.+)$/)?.[1];
15791
+ const separate = (arg === "--outDir" || arg === "--outFile") && args[index + 1] ? args[index + 1] : null;
15792
+ const output = inline ?? separate;
15793
+ if (!output) {
15794
+ continue;
15695
15795
  }
15696
- }
15697
- return requirements;
15698
- }
15699
- function canonicalRmOperand(targetPath, finalOperandIsSymlink) {
15700
- try {
15701
- if (finalOperandIsSymlink) {
15702
- return path42.join(realpathSync4.native(path42.dirname(targetPath)), path42.basename(targetPath));
15796
+ hasOutput = true;
15797
+ addWriteEffects(requirements, resolvePathOperand(output, cwd), segment, ["tsc.output"]);
15798
+ if (separate) {
15799
+ index += 1;
15703
15800
  }
15704
- return realpathSync4.native(targetPath);
15705
- } catch {
15706
- return path42.resolve(targetPath);
15707
- }
15708
- }
15709
- function isSymbolicLink(targetPath) {
15710
- try {
15711
- return lstatSync2(targetPath).isSymbolicLink();
15712
- } catch {
15713
- return false;
15714
15801
  }
15715
- }
15716
- function pathContains(ancestor, candidate) {
15717
- const relative = path42.relative(path42.resolve(ancestor), path42.resolve(candidate));
15718
- return relative === "" || !relative.startsWith("..") && !path42.isAbsolute(relative);
15802
+ const typecheckOnly = args.includes("--noEmit") && !hasOutput;
15803
+ const inspect = metadataOnly || typecheckOnly;
15804
+ requirements.unshift(
15805
+ processRequirement("tsc", inspect ? "inspect" : "spawn", segment, [
15806
+ metadataOnly ? "process.inspect.tsc_metadata" : typecheckOnly ? "process.inspect.tsc_typecheck" : "process.known_local_spawn"
15807
+ ])
15808
+ );
15809
+ return requirements;
15719
15810
  }
15720
15811
  function decodeGo(args, segment) {
15721
15812
  if (["test", "list", "vet"].includes(args[0] ?? "")) {
@@ -15877,285 +15968,320 @@ function decodeNode2(args, cwd, segment) {
15877
15968
  )
15878
15969
  ];
15879
15970
  }
15880
- return unsupportedProcess("node", segment, "process.node_grammar_incomplete");
15881
- }
15882
- function decodePrisma(args, env, repoRoot, segment) {
15883
- const processEffect = processRequirement("prisma", "spawn", segment, ["process.prisma"]);
15884
- if (args[0] === "generate") {
15885
- return [
15886
- processEffect,
15887
- requirement2("fs.write", "fs.write", { kind: "path", path: repoRoot }, segment, [
15888
- "prisma.generate.repo_local"
15889
- ])
15890
- ];
15971
+ return unsupportedProcess("node", segment, "process.node_grammar_incomplete");
15972
+ }
15973
+
15974
+ // src/core/effect-ir/shell-lower/decode-process.ts
15975
+ function decodeProcessOrFilesystem(params) {
15976
+ const { tokens, head, env, cwd, repoRoot, segment } = params;
15977
+ const args = tokens.slice(1);
15978
+ if (tokens.length > 0 && tokens.every((token) => ENV_PREFIX_PATTERN2.test(token))) {
15979
+ return [];
15980
+ }
15981
+ const shellControl = decodeShellControlBuiltin(head, args);
15982
+ if (shellControl) {
15983
+ return shellControl;
15984
+ }
15985
+ if (isCommandInspection(tokens)) {
15986
+ return [processRequirement(head, "inspect", segment, ["process.inspect.command_lookup"])];
15987
+ }
15988
+ if (head === "lsof") {
15989
+ return validLsof(args) ? [processRequirement(head, "inspect", segment, ["process.inspect.lsof"])] : unsupportedProcess(head, segment, "process.lsof_grammar_incomplete");
15990
+ }
15991
+ if (head === "ps") {
15992
+ return validPs(args) ? [processRequirement(head, "inspect", segment, ["process.inspect.ps"])] : unsupportedProcess(head, segment, "process.ps_grammar_incomplete");
15993
+ }
15994
+ if (head === "test" || head === "[") {
15995
+ return validFilesystemTest(head, args) ? [processRequirement(head, "inspect", segment, ["process.inspect.filesystem_test"])] : unsupportedProcess(head, segment, "process.filesystem_test_grammar_incomplete");
15996
+ }
15997
+ if (head === "docker") {
15998
+ if (args[0] === "info") {
15999
+ return validDockerInfo(args.slice(1)) ? [processRequirement(head, "inspect", segment, ["process.inspect.docker_info"])] : unsupportedProcess(head, segment, "process.docker_info_grammar_incomplete");
16000
+ }
16001
+ if (args[0] === "compose" && ["up", "down", "start", "stop", "restart", "create", "rm"].includes(args[1] ?? "")) {
16002
+ return [processRequirement(head, "spawn", segment, ["service.local_mutation"])];
16003
+ }
16004
+ if (args[0] === "push" || args[0] === "build" && args.includes("--push") || args[0] === "buildx" && args[1] === "build" && (args.includes("--push") || args.some(
16005
+ (arg) => arg.startsWith("--output=type=registry") || arg.startsWith("-o=type=registry")
16006
+ ))) {
16007
+ return [
16008
+ processRequirement(head, "spawn", segment, ["tier0_external", "docker.remote_mutation"]),
16009
+ requirement2(
16010
+ "network.connect",
16011
+ "network.connect",
16012
+ {
16013
+ kind: "network",
16014
+ host: "registry",
16015
+ protocol: "container-registry",
16016
+ mode: "mutate",
16017
+ payload: "present"
16018
+ },
16019
+ segment,
16020
+ ["tier0_external", "docker.remote_mutation"]
16021
+ )
16022
+ ];
16023
+ }
16024
+ if (args[0] === "run" || args[0] === "create" || args[0] === "pull" || args[0] === "build" || args[0] === "buildx" && args[1] === "build") {
16025
+ return [
16026
+ processRequirement(head, "spawn", segment, ["process.docker_spawn"]),
16027
+ requirement2(
16028
+ "network.acquire",
16029
+ "network.connect",
16030
+ {
16031
+ kind: "network",
16032
+ host: "unknown",
16033
+ protocol: "container-registry",
16034
+ mode: "ambiguous",
16035
+ payload: "none"
16036
+ },
16037
+ segment,
16038
+ ["docker.image_acquisition_possible"]
16039
+ )
16040
+ ];
16041
+ }
16042
+ return [processRequirement(head, "spawn", segment, ["process.docker_spawn"])];
16043
+ }
16044
+ if (isShellHead(head)) {
16045
+ const syntax = bashSyntaxTarget(args);
16046
+ if (syntax) {
16047
+ return [
16048
+ processRequirement(head, "inspect", segment, ["process.inspect.shell_syntax"]),
16049
+ requirement2(
16050
+ "fs.read",
16051
+ "fs.read",
16052
+ { kind: "path", path: path47.resolve(cwd, syntax) },
16053
+ segment,
16054
+ ["shell.syntax_source_read"]
16055
+ )
16056
+ ];
16057
+ }
16058
+ return unsupportedProcess(head, segment, "process.shell_grammar_incomplete");
16059
+ }
16060
+ if (head === "prisma") {
16061
+ return decodePrisma(args, env, repoRoot, segment);
16062
+ }
16063
+ const runtimeMetadata = decodeRuntimeMetadataProcess(head, args, segment);
16064
+ if (runtimeMetadata) {
16065
+ return runtimeMetadata;
16066
+ }
16067
+ if (head === "ruby") {
16068
+ return decodeRuby(args, cwd, repoRoot, segment);
16069
+ }
16070
+ if (head === "rubocop" || head === "rspec") {
16071
+ const decoded = decodeBundleExecInner(head, args, segment);
16072
+ if (decoded) {
16073
+ return decoded;
16074
+ }
16075
+ }
16076
+ const dockerCompose = decodeDockerComposeRun2(head, args, segment);
16077
+ if (dockerCompose) {
16078
+ return dockerCompose;
16079
+ }
16080
+ if ((head === "npm" || head === "pnpm") && args.length === 1 && isMetadataOnlyArgv(args)) {
16081
+ return [processRequirement(head, "inspect", segment, ["process.inspect.package_manager"])];
16082
+ }
16083
+ if (head === "belay") {
16084
+ return decodeBelay(args, repoRoot, segment);
16085
+ }
16086
+ if (head === "tsc") {
16087
+ return decodeTsc(args, cwd, segment);
16088
+ }
16089
+ if (head === "go") {
16090
+ return decodeGo(args, segment);
16091
+ }
16092
+ if (head === "rsync") {
16093
+ return decodeRsync(args, cwd, segment);
16094
+ }
16095
+ if (head === "sed") {
16096
+ return decodeSed(args, cwd, segment);
15891
16097
  }
15892
- const databaseMutation = args[0] === "migrate" || args[0] === "db" && args[1] === "seed" || args[0] === "seed";
15893
- if (!databaseMutation) {
15894
- return [
15895
- processEffect,
15896
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
15897
- "prisma.grammar_incomplete"
15898
- ])
15899
- ];
16098
+ if (head === "base64" && args.length === 1 && ["-d", "-D", "--decode"].includes(args[0] ?? "")) {
16099
+ return [processRequirement(head, "inspect", segment, ["process.inspect.base64_stdin"])];
15900
16100
  }
15901
- const endpoint = databaseEndpoint(env.DATABASE_URL);
15902
- if (!endpoint) {
16101
+ if (head === "node") {
16102
+ return decodeNode2(args, cwd, segment);
16103
+ }
16104
+ if (head === "vite" || head === "vite-node") {
16105
+ return [processRequirement(head, "spawn", segment, ["process.local_dev_spawn"])];
16106
+ }
16107
+ if ((head === "npm" || head === "pnpm") && args[0] === "publish" || head === "terraform" && args[0] === "apply") {
15903
16108
  return [
15904
- processEffect,
16109
+ processRequirement(head, "spawn", segment, ["tier0_external"]),
15905
16110
  requirement2(
15906
16111
  "network.connect",
15907
16112
  "network.connect",
15908
16113
  {
15909
16114
  kind: "network",
15910
- host: "unknown",
15911
- protocol: "database",
15912
- mode: "ambiguous",
16115
+ host: "control-plane",
16116
+ protocol: head,
16117
+ mode: "mutate",
15913
16118
  payload: "present"
15914
16119
  },
15915
16120
  segment,
15916
- ["prisma.database_endpoint_unknown"]
15917
- ),
15918
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
15919
- "prisma.database_endpoint_unknown"
15920
- ])
16121
+ ["tier0_external"]
16122
+ )
15921
16123
  ];
15922
16124
  }
15923
- const network = requirement2(
15924
- "network.connect",
15925
- "network.connect",
15926
- {
15927
- kind: "network",
15928
- ...endpoint,
15929
- mode: "mutate",
15930
- payload: "present"
15931
- },
15932
- segment,
15933
- [LOOPBACK_HOSTS2.has(endpoint.host) ? "prisma.database_local" : "prisma.database_remote"]
15934
- );
15935
- if (LOOPBACK_HOSTS2.has(endpoint.host)) {
15936
- return [processEffect, network];
16125
+ if (head === "cd") {
16126
+ return [];
15937
16127
  }
15938
- return [
15939
- processEffect,
15940
- network,
15941
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
15942
- "prisma.remote_database_mutation"
15943
- ])
15944
- ];
15945
- }
15946
- function addRedirectEffects(requirements, tokens, env, context, segment) {
15947
- for (let index = 0; index < tokens.length; index += 1) {
15948
- const operator = tokens[index] ?? "";
15949
- if (!isRedirectOperator(operator) || isFdDuplication(operator)) {
15950
- continue;
15951
- }
15952
- const rawTarget = tokens[index + 1];
15953
- if (!rawTarget) {
15954
- requirements.push(
15955
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
15956
- "shell.redirect_target_missing"
15957
- ])
15958
- );
15959
- continue;
15960
- }
15961
- if (operator.includes("<<")) {
15962
- requirements.push(
15963
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
15964
- "shell.heredoc_opaque"
15965
- ])
15966
- );
15967
- index += 1;
15968
- continue;
15969
- }
15970
- const target = expandKnownVariables(rawTarget, env);
15971
- const resolved = resolvePathOperand(target, context.cwd);
15972
- if (resolved === "/dev/null") {
15973
- index += 1;
15974
- continue;
15975
- }
15976
- if (operator.includes("<")) {
15977
- requirements.push(
16128
+ if (head === "cp" || head === "mv") {
16129
+ return decodeCopyMove(head, args, cwd, segment);
16130
+ }
16131
+ if (head === "rm") {
16132
+ return decodeRm(args, cwd, repoRoot, segment);
16133
+ }
16134
+ if (head === "vitest" || head === "eslint" || head === "biome" || head === "belay") {
16135
+ return [processRequirement(head, "spawn", segment, ["process.known_local_spawn"])];
16136
+ }
16137
+ if (head === "pwd" || head === "which" || head === "whoami") {
16138
+ return [processRequirement(head, "inspect", segment, ["process.pure_inspection"])];
16139
+ }
16140
+ const readOperands = filesystemReadOperands(head, args);
16141
+ if (readOperands !== null) {
16142
+ const lowered = [processRequirement(head, "inspect", segment, ["process.filesystem_inspect"])];
16143
+ for (const operand of readOperands) {
16144
+ const resolved = resolvePathOperand(operand, cwd);
16145
+ lowered.push(
15978
16146
  requirement2("fs.read", "fs.read", { kind: "path", path: resolved }, segment, [
15979
- "shell.input_redirect"
16147
+ "filesystem.read"
15980
16148
  ])
15981
16149
  );
15982
- addSecretRead(requirements, resolved, segment);
15983
- }
15984
- if (operator.includes(">")) {
15985
- addWriteEffects(requirements, resolved, segment, ["shell.output_redirect"]);
16150
+ addSecretRead(lowered, resolved, segment);
15986
16151
  }
15987
- index += 1;
16152
+ return lowered;
15988
16153
  }
15989
- }
15990
- function addSubstitutionEffects(requirements, command, context, segment, signals) {
15991
- const inners = structuralSubstitutionInners(command);
15992
- for (const inner of inners) {
15993
- signals.add("command_substitution");
15994
- if (context.depth >= MAX_LOWER_DEPTH) {
15995
- requirements.push(
15996
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
15997
- "shell.substitution_depth_exceeded"
15998
- ])
15999
- );
16000
- continue;
16001
- }
16002
- const nestedSegments = lowerTopLevelSegments(inner, {
16003
- ...context,
16004
- command: inner,
16005
- depth: context.depth + 1
16006
- });
16007
- for (const nested of nestedSegments) {
16008
- requirements.push(
16009
- ...nested.requirements.map((entry) => {
16010
- const outerProvenance = {
16011
- segment,
16012
- innerCommand: inner
16013
- };
16014
- const provenance = {
16015
- ...entry.provenance,
16016
- segment,
16017
- innerCommand: entry.provenance.innerCommand ?? inner
16018
- };
16019
- return withProvenances(entry, provenance, outerProvenance);
16020
- })
16021
- );
16154
+ const writeOperands = filesystemWriteOperands(head, args);
16155
+ if (writeOperands !== null) {
16156
+ const lowered = [processRequirement(head, "spawn", segment, ["process.filesystem_mutation"])];
16157
+ for (const operand of writeOperands) {
16158
+ addWriteEffects(lowered, resolvePathOperand(operand, cwd), segment, ["filesystem.write"]);
16022
16159
  }
16160
+ return lowered;
16023
16161
  }
16024
- }
16025
- function extractEnvironment(tokens, inherited) {
16026
- const env = { ...inherited };
16027
- const changedNames = /* @__PURE__ */ new Set();
16028
- if (tokens[0] === "env") {
16029
- let index = 1;
16030
- let malformed = false;
16031
- while (index < tokens.length) {
16032
- const option = tokens[index] ?? "";
16033
- if (option === "-u" || option === "--unset") {
16034
- const name = tokens[index + 1] ?? "";
16035
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
16036
- malformed = true;
16037
- index += 1;
16038
- break;
16039
- }
16040
- delete env[name];
16041
- changedNames.add(name);
16042
- index += 2;
16043
- continue;
16044
- }
16045
- if (option.startsWith("--unset=")) {
16046
- const name = option.slice("--unset=".length);
16047
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
16048
- malformed = true;
16049
- index += 1;
16050
- break;
16051
- }
16052
- delete env[name];
16053
- changedNames.add(name);
16054
- index += 1;
16055
- continue;
16056
- }
16057
- if (option === "-i" || option === "--ignore-environment") {
16058
- for (const name of Object.keys(env)) {
16059
- delete env[name];
16060
- }
16061
- changedNames.add("*");
16062
- index += 1;
16063
- continue;
16064
- }
16065
- if (option === "--") {
16066
- index += 1;
16067
- break;
16068
- }
16069
- if (option.startsWith("-")) {
16070
- malformed = true;
16071
- index += 1;
16072
- break;
16073
- }
16074
- break;
16075
- }
16076
- while (index < tokens.length) {
16077
- const token = tokens[index] ?? "";
16078
- const match = ENV_PREFIX_PATTERN2.exec(token);
16079
- if (!match) {
16080
- break;
16081
- }
16082
- const [, name, value] = match;
16083
- if (name) {
16084
- env[name] = expandKnownVariables(value ?? "", env);
16085
- changedNames.add(name);
16086
- }
16087
- index += 1;
16088
- }
16089
- const commandTokens = tokens.slice(index);
16090
- return {
16091
- env,
16092
- commandTokens,
16093
- malformed: malformed || commandTokens.length === 0,
16094
- changedNames
16095
- };
16162
+ if (head === "printf" || head === "echo" || head === "true" || head === "false" || head === ":") {
16163
+ return [];
16096
16164
  }
16097
- for (const token of tokens) {
16098
- const match = ENV_PREFIX_PATTERN2.exec(token);
16099
- if (!match) {
16100
- break;
16101
- }
16102
- const [, name, value] = match;
16103
- if (name) {
16104
- env[name] = expandKnownVariables(value ?? "", env);
16105
- changedNames.add(name);
16106
- }
16165
+ if (!head) {
16166
+ return [
16167
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
16168
+ "shell.segment_empty"
16169
+ ])
16170
+ ];
16107
16171
  }
16108
- return { env, malformed: false, changedNames };
16172
+ return unsupportedProcess(head, segment, "process.grammar_unknown");
16109
16173
  }
16110
- function expandKnownVariables(token, env) {
16111
- return token.replace(
16112
- /\$(?:\{([A-Za-z_][A-Za-z0-9_]*)\}|([A-Za-z_][A-Za-z0-9_]*))/g,
16113
- (raw, a, b) => {
16114
- const value = env[a ?? b];
16115
- return value === void 0 ? raw : value;
16174
+
16175
+ // src/core/effect-ir/shell-lower/decoders/decode-package-exec.ts
16176
+ function decodePackageExec(peel, cwd, repoRoot, segment) {
16177
+ if (peel.opaque) {
16178
+ return [
16179
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
16180
+ peel.reason,
16181
+ ...peel.signals
16182
+ ])
16183
+ ];
16184
+ }
16185
+ if (peel.reason === "npx_wrapper_readonly") {
16186
+ return [];
16187
+ }
16188
+ const binHead = peel.innerTokens[0] ?? "";
16189
+ const metadataOnly = packageExecInnerIsMetadata(peel);
16190
+ const local = !peel.forceAcquire ? resolveLocalBin(binHead, cwd, repoRoot) : null;
16191
+ if (local) {
16192
+ return metadataOnly ? [
16193
+ requirement2(
16194
+ "process.exec",
16195
+ "process.exec",
16196
+ { kind: "executable", command: local.path, operation: "inspect" },
16197
+ segment,
16198
+ ["package_exec.delegate", "package_exec.local_bin_resolved"]
16199
+ )
16200
+ ] : [];
16201
+ }
16202
+ const networkResources = peel.acquisitionSpecs.map((spec) => classifyPackageAcquisitionSpec(spec)).filter((source) => source.kind !== "local").map(
16203
+ (source) => source.kind === "registry" ? {
16204
+ kind: "network",
16205
+ host: "registry.npmjs.org",
16206
+ protocol: "registry",
16207
+ mode: "read",
16208
+ payload: "none"
16209
+ } : {
16210
+ kind: "network",
16211
+ host: source.host,
16212
+ ...source.port ? { port: source.port } : {},
16213
+ protocol: source.protocol,
16214
+ mode: "read",
16215
+ payload: "none"
16116
16216
  }
16117
16217
  );
16118
- }
16119
- function stripRedirects(tokens) {
16120
- const stripped = [];
16121
- for (let index = 0; index < tokens.length; index += 1) {
16122
- const token = tokens[index] ?? "";
16123
- if (isFdDuplication(token)) {
16124
- continue;
16125
- }
16126
- if (!isRedirectOperator(token)) {
16127
- stripped.push(token);
16128
- continue;
16129
- }
16130
- index += 1;
16218
+ if (networkResources.length === 0) {
16219
+ networkResources.push({
16220
+ kind: "network",
16221
+ host: "registry.npmjs.org",
16222
+ protocol: "registry",
16223
+ mode: "read",
16224
+ payload: "none"
16225
+ });
16131
16226
  }
16132
- return stripped;
16227
+ return [
16228
+ ...networkResources.map(
16229
+ (resource) => requirement2("network.acquire", "network.connect", resource, segment, [
16230
+ "package_acquire_possible"
16231
+ ])
16232
+ ),
16233
+ requirement2(
16234
+ "fs.write",
16235
+ "fs.write",
16236
+ { kind: "package-cache", manager: peel.launcher === "pnpm" ? "pnpm" : "npm" },
16237
+ segment,
16238
+ ["package_cache_write"]
16239
+ ),
16240
+ ...metadataOnly ? [
16241
+ requirement2(
16242
+ "process.exec",
16243
+ "process.exec",
16244
+ { kind: "executable", command: binHead || peel.launcher, operation: "inspect" },
16245
+ segment,
16246
+ ["package_exec.delegate"]
16247
+ )
16248
+ ] : []
16249
+ ];
16133
16250
  }
16134
- function stripStructuredRedirects(tokens) {
16135
- const stripped = [];
16136
- for (let index = 0; index < tokens.length; index += 1) {
16137
- const token = tokens[index];
16138
- if (!token) continue;
16139
- if (isFdDuplication(token.value)) {
16140
- continue;
16141
- }
16142
- if (!isRedirectOperator(token.value)) {
16143
- stripped.push(token);
16144
- continue;
16145
- }
16146
- index += 1;
16251
+ function packageExecInnerIsMetadata(peel) {
16252
+ const args = peel.innerTokens.slice(1);
16253
+ return args.length > 0 && args.every((arg) => ["--help", "--version", "-V", "-h", "-v"].includes(arg));
16254
+ }
16255
+
16256
+ // src/core/effect-ir/shell-lower/segment.ts
16257
+ init_shell_tokenizer();
16258
+ import path48 from "node:path";
16259
+ var DYNAMIC_SHELL_VALUE_PATTERN = /(?:\$\(|`|\$(?:\d+|[@*#?$!-]|\{[^}]*\}|[A-Za-z_][A-Za-z0-9_]*))/;
16260
+ var SHELL_GLOB_PATTERN = /[*?[]/;
16261
+ function requiresKnownCwd(requirementValue) {
16262
+ if (requirementValue.action === "fs.write" || requirementValue.action === "git.ref.write" || requirementValue.action === "control_plane.write") {
16263
+ return true;
16147
16264
  }
16148
- return stripped;
16265
+ return requirementValue.action === "process.exec" && requirementValue.resource.kind === "executable" && requirementValue.resource.operation === "spawn";
16149
16266
  }
16150
- function alignStructuredTokens(tokens, values) {
16151
- if (values.length === 0) return [];
16152
- for (let start = tokens.length - values.length; start >= 0; start -= 1) {
16153
- const candidate = tokens.slice(start);
16154
- if (candidate.length === values.length && candidate.every((token, index) => token.value === values[index])) {
16155
- return candidate;
16156
- }
16267
+ function joinNestedOpacity(outer, nested) {
16268
+ const nestedOpacity = nested.completeness === "partial" && nested.opacity === "transparent" ? "recursive" : nested.opacity;
16269
+ return joinEffectOpacity(outer, nestedOpacity);
16270
+ }
16271
+ function startsLocalPostgresService(command) {
16272
+ const tokens = tokenizeShell(command);
16273
+ return path48.basename(tokens[0] ?? "") === "docker" && tokens[1] === "compose" && ["up", "start", "restart"].includes(tokens[2] ?? "") && tokens.includes("postgres");
16274
+ }
16275
+ function resolveCdTransition(command, currentCwd) {
16276
+ const tokens = tokenizeShell(command);
16277
+ if (path48.basename(tokens[0] ?? "") !== "cd") {
16278
+ return null;
16157
16279
  }
16158
- return [];
16280
+ const target = tokens[1] ?? "~";
16281
+ if (!target || target === "-" || target.includes("$") || target.includes("`")) {
16282
+ return { cwd: currentCwd, known: false };
16283
+ }
16284
+ return { cwd: resolvePathOperand(target, currentCwd), known: true };
16159
16285
  }
16160
16286
  function shellSegment(commandRedacted, segmentHead, requirements, opacity, signals) {
16161
16287
  const normalizedRequirements = requirements.flatMap((entry) => {
@@ -16186,430 +16312,520 @@ function shellSegment(commandRedacted, segmentHead, requirements, opacity, signa
16186
16312
  signals: [...signals].sort()
16187
16313
  };
16188
16314
  }
16189
- var DYNAMIC_SHELL_VALUE_PATTERN = /(?:\$\(|`|\$(?:\d+|[@*#?$!-]|\{[^}]*\}|[A-Za-z_][A-Za-z0-9_]*))/;
16190
- var SHELL_GLOB_PATTERN = /[*?[]/;
16191
- function effectChangingEnvironmentSignals(head, env, changedNames) {
16192
- if (head === "curl" && (env.CURL_HOME || changedNames.has("CURL_HOME") || changedNames.has("HOME") || changedNames.has("XDG_CONFIG_HOME"))) {
16193
- return ["egress.curl.environment_config_override"];
16194
- }
16195
- if (head === "wget" && (env.WGETRC || changedNames.has("WGETRC") || changedNames.has("HOME"))) {
16196
- return ["egress.wget.environment_config_override"];
16315
+ function dynamicResourceSignal(resource) {
16316
+ const expanded = (value) => Boolean(value && DYNAMIC_SHELL_VALUE_PATTERN.test(value));
16317
+ const pathLike = (value, allowSemanticWildcard = false) => Boolean(
16318
+ value && (expanded(value) || !allowSemanticWildcard && SHELL_GLOB_PATTERN.test(value))
16319
+ );
16320
+ switch (resource.kind) {
16321
+ case "path":
16322
+ return pathLike(resource.path) ? "shell.path_unresolved" : null;
16323
+ case "network":
16324
+ return expanded(resource.host) || expanded(resource.protocol) ? "shell.network_resource_unresolved" : null;
16325
+ case "git-ref":
16326
+ return pathLike(resource.repoPath) || pathLike(resource.ref, resource.ref.endsWith("/*")) ? "shell.git_resource_unresolved" : null;
16327
+ case "executable":
16328
+ return expanded(resource.command) ? "shell.executable_unresolved" : null;
16329
+ default:
16330
+ return null;
16197
16331
  }
16198
- if (head !== "git") {
16199
- return [];
16332
+ }
16333
+
16334
+ // src/core/effect-ir/shell-lower.ts
16335
+ var MAX_LOWER_DEPTH = 8;
16336
+ function lowerShellEffectPlan(params) {
16337
+ const context = { ...params, depth: 0 };
16338
+ const segments = lowerTopLevelSegments(params.command, context);
16339
+ return buildShellEffectPlan({
16340
+ inputFingerprint: params.inputFingerprint,
16341
+ segments,
16342
+ signals: pipeToShell(params.command) ? ["pipe_to_shell"] : []
16343
+ });
16344
+ }
16345
+ function lowerTopLevelSegments(command, context) {
16346
+ const commands = splitStructuralShellSegments(command);
16347
+ if (commands.length === 0 && command.trim()) {
16348
+ commands.push(command.trim());
16200
16349
  }
16201
- const hazardousNames = /* @__PURE__ */ new Set([
16202
- "GIT_ASKPASS",
16203
- "GIT_ALTERNATE_OBJECT_DIRECTORIES",
16204
- "GIT_CEILING_DIRECTORIES",
16205
- "GIT_COMMON_DIR",
16206
- "GIT_CONFIG",
16207
- "GIT_CONFIG_GLOBAL",
16208
- "GIT_CONFIG_SYSTEM",
16209
- "GIT_DIR",
16210
- "GIT_DISCOVERY_ACROSS_FILESYSTEM",
16211
- "GIT_EDITOR",
16212
- "GIT_EXEC_PATH",
16213
- "GIT_EXTERNAL_DIFF",
16214
- "GIT_INDEX_FILE",
16215
- "GIT_NAMESPACE",
16216
- "GIT_OBJECT_DIRECTORY",
16217
- "GIT_PAGER",
16218
- "GIT_PROXY_COMMAND",
16219
- "GIT_QUARANTINE_PATH",
16220
- "GIT_SEQUENCE_EDITOR",
16221
- "GIT_SHALLOW_FILE",
16222
- "GIT_SSH",
16223
- "GIT_SSH_COMMAND",
16224
- "GIT_TEMPLATE_DIR",
16225
- "GIT_WORK_TREE",
16226
- "SSH_ASKPASS"
16227
- ]);
16228
- const overridden = changedNames.has("HOME") || changedNames.has("XDG_CONFIG_HOME") || Object.entries(env).some(([name, value]) => {
16229
- if (!value) {
16230
- return false;
16350
+ const pipedToShell = pipeToShell(command);
16351
+ const lowered = [];
16352
+ let cwd = context.cwd;
16353
+ let cwdKnown = true;
16354
+ let inferredEnv = { ...context.env };
16355
+ for (const segment of commands) {
16356
+ let result = lowerSegment(segment, {
16357
+ ...context,
16358
+ cwd,
16359
+ env: inferredEnv,
16360
+ command: segment,
16361
+ depth: context.depth,
16362
+ inputFingerprint: context.inputFingerprint,
16363
+ ...pipedToShell && isShellHead(parseSegment(segment).head) ? { pipeToShellSegment: true } : {}
16364
+ });
16365
+ if (!cwdKnown) {
16366
+ const requiresCwd = result.requirements.some(requiresKnownCwd);
16367
+ const requirements = [...result.requirements];
16368
+ if (requiresCwd) {
16369
+ requirements.push(
16370
+ requirement2(
16371
+ "indeterminate",
16372
+ "indeterminate",
16373
+ { kind: "unknown" },
16374
+ result.commandRedacted,
16375
+ ["shell.cwd_unknown"]
16376
+ )
16377
+ );
16378
+ }
16379
+ result = {
16380
+ ...result,
16381
+ requirements,
16382
+ ...requiresCwd ? {
16383
+ completeness: "partial",
16384
+ opacity: joinEffectOpacity(result.opacity, "opaque")
16385
+ } : {},
16386
+ signals: [.../* @__PURE__ */ new Set([...result.signals, "shell.cwd_unknown"])]
16387
+ };
16231
16388
  }
16232
- if (hazardousNames.has(name) || /^GIT_CONFIG_(?:KEY|VALUE)_\d+$/.test(name) || /^GIT_TRACE/.test(name)) {
16233
- return true;
16389
+ lowered.push(result);
16390
+ if (!inferredEnv.DATABASE_URL && startsLocalPostgresService(segment)) {
16391
+ inferredEnv = {
16392
+ ...inferredEnv,
16393
+ DATABASE_URL: "postgresql://127.0.0.1:5432/local"
16394
+ };
16395
+ }
16396
+ const nextCwd = resolveCdTransition(segment, cwd);
16397
+ if (nextCwd) {
16398
+ cwd = nextCwd.cwd;
16399
+ cwdKnown = nextCwd.known;
16234
16400
  }
16235
- return name === "GIT_CONFIG_COUNT" && value !== "0";
16236
- });
16237
- return overridden ? ["git.environment_execution_or_config_override"] : [];
16238
- }
16239
- function dynamicResourceSignal(resource) {
16240
- const expanded = (value) => Boolean(value && DYNAMIC_SHELL_VALUE_PATTERN.test(value));
16241
- const pathLike = (value, allowSemanticWildcard = false) => Boolean(
16242
- value && (expanded(value) || !allowSemanticWildcard && SHELL_GLOB_PATTERN.test(value))
16243
- );
16244
- switch (resource.kind) {
16245
- case "path":
16246
- return pathLike(resource.path) ? "shell.path_unresolved" : null;
16247
- case "network":
16248
- return expanded(resource.host) || expanded(resource.protocol) ? "shell.network_resource_unresolved" : null;
16249
- case "git-ref":
16250
- return pathLike(resource.repoPath) || pathLike(resource.ref, resource.ref.endsWith("/*")) ? "shell.git_resource_unresolved" : null;
16251
- case "executable":
16252
- return expanded(resource.command) ? "shell.executable_unresolved" : null;
16253
- default:
16254
- return null;
16255
16401
  }
16256
- }
16257
- function processRequirement(command, operation, segment, signals) {
16258
- return requirement2(
16259
- "process.exec",
16260
- "process.exec",
16261
- { kind: "executable", command, operation },
16262
- segment,
16263
- signals
16264
- );
16265
- }
16266
- function unsupportedProcess(command, segment, signal) {
16267
- return [
16268
- processRequirement(command, "spawn", segment, [signal]),
16269
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [signal])
16270
- ];
16271
- }
16272
- function addWriteEffects(requirements, resolved, segment, signals) {
16273
- requirements.push(
16274
- requirement2("fs.write", "fs.write", { kind: "path", path: resolved }, segment, signals)
16275
- );
16276
- if (CONTROL_PLANE_PATH_PATTERN.test(resolved)) {
16277
- requirements.push(
16278
- requirement2(
16279
- "control_plane.write",
16280
- "control_plane.write",
16281
- { kind: "path", path: resolved },
16282
- segment,
16283
- [...signals, "protected_path"]
16402
+ if (commands.length > 1 && /[\r\n]/.test(command) && lowered.some(
16403
+ (segment) => segment.requirements.some((entry) => entry.action === "network.connect")
16404
+ )) {
16405
+ lowered.push(
16406
+ shellSegment(
16407
+ "[multiline network boundary]",
16408
+ "shell-boundary",
16409
+ [
16410
+ requirement2(
16411
+ "indeterminate",
16412
+ "indeterminate",
16413
+ { kind: "unknown" },
16414
+ "[multiline network boundary]",
16415
+ ["shell.multiline_network_boundary"]
16416
+ )
16417
+ ],
16418
+ "opaque",
16419
+ /* @__PURE__ */ new Set(["shell.multiline_network_boundary"])
16284
16420
  )
16285
16421
  );
16286
16422
  }
16423
+ return lowered;
16287
16424
  }
16288
- function addSecretRead(requirements, resolved, segment) {
16289
- if (SECRET_PATH_PATTERN.test(resolved)) {
16425
+ function lowerSegment(command, context) {
16426
+ const commandRedacted = redactCommand(command);
16427
+ const lexed = lexShell(command);
16428
+ const rawTokens = lexed.tokens.map((token) => token.value);
16429
+ const environment = extractEnvironment(rawTokens, context.env);
16430
+ const env = environment.env;
16431
+ const parsed = parseSegment(command);
16432
+ const parsedTokens = environment.commandTokens ?? parsed.tokens;
16433
+ const tokens = stripRedirects(
16434
+ parsedTokens.length === 0 && rawTokens.length > 0 && rawTokens.every((token) => ENV_PREFIX_PATTERN2.test(token)) ? rawTokens : parsedTokens
16435
+ ).map((token) => expandKnownVariables(token, env));
16436
+ const decoderTokens = alignStructuredTokens(
16437
+ stripStructuredRedirects(lexed.tokens),
16438
+ stripRedirects(parsedTokens)
16439
+ );
16440
+ const head = path49.basename(tokens[0] ?? parsed.head);
16441
+ let opacity = segmentOpacity(command);
16442
+ const signals = /* @__PURE__ */ new Set();
16443
+ const requirements = [];
16444
+ if (!lexed.complete) {
16290
16445
  requirements.push(
16291
- requirement2("secret.read", "secret.read", { kind: "path", path: resolved }, segment, [
16292
- "secret_path_read"
16446
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
16447
+ "shell.grammar_incomplete"
16293
16448
  ])
16294
16449
  );
16450
+ signals.add("shell.grammar_incomplete");
16451
+ opacity = joinEffectOpacity(opacity, "unparseable");
16295
16452
  }
16296
- }
16297
- function filesystemReadOperands(head, args) {
16298
- switch (head) {
16299
- case "ls": {
16300
- const operands = args.filter((arg) => arg && !arg.startsWith("-"));
16301
- return operands.length > 0 ? operands : ["."];
16302
- }
16303
- case "find":
16304
- return findReadOperands(args);
16305
- case "cat":
16306
- case "head":
16307
- case "tail":
16308
- case "less":
16309
- case "stat":
16310
- case "file":
16311
- case "du":
16312
- case "wc":
16313
- return args.filter((arg) => arg && !arg.startsWith("-"));
16314
- case "grep":
16315
- case "rg":
16316
- return args.slice(1).filter((arg) => arg && !arg.startsWith("-"));
16317
- case "jq":
16318
- return args.slice(1).filter((arg) => arg && !arg.startsWith("-"));
16319
- default:
16320
- return null;
16321
- }
16322
- }
16323
- function findReadOperands(args) {
16324
- const mutatingPrimaries = /* @__PURE__ */ new Set([
16325
- "-delete",
16326
- "-exec",
16327
- "-execdir",
16328
- "-fls",
16329
- "-fprint",
16330
- "-fprintf",
16331
- "-ok",
16332
- "-okdir"
16333
- ]);
16334
- if (args.some(
16335
- (arg) => mutatingPrimaries.has(arg) || [...mutatingPrimaries].some((primary) => arg.startsWith(`${primary}=`))
16336
- )) {
16337
- return null;
16338
- }
16339
- const paths = [];
16340
- for (const arg of args) {
16341
- if (arg.startsWith("-") || arg === "!" || arg === "(") {
16342
- break;
16343
- }
16344
- paths.push(arg);
16453
+ addRedirectEffects(requirements, rawTokens, env, context, commandRedacted);
16454
+ addSubstitutionEffects(requirements, command, context, commandRedacted, signals);
16455
+ if (environment.malformed) {
16456
+ requirements.push(
16457
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
16458
+ "shell.env_wrapper_incomplete"
16459
+ ])
16460
+ );
16461
+ opacity = joinEffectOpacity(opacity, "opaque");
16345
16462
  }
16346
- return paths.length > 0 ? paths : ["."];
16347
- }
16348
- function filesystemWriteOperands(head, args) {
16349
- const positional = args.filter((arg) => arg && !arg.startsWith("-"));
16350
- switch (head) {
16351
- case "touch":
16352
- case "mkdir":
16353
- case "rm":
16354
- case "truncate":
16355
- case "chmod":
16356
- return positional;
16357
- case "cp":
16358
- case "mv":
16359
- return positional.length > 0 ? [positional[positional.length - 1] ?? ""] : [];
16360
- case "tee":
16361
- return positional;
16362
- default:
16363
- return null;
16463
+ if (parsed.encounteredXargs) {
16464
+ requirements.push(
16465
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
16466
+ "shell.xargs_stdin_dynamic"
16467
+ ])
16468
+ );
16469
+ signals.add("shell.xargs_stdin_dynamic");
16470
+ opacity = joinEffectOpacity(opacity, "opaque");
16364
16471
  }
16365
- }
16366
- function validLsof(args) {
16367
- const flagsWithoutValues = /* @__PURE__ */ new Set(["-a", "-b", "-l", "-n", "-P", "-R", "-t", "-V"]);
16368
- const flagsWithValues = /* @__PURE__ */ new Set(["-c", "-d", "-F", "-g", "-p", "-T", "-u", "+d", "+D"]);
16369
- for (let index = 0; index < args.length; index += 1) {
16370
- const arg = args[index] ?? "";
16371
- if (flagsWithoutValues.has(arg) || arg === "-i") {
16372
- continue;
16373
- }
16374
- if (flagsWithValues.has(arg)) {
16375
- if (!args[index + 1] || isOptionToken(args[index + 1] ?? "")) {
16376
- return false;
16377
- }
16378
- index += 1;
16379
- continue;
16380
- }
16381
- 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)) {
16382
- continue;
16383
- }
16384
- return false;
16472
+ if (context.depth > MAX_LOWER_DEPTH) {
16473
+ requirements.push(
16474
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
16475
+ "shell.lower_depth_exceeded"
16476
+ ])
16477
+ );
16478
+ return shellSegment(
16479
+ commandRedacted,
16480
+ head,
16481
+ requirements,
16482
+ joinEffectOpacity(opacity, "opaque"),
16483
+ signals
16484
+ );
16385
16485
  }
16386
- return true;
16387
- }
16388
- function validPs(args) {
16389
- const bareForms = /* @__PURE__ */ new Set(["aux", "ax", "x", "u"]);
16390
- const flagsWithoutValues = /* @__PURE__ */ new Set(["-A", "-a", "-d", "-e", "-f", "-j", "-l", "-T", "-x"]);
16391
- const flagsWithValues = /* @__PURE__ */ new Set([
16392
- "--format",
16393
- "--group",
16394
- "--pid",
16395
- "--ppid",
16396
- "--sort",
16397
- "--tty",
16398
- "--user"
16399
- ]);
16400
- for (let index = 0; index < args.length; index += 1) {
16401
- const arg = args[index] ?? "";
16402
- if (bareForms.has(arg) || flagsWithoutValues.has(arg)) {
16403
- continue;
16404
- }
16405
- if (flagsWithValues.has(arg)) {
16406
- if (!args[index + 1] || isOptionToken(args[index + 1] ?? "")) {
16407
- return false;
16408
- }
16409
- index += 1;
16410
- continue;
16486
+ const packageExec = peelPackageExecArgv(tokens);
16487
+ if (packageExec) {
16488
+ requirements.push(
16489
+ ...decodePackageExec(packageExec, context.cwd, context.repoRoot, commandRedacted)
16490
+ );
16491
+ for (const signal of packageExec.signals) {
16492
+ signals.add(signal);
16411
16493
  }
16412
- if (/^(?:--format|--group|--pid|--ppid|--sort|--tty|--user)=.+$/.test(arg)) {
16413
- continue;
16494
+ if (packageExec.opaque) {
16495
+ opacity = joinEffectOpacity(opacity, "opaque");
16414
16496
  }
16415
- if (/^-[^-]/.test(arg)) {
16416
- const noValue = /* @__PURE__ */ new Set(["A", "a", "d", "e", "f", "j", "l", "T", "x"]);
16417
- const withValue = /* @__PURE__ */ new Set(["G", "g", "N", "o", "p", "t", "U", "u"]);
16418
- const characters = arg.slice(1);
16419
- let valid = true;
16420
- for (let optionIndex = 0; optionIndex < characters.length; optionIndex += 1) {
16421
- const option = characters[optionIndex] ?? "";
16422
- if (noValue.has(option)) {
16423
- continue;
16424
- }
16425
- if (withValue.has(option)) {
16426
- if (optionIndex === characters.length - 1) {
16427
- if (!args[index + 1] || isOptionToken(args[index + 1] ?? "")) {
16428
- return false;
16429
- }
16430
- index += 1;
16431
- }
16432
- break;
16497
+ const innerRecipe = packageExecInnerIsMetadata(packageExec) ? null : innerRecipeFromPeel(packageExec);
16498
+ if (innerRecipe) {
16499
+ const nested = lowerTopLevelSegments(innerRecipe, {
16500
+ ...context,
16501
+ command: innerRecipe,
16502
+ env,
16503
+ depth: context.depth + 1
16504
+ });
16505
+ for (const nestedSegment of nested) {
16506
+ requirements.push(
16507
+ ...nestedSegment.requirements.map(
16508
+ (entry) => withInnerProvenance(entry, innerRecipe, packageExec.launcher, commandRedacted)
16509
+ )
16510
+ );
16511
+ for (const signal of nestedSegment.signals) {
16512
+ signals.add(signal);
16433
16513
  }
16434
- valid = false;
16435
- break;
16436
- }
16437
- if (valid) {
16438
- continue;
16514
+ opacity = joinNestedOpacity(opacity, nestedSegment);
16439
16515
  }
16440
16516
  }
16441
- return false;
16442
- }
16443
- return true;
16444
- }
16445
- function validFilesystemTest(head, args) {
16446
- const operands = head === "[" && args.at(-1) === "]" ? args.slice(0, -1) : args;
16447
- if (head === "[" && args.at(-1) !== "]") {
16448
- return false;
16517
+ return shellSegment(commandRedacted, head, requirements, opacity, signals);
16449
16518
  }
16450
- return operands.length === 2 && (/* @__PURE__ */ new Set(["-b", "-c", "-d", "-e", "-f", "-h", "-L", "-r", "-s", "-w", "-x"])).has(
16451
- operands[0] ?? ""
16452
- ) && Boolean(operands[1]);
16453
- }
16454
- function validDockerInfo(args) {
16455
- for (let index = 0; index < args.length; index += 1) {
16456
- const arg = args[index] ?? "";
16457
- if (arg === "--help") {
16458
- continue;
16459
- }
16460
- if (arg === "--format" || arg === "-f") {
16461
- if (!args[index + 1] || isOptionToken(args[index + 1] ?? "")) {
16462
- return false;
16519
+ const recursive = decodeRecursiveInvocationTokens(decoderTokens);
16520
+ if (recursive.kind === "static" && opacity !== "opaque" && opacity !== "unparseable") {
16521
+ requirements.push(
16522
+ processRequirement(recursive.interpreter, "spawn", commandRedacted, [
16523
+ "shell.recursive_wrapper",
16524
+ "dynamic_shell_evaluation"
16525
+ ])
16526
+ );
16527
+ if (recursive.script !== "") {
16528
+ const nested = lowerTopLevelSegments(recursive.script, {
16529
+ ...context,
16530
+ command: recursive.script,
16531
+ env,
16532
+ depth: context.depth + 1
16533
+ });
16534
+ for (const nestedSegment of nested) {
16535
+ requirements.push(
16536
+ ...nestedSegment.requirements.map(
16537
+ (entry) => withInnerProvenance(entry, recursive.script, head, commandRedacted)
16538
+ )
16539
+ );
16540
+ for (const signal of nestedSegment.signals) signals.add(signal);
16463
16541
  }
16464
- index += 1;
16465
- continue;
16466
16542
  }
16467
- if (arg.startsWith("--format=")) {
16468
- if (arg.slice("--format=".length).length === 0) {
16469
- return false;
16543
+ signals.add("shell.recursive_wrapper");
16544
+ signals.add("dynamic_shell_evaluation");
16545
+ return shellSegment(commandRedacted, head, requirements, "recursive", signals);
16546
+ }
16547
+ if (recursive.kind === "dynamic" || recursive.kind === "indeterminate") {
16548
+ const recursiveSignals = [
16549
+ recursive.signal,
16550
+ ...recursive.kind === "dynamic" ? ["dynamic_shell_evaluation"] : []
16551
+ ];
16552
+ requirements.push(
16553
+ processRequirement(recursive.interpreter, "spawn", commandRedacted, recursiveSignals),
16554
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
16555
+ ...recursiveSignals
16556
+ ])
16557
+ );
16558
+ for (const signal of recursiveSignals) signals.add(signal);
16559
+ return shellSegment(
16560
+ commandRedacted,
16561
+ head,
16562
+ requirements,
16563
+ joinEffectOpacity(opacity, "opaque"),
16564
+ signals
16565
+ );
16566
+ }
16567
+ const compose = decodeDockerComposeRun(decoderTokens);
16568
+ if (compose.kind === "recursive" && opacity !== "opaque" && opacity !== "unparseable") {
16569
+ requirements.push(
16570
+ processRequirement(head, "spawn", commandRedacted, ["process.docker_compose_run"])
16571
+ );
16572
+ if (compose.script !== "") {
16573
+ const nested = lowerTopLevelSegments(compose.script, {
16574
+ ...context,
16575
+ command: compose.script,
16576
+ env,
16577
+ depth: context.depth + 1
16578
+ });
16579
+ for (const nestedSegment of nested) {
16580
+ requirements.push(
16581
+ ...nestedSegment.requirements.map(
16582
+ (entry) => withInnerProvenance(entry, compose.script, head, commandRedacted)
16583
+ )
16584
+ );
16585
+ for (const signal of nestedSegment.signals) signals.add(signal);
16586
+ opacity = joinNestedOpacity(opacity, nestedSegment);
16470
16587
  }
16471
- continue;
16472
16588
  }
16473
- return false;
16589
+ signals.add("process.docker_compose_run");
16590
+ return shellSegment(commandRedacted, head, requirements, "recursive", signals);
16474
16591
  }
16475
- return true;
16476
- }
16477
- function bashSyntaxTarget(args) {
16478
- let syntaxOnly = false;
16479
- let script = null;
16480
- const shortFlags = new Set("abefhkmnptuvxBCEHPT".split(""));
16481
- const longFlags = /* @__PURE__ */ new Set([
16482
- "--debugger",
16483
- "--dump-po-strings",
16484
- "--dump-strings",
16485
- "--help",
16486
- "--login",
16487
- "--noediting",
16488
- "--noprofile",
16489
- "--norc",
16490
- "--posix",
16491
- "--restricted",
16492
- "--verbose",
16493
- "--version"
16494
- ]);
16495
- for (let index = 0; index < args.length; index += 1) {
16496
- const arg = args[index] ?? "";
16497
- if (arg === "-c" || arg === "-lc") {
16498
- return null;
16499
- }
16500
- if (arg === "-O" || arg === "+O" || arg === "--rcfile" || arg === "--init-file") {
16501
- if (!args[index + 1] || isOptionToken(args[index + 1] ?? "")) {
16502
- return null;
16592
+ if (compose.kind === "dynamic" || compose.kind === "indeterminate") {
16593
+ requirements.push(
16594
+ processRequirement(head, "spawn", commandRedacted, ["process.docker_compose_run"]),
16595
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
16596
+ compose.signal
16597
+ ])
16598
+ );
16599
+ signals.add("process.docker_compose_run");
16600
+ signals.add(compose.signal);
16601
+ return shellSegment(
16602
+ commandRedacted,
16603
+ head,
16604
+ requirements,
16605
+ joinEffectOpacity(opacity, "opaque"),
16606
+ signals
16607
+ );
16608
+ }
16609
+ const launcher = resolveLauncherRecipe({
16610
+ tokens,
16611
+ cwd: context.cwd,
16612
+ repoRoot: context.repoRoot,
16613
+ depth: context.depth
16614
+ });
16615
+ if (launcher) {
16616
+ signals.add(`launcher.${launcher.reason}`);
16617
+ requirements.push(
16618
+ requirement2(
16619
+ "process.exec",
16620
+ "process.exec",
16621
+ { kind: "executable", command: head, operation: "spawn" },
16622
+ commandRedacted,
16623
+ ["launcher.invoke"]
16624
+ )
16625
+ );
16626
+ for (const recipe of launcher.recipes) {
16627
+ const nested = lowerTopLevelSegments(recipe, {
16628
+ ...context,
16629
+ command: recipe,
16630
+ env,
16631
+ depth: context.depth + 1
16632
+ });
16633
+ for (const nestedSegment of nested) {
16634
+ requirements.push(
16635
+ ...nestedSegment.requirements.map(
16636
+ (entry) => withInnerProvenance(entry, recipe, head, commandRedacted)
16637
+ )
16638
+ );
16639
+ for (const signal of nestedSegment.signals) {
16640
+ signals.add(signal);
16641
+ }
16642
+ opacity = joinNestedOpacity(opacity, nestedSegment);
16503
16643
  }
16504
- index += 1;
16505
- continue;
16506
16644
  }
16507
- 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) {
16508
- continue;
16509
- }
16510
- if (longFlags.has(arg)) {
16511
- continue;
16512
- }
16513
- if (/^[+-][A-Za-z]+$/.test(arg)) {
16514
- const options = arg.slice(1);
16515
- if ([...options].some((option) => !shortFlags.has(option))) {
16516
- return null;
16517
- }
16518
- syntaxOnly ||= options.includes("n");
16519
- continue;
16645
+ if (launcher.opaque || launcher.recipes.length === 0) {
16646
+ requirements.push(
16647
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
16648
+ `launcher.${launcher.reason}`
16649
+ ])
16650
+ );
16651
+ opacity = joinEffectOpacity(opacity, "opaque");
16520
16652
  }
16521
- if (arg === "--") {
16522
- script = args[index + 1] ?? null;
16523
- break;
16653
+ return shellSegment(commandRedacted, head, requirements, opacity, signals);
16654
+ }
16655
+ const egress = decodeEgressEffects({
16656
+ tokens,
16657
+ cwd: context.cwd,
16658
+ segment: commandRedacted
16659
+ });
16660
+ if (egress) {
16661
+ requirements.push(...egress);
16662
+ for (const signal of egress.flatMap((entry) => entry.evidence.signals)) {
16663
+ signals.add(signal);
16524
16664
  }
16525
- if (arg.startsWith("-") || arg.startsWith("+")) {
16526
- return null;
16665
+ } else {
16666
+ const git = decodeGitEffects({
16667
+ tokens,
16668
+ cwd: context.cwd,
16669
+ repoRoot: context.repoRoot,
16670
+ segment: commandRedacted
16671
+ });
16672
+ if (git) {
16673
+ requirements.push(...git);
16674
+ for (const signal of git.flatMap((entry) => entry.evidence.signals)) {
16675
+ signals.add(signal);
16676
+ }
16677
+ } else {
16678
+ const processRequirements = decodeProcessOrFilesystem({
16679
+ tokens,
16680
+ head,
16681
+ env,
16682
+ cwd: context.cwd,
16683
+ repoRoot: context.repoRoot,
16684
+ segment: commandRedacted
16685
+ });
16686
+ if (isGrammarUnknownOnly(processRequirements, head)) {
16687
+ const argvDelegate = peelArgvDelegateArgv(tokens);
16688
+ if (argvDelegate && !argvDelegate.opaque && shouldApplyArgvDelegate(head, argvDelegate.innerTokens, context.depth)) {
16689
+ const innerRecipe = innerRecipeFromArgvDelegate(argvDelegate);
16690
+ if (innerRecipe) {
16691
+ requirements.push(
16692
+ processRequirement(head, "inspect", commandRedacted, [
16693
+ "process.argv_delegate",
16694
+ ...argvDelegate.signals
16695
+ ])
16696
+ );
16697
+ for (const signal of argvDelegate.signals) {
16698
+ signals.add(signal);
16699
+ }
16700
+ const nested = lowerTopLevelSegments(innerRecipe, {
16701
+ ...context,
16702
+ command: innerRecipe,
16703
+ env,
16704
+ depth: context.depth + 1
16705
+ });
16706
+ for (const nestedSegment of nested) {
16707
+ requirements.push(
16708
+ ...nestedSegment.requirements.map(
16709
+ (entry) => withInnerProvenance(entry, innerRecipe, head, commandRedacted)
16710
+ )
16711
+ );
16712
+ for (const signal of nestedSegment.signals) {
16713
+ signals.add(signal);
16714
+ }
16715
+ opacity = joinNestedOpacity(opacity, nestedSegment);
16716
+ }
16717
+ return shellSegment(commandRedacted, head, requirements, opacity, signals);
16718
+ }
16719
+ }
16720
+ if (argvDelegate?.opaque) {
16721
+ requirements.push(
16722
+ processRequirement(head, "spawn", commandRedacted, [
16723
+ "process.argv_delegate",
16724
+ argvDelegate.reason,
16725
+ ...argvDelegate.signals
16726
+ ]),
16727
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
16728
+ argvDelegate.reason,
16729
+ "process.argv_delegate_opaque"
16730
+ ])
16731
+ );
16732
+ for (const signal of argvDelegate.signals) {
16733
+ signals.add(signal);
16734
+ }
16735
+ signals.add("process.argv_delegate_opaque");
16736
+ return shellSegment(
16737
+ commandRedacted,
16738
+ head,
16739
+ requirements,
16740
+ joinEffectOpacity(opacity, "opaque"),
16741
+ signals
16742
+ );
16743
+ }
16744
+ }
16745
+ requirements.push(...processRequirements);
16527
16746
  }
16528
- script = arg;
16529
- break;
16530
- }
16531
- return syntaxOnly ? script : null;
16532
- }
16533
- function isOptionToken(value) {
16534
- return value.startsWith("-") || value.startsWith("+");
16535
- }
16536
- function databaseEndpoint(raw) {
16537
- if (!raw || raw.includes("$")) {
16538
- return null;
16539
16747
  }
16540
- try {
16541
- const url = new URL(raw);
16542
- if (!url.hostname || !url.protocol) {
16543
- return null;
16748
+ const environmentSignals = effectChangingEnvironmentSignals(head, env, environment.changedNames);
16749
+ if (environmentSignals.length > 0) {
16750
+ requirements.push(
16751
+ requirement2(
16752
+ "indeterminate",
16753
+ "indeterminate",
16754
+ { kind: "unknown" },
16755
+ commandRedacted,
16756
+ environmentSignals
16757
+ )
16758
+ );
16759
+ opacity = joinEffectOpacity(opacity, "opaque");
16760
+ for (const signal of environmentSignals) {
16761
+ signals.add(signal);
16544
16762
  }
16545
- return {
16546
- host: url.hostname.toLowerCase(),
16547
- protocol: url.protocol.slice(0, -1),
16548
- ...url.port ? { port: Number(url.port) } : {}
16549
- };
16550
- } catch {
16551
- return null;
16552
16763
  }
16553
- }
16554
- function resolvePathOperand(operand, cwd) {
16555
- if (operand === "~") {
16556
- return process.env.HOME ?? operand;
16764
+ if (context.pipeToShellSegment) {
16765
+ signals.add("pipe_to_shell");
16766
+ ensureRequirement(
16767
+ requirements,
16768
+ requirement2(
16769
+ "process.exec",
16770
+ "process.exec",
16771
+ { kind: "executable", command: head || "sh", operation: "spawn" },
16772
+ commandRedacted,
16773
+ ["pipe_to_shell"]
16774
+ )
16775
+ );
16776
+ ensureRequirement(
16777
+ requirements,
16778
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
16779
+ "pipe_to_shell"
16780
+ ])
16781
+ );
16782
+ opacity = joinEffectOpacity(opacity, "opaque");
16557
16783
  }
16558
- if (operand.startsWith("~/")) {
16559
- return path42.join(process.env.HOME ?? "~", operand.slice(2));
16784
+ if (opacity === "unparseable" || opacity === "opaque") {
16785
+ ensureRequirement(
16786
+ requirements,
16787
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
16788
+ `shell.${opacity}`
16789
+ ])
16790
+ );
16560
16791
  }
16561
- return path42.resolve(cwd, operand);
16562
- }
16563
- function isShellHead(head) {
16564
- return head === "bash" || head === "sh" || head === "zsh" || head === "dash" || head === "fish";
16565
- }
16566
- function pipeToShell(command) {
16567
- return /(?:^|[|;&]\s*)(?:bash|sh|zsh|dash|fish)(?:\s|$)/.test(command) && /\|/.test(command);
16568
- }
16569
- function withInnerProvenance(requirementValue, innerCommand, launcher, outerSegment) {
16570
- const provenance = {
16571
- ...requirementValue.provenance,
16572
- segment: outerSegment,
16573
- innerCommand: requirementValue.provenance.innerCommand ?? innerCommand,
16574
- ...launcher === "npm" || launcher === "pnpm" ? { launcher, phase: "delegate" } : {}
16575
- };
16576
- const launcherProvenance = {
16577
- segment: outerSegment,
16578
- innerCommand,
16579
- ...launcher === "npm" || launcher === "pnpm" ? { launcher, phase: "delegate" } : {}
16580
- };
16581
- return withProvenances(requirementValue, provenance, launcherProvenance);
16582
- }
16583
- function withProvenances(requirementValue, provenance, contributing) {
16584
- const provenances = [
16585
- ...requirementValue.provenances ?? [requirementValue.provenance],
16586
- contributing,
16587
- provenance
16588
- ].filter(
16589
- (candidate, index, all) => all.findIndex((entry) => JSON.stringify(entry) === JSON.stringify(candidate)) === index
16590
- );
16591
- return { ...requirementValue, provenance, provenances };
16792
+ return shellSegment(commandRedacted, head, requirements, opacity, signals);
16592
16793
  }
16593
- function ensureRequirement(requirements, candidate) {
16594
- if (!requirements.some(
16595
- (entry) => entry.action === candidate.action && JSON.stringify(entry.resource) === JSON.stringify(candidate.resource)
16596
- )) {
16597
- requirements.push(candidate);
16794
+ function addSubstitutionEffects(requirements, command, context, segment, signals) {
16795
+ const inners = structuralSubstitutionInners(command);
16796
+ for (const inner of inners) {
16797
+ signals.add("command_substitution");
16798
+ if (context.depth >= MAX_LOWER_DEPTH) {
16799
+ requirements.push(
16800
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
16801
+ "shell.substitution_depth_exceeded"
16802
+ ])
16803
+ );
16804
+ continue;
16805
+ }
16806
+ const nestedSegments = lowerTopLevelSegments(inner, {
16807
+ ...context,
16808
+ command: inner,
16809
+ depth: context.depth + 1
16810
+ });
16811
+ for (const nested of nestedSegments) {
16812
+ requirements.push(
16813
+ ...nested.requirements.map((entry) => {
16814
+ const outerProvenance = {
16815
+ segment,
16816
+ innerCommand: inner
16817
+ };
16818
+ const provenance = {
16819
+ ...entry.provenance,
16820
+ segment,
16821
+ innerCommand: entry.provenance.innerCommand ?? inner
16822
+ };
16823
+ return withProvenances(entry, provenance, outerProvenance);
16824
+ })
16825
+ );
16826
+ }
16598
16827
  }
16599
16828
  }
16600
- function requirement2(tag, action, resource, segment, signals) {
16601
- return {
16602
- tag,
16603
- action,
16604
- resource,
16605
- evidence: {
16606
- level: tag === "indeterminate" ? "indeterminate" : "certain",
16607
- signals: [...new Set(signals)].sort(),
16608
- basis: ["shell_semantic_lowering"]
16609
- },
16610
- provenance: { segment }
16611
- };
16612
- }
16613
16829
 
16614
16830
  // src/core/verdict/verdict.ts
16615
16831
  init_path_utils();
@@ -16829,6 +17045,13 @@ function policyAuth(options) {
16829
17045
  function scrubPayload(value, options) {
16830
17046
  return scrubValue(value, options.scrubOptions);
16831
17047
  }
17048
+ function fingerprintPayload(payload, options) {
17049
+ const toolInput = fingerprintToolInputSource(payload);
17050
+ if (Object.keys(toolInput).length > 0) {
17051
+ return toolInput;
17052
+ }
17053
+ return scrubPayload(payload.tool_input ?? {}, options);
17054
+ }
16832
17055
  function extractFilePath(payload) {
16833
17056
  const toolInput = payload.tool_input;
16834
17057
  if (!toolInput || typeof toolInput !== "object") {
@@ -17003,11 +17226,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17003
17226
  verdict: "deny_pending_approval",
17004
17227
  reason: "tool_shell_missing_command",
17005
17228
  summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17006
- fingerprint: toolFingerprint(
17007
- toolName,
17008
- scrubPayload(payload.tool_input ?? {}, options),
17009
- repoRoot
17010
- ),
17229
+ fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17011
17230
  assessment: {
17012
17231
  reversibility: "irreversible",
17013
17232
  external: false,
@@ -17021,11 +17240,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17021
17240
  verdict: "allow_flagged",
17022
17241
  reason: "tool_shell_missing_command",
17023
17242
  summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17024
- fingerprint: toolFingerprint(
17025
- toolName,
17026
- scrubPayload(payload.tool_input ?? {}, options),
17027
- repoRoot
17028
- ),
17243
+ fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17029
17244
  assessment: {
17030
17245
  reversibility: "recoverable_with_cost",
17031
17246
  external: false,
@@ -17049,11 +17264,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17049
17264
  verdict: "deny_pending_approval",
17050
17265
  reason: "file_mutation_missing_path",
17051
17266
  summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17052
- fingerprint: toolFingerprint(
17053
- toolName,
17054
- scrubPayload(payload.tool_input ?? {}, options),
17055
- repoRoot
17056
- ),
17267
+ fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17057
17268
  assessment: {
17058
17269
  reversibility: "irreversible",
17059
17270
  external: false,
@@ -17067,11 +17278,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17067
17278
  verdict: "allow_flagged",
17068
17279
  reason: "file_mutation_missing_path",
17069
17280
  summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17070
- fingerprint: toolFingerprint(
17071
- toolName,
17072
- scrubPayload(payload.tool_input ?? {}, options),
17073
- repoRoot
17074
- ),
17281
+ fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17075
17282
  assessment: {
17076
17283
  reversibility: "recoverable_with_cost",
17077
17284
  external: false,
@@ -17082,7 +17289,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17082
17289
  };
17083
17290
  }
17084
17291
  const signals = [];
17085
- const resolvedPath = path43.isAbsolute(filePath) ? filePath : path43.resolve(cwd, filePath);
17292
+ const resolvedPath = path50.isAbsolute(filePath) ? filePath : path50.resolve(cwd, filePath);
17086
17293
  const hitsProtectedRoot = protectedRoots.some((root) => pathWithinRoot(root, resolvedPath));
17087
17294
  if (hitsProtectedRoot) {
17088
17295
  signals.push("control_plane_path");
@@ -17221,11 +17428,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17221
17428
  verdict: "deny_pending_approval",
17222
17429
  reason: "apply_patch_missing_path",
17223
17430
  summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17224
- fingerprint: toolFingerprint(
17225
- toolName,
17226
- scrubPayload(payload.tool_input ?? {}, options),
17227
- repoRoot
17228
- ),
17431
+ fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17229
17432
  assessment: {
17230
17433
  reversibility: "irreversible",
17231
17434
  external: false,
@@ -17239,11 +17442,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17239
17442
  verdict: "allow_flagged",
17240
17443
  reason: "apply_patch_missing_path",
17241
17444
  summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17242
- fingerprint: toolFingerprint(
17243
- toolName,
17244
- scrubPayload(payload.tool_input ?? {}, options),
17245
- repoRoot
17246
- ),
17445
+ fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17247
17446
  assessment: {
17248
17447
  reversibility: "recoverable_with_cost",
17249
17448
  external: false,
@@ -17274,11 +17473,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17274
17473
  verdict: "allow_flagged",
17275
17474
  reason: sawDelete ? "file_delete" : "file_mutation",
17276
17475
  summary: targets.map((target) => target.path).join(", "),
17277
- fingerprint: toolFingerprint(
17278
- toolName,
17279
- scrubPayload(payload.tool_input ?? {}, options),
17280
- repoRoot
17281
- ),
17476
+ fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17282
17477
  assessment: {
17283
17478
  reversibility: "recoverable_with_cost",
17284
17479
  external: false,
@@ -17292,11 +17487,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17292
17487
  verdict: "allow",
17293
17488
  reason: "unclassified_tool",
17294
17489
  summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17295
- fingerprint: toolFingerprint(
17296
- toolName,
17297
- scrubPayload(payload.tool_input ?? {}, options),
17298
- repoRoot
17299
- ),
17490
+ fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17300
17491
  assessment: {
17301
17492
  reversibility: "reversible",
17302
17493
  external: false,
@@ -17684,7 +17875,7 @@ function hashDecisionConfig(config) {
17684
17875
  init_fingerprint2();
17685
17876
 
17686
17877
  // src/version.ts
17687
- var PACKAGE_VERSION = "0.9.3";
17878
+ var PACKAGE_VERSION = "0.9.4";
17688
17879
 
17689
17880
  // src/runtime-provenance.ts
17690
17881
  function resolveRuntimeArtifactHash(artifactHash) {
@@ -17759,7 +17950,7 @@ init_path_utils();
17759
17950
  import { randomUUID as randomUUID5 } from "node:crypto";
17760
17951
  import { existsSync as existsSync15 } from "node:fs";
17761
17952
  import { mkdir as mkdir11, readdir as readdir3, readFile as readFile13, rename as rename3, rm as rm6 } from "node:fs/promises";
17762
- import path48 from "node:path";
17953
+ import path55 from "node:path";
17763
17954
 
17764
17955
  // src/core/recovery/artifact-store.ts
17765
17956
  init_fingerprint2();
@@ -17767,7 +17958,7 @@ init_path_utils();
17767
17958
  import { randomUUID as randomUUID4 } from "node:crypto";
17768
17959
  import { existsSync as existsSync13 } from "node:fs";
17769
17960
  import { lstat as lstat7, mkdir as mkdir10, open as open5, readdir as readdir2, readFile as readFile10, rename as rename2, rm as rm5, writeFile as writeFile7 } from "node:fs/promises";
17770
- import path45 from "node:path";
17961
+ import path52 from "node:path";
17771
17962
 
17772
17963
  // src/core/recovery/snapshot-node.ts
17773
17964
  init_fingerprint2();
@@ -17787,25 +17978,25 @@ import {
17787
17978
  symlink as symlink3,
17788
17979
  writeFile as writeFile6
17789
17980
  } from "node:fs/promises";
17790
- import path44 from "node:path";
17981
+ import path51 from "node:path";
17791
17982
  var RECOVERY_UNSUPPORTED_FILE_KIND = "recovery_unsupported_file_kind";
17792
17983
  function validRecoveryRelativePath(relativePath) {
17793
- if (!relativePath || relativePath.includes("\0") || path44.isAbsolute(relativePath)) return false;
17794
- const normalized = path44.normalize(relativePath);
17795
- return normalized !== "." && normalized !== ".." && !normalized.startsWith(`..${path44.sep}`);
17984
+ if (!relativePath || relativePath.includes("\0") || path51.isAbsolute(relativePath)) return false;
17985
+ const normalized = path51.normalize(relativePath);
17986
+ return normalized !== "." && normalized !== ".." && !normalized.startsWith(`..${path51.sep}`);
17796
17987
  }
17797
17988
  async function assertRecoverySafeTarget(resourceRoot, relativePath) {
17798
17989
  if (!validRecoveryRelativePath(relativePath)) throw new Error("recovery_path_escape");
17799
17990
  const root = canonicalPath(resourceRoot);
17800
- const target = path44.resolve(root, relativePath);
17801
- const relative = path44.relative(root, target);
17802
- if (relative === ".." || relative.startsWith(`..${path44.sep}`) || path44.isAbsolute(relative)) {
17991
+ const target = path51.resolve(root, relativePath);
17992
+ const relative = path51.relative(root, target);
17993
+ if (relative === ".." || relative.startsWith(`..${path51.sep}`) || path51.isAbsolute(relative)) {
17803
17994
  throw new Error("recovery_path_escape");
17804
17995
  }
17805
17996
  let current = root;
17806
- const parentParts = path44.relative(root, path44.dirname(target)).split(path44.sep).filter(Boolean);
17997
+ const parentParts = path51.relative(root, path51.dirname(target)).split(path51.sep).filter(Boolean);
17807
17998
  for (const part of parentParts) {
17808
- current = path44.join(current, part);
17999
+ current = path51.join(current, part);
17809
18000
  if (!existsSync12(current)) break;
17810
18001
  const info = await lstat6(current);
17811
18002
  if (info.isSymbolicLink()) throw new Error("recovery_symlink_escape");
@@ -17861,7 +18052,7 @@ async function captureRecoverySnapshot(filePath, options) {
17861
18052
  let blob;
17862
18053
  if (options?.blobDir) {
17863
18054
  await mkdir9(options.blobDir, { recursive: true, mode: 448 });
17864
- const blobPath = path44.join(options.blobDir, hash);
18055
+ const blobPath = path51.join(options.blobDir, hash);
17865
18056
  if (!existsSync12(blobPath)) {
17866
18057
  await writeFile6(blobPath, content, { mode: 384 });
17867
18058
  await fsyncPath(blobPath);
@@ -17916,7 +18107,7 @@ async function validateRecoverySnapshot(params) {
17916
18107
  if (record.blob !== `blobs/${record.hash}`) throw new Error(params.corruptReason);
17917
18108
  let content;
17918
18109
  try {
17919
- content = await readFile9(path44.join(params.artifactDir, record.blob));
18110
+ content = await readFile9(path51.join(params.artifactDir, record.blob));
17920
18111
  } catch {
17921
18112
  throw new Error(params.corruptReason);
17922
18113
  }
@@ -17944,13 +18135,13 @@ var RECOVERY_STATES = /* @__PURE__ */ new Set([
17944
18135
  ]);
17945
18136
  var STAGING_STALE_MS = 5 * 6e4;
17946
18137
  function checkpointsRoot(stateDir) {
17947
- return path45.join(stateDir, "recovery", "checkpoints");
18138
+ return path52.join(stateDir, "recovery", "checkpoints");
17948
18139
  }
17949
18140
  function checkpointDir(stateDir, checkpointId) {
17950
18141
  if (!/^cp_[a-f0-9]{24}$/.test(checkpointId)) {
17951
18142
  throw new Error("invalid_recovery_checkpoint_id");
17952
18143
  }
17953
- return path45.join(checkpointsRoot(stateDir), checkpointId);
18144
+ return path52.join(checkpointsRoot(stateDir), checkpointId);
17954
18145
  }
17955
18146
  async function fsyncPath2(filePath) {
17956
18147
  const handle = await open5(filePath, "r");
@@ -17961,13 +18152,13 @@ async function fsyncPath2(filePath) {
17961
18152
  }
17962
18153
  }
17963
18154
  async function atomicWriteJson(filePath, value) {
17964
- await mkdir10(path45.dirname(filePath), { recursive: true, mode: 448 });
18155
+ await mkdir10(path52.dirname(filePath), { recursive: true, mode: 448 });
17965
18156
  const temporary = `${filePath}.tmp-${randomUUID4()}`;
17966
18157
  await writeFile7(temporary, `${JSON.stringify(value, null, 2)}
17967
18158
  `, { mode: 384 });
17968
18159
  await fsyncPath2(temporary);
17969
18160
  await rename2(temporary, filePath);
17970
- await fsyncPath2(path45.dirname(filePath));
18161
+ await fsyncPath2(path52.dirname(filePath));
17971
18162
  }
17972
18163
  async function writeRecoveryState(artifactDir, state, manifestHash, detail) {
17973
18164
  const value = {
@@ -17977,13 +18168,13 @@ async function writeRecoveryState(artifactDir, state, manifestHash, detail) {
17977
18168
  manifestHash,
17978
18169
  ...detail ? { detail } : {}
17979
18170
  };
17980
- await atomicWriteJson(path45.join(artifactDir, "state.json"), value);
18171
+ await atomicWriteJson(path52.join(artifactDir, "state.json"), value);
17981
18172
  }
17982
18173
  async function directorySize(root) {
17983
18174
  if (!existsSync13(root)) return 0;
17984
18175
  let total = 0;
17985
18176
  for (const entry of await readdir2(root, { withFileTypes: true })) {
17986
- const entryPath = path45.join(root, entry.name);
18177
+ const entryPath = path52.join(root, entry.name);
17987
18178
  if (entry.isDirectory()) total += await directorySize(entryPath);
17988
18179
  else total += (await lstat7(entryPath)).size;
17989
18180
  }
@@ -18028,9 +18219,9 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
18028
18219
  let rawManifest;
18029
18220
  let state;
18030
18221
  try {
18031
- rawManifest = JSON.parse(await readFile10(path45.join(artifactDir, "manifest.json"), "utf8"));
18222
+ rawManifest = JSON.parse(await readFile10(path52.join(artifactDir, "manifest.json"), "utf8"));
18032
18223
  state = JSON.parse(
18033
- await readFile10(path45.join(artifactDir, "state.json"), "utf8")
18224
+ await readFile10(path52.join(artifactDir, "state.json"), "utf8")
18034
18225
  );
18035
18226
  } catch {
18036
18227
  throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
@@ -18046,10 +18237,10 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
18046
18237
  }
18047
18238
  const entryPaths = /* @__PURE__ */ new Set();
18048
18239
  for (const entry of manifest.entries) {
18049
- 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(path45.normalize(entry.path))) {
18240
+ 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(path52.normalize(entry.path))) {
18050
18241
  throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
18051
18242
  }
18052
- entryPaths.add(path45.normalize(entry.path));
18243
+ entryPaths.add(path52.normalize(entry.path));
18053
18244
  for (const [side, snapshot] of [
18054
18245
  ["before", entry.before],
18055
18246
  ["after", entry.after]
@@ -18063,7 +18254,7 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
18063
18254
  });
18064
18255
  }
18065
18256
  }
18066
- const receiptPath = path45.join(artifactDir, "receipt.json");
18257
+ const receiptPath = path52.join(artifactDir, "receipt.json");
18067
18258
  let receipt;
18068
18259
  if (["applied", "restoring", "restored", "conflict"].includes(state.state) || existsSync13(receiptPath)) {
18069
18260
  receipt = await readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash);
@@ -18073,7 +18264,7 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
18073
18264
  async function readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash) {
18074
18265
  let rawReceipt;
18075
18266
  try {
18076
- rawReceipt = JSON.parse(await readFile10(path45.join(artifactDir, "receipt.json"), "utf8"));
18267
+ rawReceipt = JSON.parse(await readFile10(path52.join(artifactDir, "receipt.json"), "utf8"));
18077
18268
  } catch {
18078
18269
  throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
18079
18270
  }
@@ -18096,7 +18287,7 @@ async function readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHas
18096
18287
  return receipt;
18097
18288
  }
18098
18289
  async function ensureRecoveryReceipt(artifactDir, manifest, manifestHash) {
18099
- const receiptPath = path45.join(artifactDir, "receipt.json");
18290
+ const receiptPath = path52.join(artifactDir, "receipt.json");
18100
18291
  if (existsSync13(receiptPath)) {
18101
18292
  return readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash);
18102
18293
  }
@@ -18121,7 +18312,7 @@ async function artifactRepoRoot(stateDir, checkpointId) {
18121
18312
  const artifactDir = checkpointDir(stateDir, checkpointId);
18122
18313
  try {
18123
18314
  const manifest = JSON.parse(
18124
- await readFile10(path45.join(artifactDir, "manifest.json"), "utf8")
18315
+ await readFile10(path52.join(artifactDir, "manifest.json"), "utf8")
18125
18316
  );
18126
18317
  if (typeof manifest.repoRoot === "string" && manifest.repoRoot) {
18127
18318
  return canonicalPath(manifest.repoRoot);
@@ -18129,7 +18320,7 @@ async function artifactRepoRoot(stateDir, checkpointId) {
18129
18320
  } catch {
18130
18321
  }
18131
18322
  try {
18132
- const owner = JSON.parse(await readFile10(path45.join(artifactDir, "owner.json"), "utf8"));
18323
+ const owner = JSON.parse(await readFile10(path52.join(artifactDir, "owner.json"), "utf8"));
18133
18324
  return typeof owner.repoRoot === "string" && owner.repoRoot ? canonicalPath(owner.repoRoot) : null;
18134
18325
  } catch {
18135
18326
  return null;
@@ -18149,10 +18340,10 @@ async function cleanupOrphanedStaging(stateDir) {
18149
18340
  const now = Date.now();
18150
18341
  for (const entry of await readdir2(root, { withFileTypes: true })) {
18151
18342
  if (!entry.isDirectory() || !/^\.tmp-cp_[a-f0-9]{24}$/.test(entry.name)) continue;
18152
- const stagingPath = path45.join(root, entry.name);
18343
+ const stagingPath = path52.join(root, entry.name);
18153
18344
  let stale = false;
18154
18345
  try {
18155
- const owner = JSON.parse(await readFile10(path45.join(stagingPath, "owner.json"), "utf8"));
18346
+ const owner = JSON.parse(await readFile10(path52.join(stagingPath, "owner.json"), "utf8"));
18156
18347
  const pid = typeof owner.pid === "number" ? owner.pid : Number.NaN;
18157
18348
  const createdAt = typeof owner.createdAt === "string" ? Date.parse(owner.createdAt) : NaN;
18158
18349
  let alive = false;
@@ -18194,7 +18385,7 @@ async function markRecoveryCheckpointApplied(stateDir, checkpoint) {
18194
18385
  init_fingerprint2();
18195
18386
  import { existsSync as existsSync14 } from "node:fs";
18196
18387
  import { readFile as readFile11 } from "node:fs/promises";
18197
- import path46 from "node:path";
18388
+ import path53 from "node:path";
18198
18389
  async function matchRecoverySide(resourceRoot, entries, side) {
18199
18390
  for (const entry of entries) {
18200
18391
  const target = await assertRecoverySafeTarget(resourceRoot, entry.path);
@@ -18209,7 +18400,7 @@ async function reconcileRecoveryCheckpoint(stateDir, checkpointId) {
18209
18400
  } catch {
18210
18401
  const artifactDir = checkpointDir(stateDir, checkpointId);
18211
18402
  if (existsSync14(artifactDir)) {
18212
- const manifestPath = path46.join(artifactDir, "manifest.json");
18403
+ const manifestPath = path53.join(artifactDir, "manifest.json");
18213
18404
  const hash = existsSync14(manifestPath) ? hashValue(await readFile11(manifestPath, "utf8")) : "unavailable";
18214
18405
  await writeRecoveryState(artifactDir, "corrupt", hash, RECOVERY_CHECKPOINT_CORRUPT);
18215
18406
  }
@@ -18246,7 +18437,7 @@ async function reconcileRecoveryCheckpoint(stateDir, checkpointId) {
18246
18437
  // src/core/recovery/resource-identity.ts
18247
18438
  init_fingerprint2();
18248
18439
  import { lstat as lstat8, readFile as readFile12, realpath as realpath3 } from "node:fs/promises";
18249
- import path47 from "node:path";
18440
+ import path54 from "node:path";
18250
18441
  async function currentRecoveryResourceIdentity(resourceRoot, resourceKind) {
18251
18442
  const resolvedRoot = await realpath3(resourceRoot);
18252
18443
  if (resourceKind === "directory") {
@@ -18254,13 +18445,13 @@ async function currentRecoveryResourceIdentity(resourceRoot, resourceKind) {
18254
18445
  if (!rootInfo.isDirectory()) throw new Error("recovery_repo_identity_unavailable");
18255
18446
  return hashValue(`${resolvedRoot}\0${rootInfo.dev}:${rootInfo.ino}:${rootInfo.birthtimeMs}`);
18256
18447
  }
18257
- const dotGit = path47.join(resolvedRoot, ".git");
18448
+ const dotGit = path54.join(resolvedRoot, ".git");
18258
18449
  const gitInfo = await lstat8(dotGit);
18259
18450
  let gitMetadataPath = dotGit;
18260
18451
  if (gitInfo.isFile()) {
18261
18452
  const marker = (await readFile12(dotGit, "utf8")).trim();
18262
18453
  if (!marker.startsWith("gitdir:")) throw new Error("recovery_repo_identity_unavailable");
18263
- gitMetadataPath = path47.resolve(resolvedRoot, marker.slice("gitdir:".length).trim());
18454
+ gitMetadataPath = path54.resolve(resolvedRoot, marker.slice("gitdir:".length).trim());
18264
18455
  } else if (!gitInfo.isDirectory()) {
18265
18456
  throw new Error("recovery_repo_identity_unavailable");
18266
18457
  }
@@ -18333,16 +18524,16 @@ async function prepareRecoveryCheckpoint(params) {
18333
18524
  throw new Error(RECOVERY_CHECKPOINT_QUOTA);
18334
18525
  }
18335
18526
  const checkpointId = `cp_${randomUUID5().replaceAll("-", "").slice(0, 24)}`;
18336
- const temporary = path48.join(checkpointsRoot(params.stateDir), `.tmp-${checkpointId}`);
18527
+ const temporary = path55.join(checkpointsRoot(params.stateDir), `.tmp-${checkpointId}`);
18337
18528
  const finalDir = checkpointDir(params.stateDir, checkpointId);
18338
18529
  await mkdir11(temporary, { recursive: true, mode: 448 });
18339
- await atomicWriteJson(path48.join(temporary, "owner.json"), {
18530
+ await atomicWriteJson(path55.join(temporary, "owner.json"), {
18340
18531
  version: 1,
18341
18532
  pid: process.pid,
18342
18533
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
18343
18534
  repoRoot: canonicalPath(params.repoRoot)
18344
18535
  });
18345
- await mkdir11(path48.join(temporary, "blobs"), { recursive: true, mode: 448 });
18536
+ await mkdir11(path55.join(temporary, "blobs"), { recursive: true, mode: 448 });
18346
18537
  try {
18347
18538
  const entries = [];
18348
18539
  const protectedRoots = (params.protectedRoots ?? []).map(canonicalPath);
@@ -18351,8 +18542,8 @@ async function prepareRecoveryCheckpoint(params) {
18351
18542
  )) {
18352
18543
  const target = await assertRecoverySafeTarget(params.repoRoot, change.relativePath);
18353
18544
  if (protectedRoots.some((root) => {
18354
- const relative = path48.relative(root, target);
18355
- return relative === "" || relative !== ".." && !relative.startsWith(`..${path48.sep}`) && !path48.isAbsolute(relative);
18545
+ const relative = path55.relative(root, target);
18546
+ return relative === "" || relative !== ".." && !relative.startsWith(`..${path55.sep}`) && !path55.isAbsolute(relative);
18356
18547
  })) {
18357
18548
  throw new Error("recovery_protected_path");
18358
18549
  }
@@ -18364,7 +18555,7 @@ async function prepareRecoveryCheckpoint(params) {
18364
18555
  entries.push({
18365
18556
  path: change.relativePath,
18366
18557
  before: await captureRecoverySnapshot(baseline, {
18367
- blobDir: path48.join(temporary, "blobs")
18558
+ blobDir: path55.join(temporary, "blobs")
18368
18559
  }),
18369
18560
  after: withoutRecoveryBlob(await captureRecoverySnapshot(source))
18370
18561
  });
@@ -18401,7 +18592,7 @@ async function prepareRecoveryCheckpoint(params) {
18401
18592
  entries
18402
18593
  };
18403
18594
  const manifestHash = hashValue(canonicalStringify(manifest));
18404
- await atomicWriteJson(path48.join(temporary, "manifest.json"), manifest);
18595
+ await atomicWriteJson(path55.join(temporary, "manifest.json"), manifest);
18405
18596
  await writeRecoveryState(temporary, "prepared", manifestHash);
18406
18597
  await fsyncPath2(temporary);
18407
18598
  const projectedBytes = await recoveryCheckpointStorageBytes(params.stateDir, params.repoRoot);
@@ -18444,7 +18635,7 @@ async function listRecoveryCheckpoints(stateDir, repoRoot) {
18444
18635
  } catch {
18445
18636
  try {
18446
18637
  const raw = JSON.parse(
18447
- await readFile13(path48.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
18638
+ await readFile13(path55.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
18448
18639
  );
18449
18640
  rootFromArtifact = typeof raw.repoRoot === "string" && raw.repoRoot ? raw.repoRoot : void 0;
18450
18641
  } catch {
@@ -18478,7 +18669,7 @@ async function listRecoveryCheckpoints(stateDir, repoRoot) {
18478
18669
  } catch {
18479
18670
  try {
18480
18671
  const manifest = JSON.parse(
18481
- await readFile13(path48.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
18672
+ await readFile13(path55.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
18482
18673
  );
18483
18674
  if (manifest.checkpointId !== id || ![1, 2].includes(manifest.version)) continue;
18484
18675
  if (repoRoot && canonicalPath(manifest.repoRoot) !== canonicalPath(repoRoot)) continue;
@@ -18508,7 +18699,7 @@ async function recoveryCheckpointStorageBytes(stateDir, repoRoot) {
18508
18699
  let total = 0;
18509
18700
  for (const entry of await readdir3(root, { withFileTypes: true })) {
18510
18701
  if (!entry.isDirectory()) continue;
18511
- const entryPath = path48.join(root, entry.name);
18702
+ const entryPath = path55.join(root, entry.name);
18512
18703
  if (/^cp_[a-f0-9]{24}$/.test(entry.name)) {
18513
18704
  if (await artifactRepoRoot(stateDir, entry.name) === expected) {
18514
18705
  total += await directorySize(entryPath);
@@ -18517,7 +18708,7 @@ async function recoveryCheckpointStorageBytes(stateDir, repoRoot) {
18517
18708
  }
18518
18709
  if (/^\.tmp-cp_[a-f0-9]{24}$/.test(entry.name)) {
18519
18710
  try {
18520
- const owner = JSON.parse(await readFile13(path48.join(entryPath, "owner.json"), "utf8"));
18711
+ const owner = JSON.parse(await readFile13(path55.join(entryPath, "owner.json"), "utf8"));
18521
18712
  if (typeof owner.repoRoot === "string" && canonicalPath(owner.repoRoot) === expected) {
18522
18713
  total += await directorySize(entryPath);
18523
18714
  }
@@ -18561,14 +18752,14 @@ init_scrub();
18561
18752
  // src/core/transactional/file-checkpoint-backend.ts
18562
18753
  import { cp, lstat as lstat11, mkdir as mkdir13, mkdtemp as mkdtemp5, readdir as readdir6, rm as rm9, writeFile as writeFile10 } from "node:fs/promises";
18563
18754
  import os5 from "node:os";
18564
- import path52 from "node:path";
18755
+ import path59 from "node:path";
18565
18756
 
18566
18757
  // src/core/transactional/file-checkpoint-git.ts
18567
18758
  init_path_utils();
18568
18759
  import { spawn as spawn8 } from "node:child_process";
18569
18760
  import { createHash as createHash13 } from "node:crypto";
18570
18761
  import { copyFile as copyFile3, lstat as lstat9, readdir as readdir4, readFile as readFile14 } from "node:fs/promises";
18571
- import path49 from "node:path";
18762
+ import path56 from "node:path";
18572
18763
  var FILE_CHECKPOINT_GIT_METADATA_CHANGED = "file_checkpoint_git_metadata_changed";
18573
18764
  var FILE_CHECKPOINT_SOURCE_CHANGED = "file_checkpoint_source_changed";
18574
18765
  var FILE_CHECKPOINT_CWD_OUTSIDE_ROOT = "file_checkpoint_cwd_outside_root";
@@ -18602,7 +18793,7 @@ function rethrowStableFileCheckpointError(error) {
18602
18793
  }
18603
18794
  async function rootGitMetadataPresent(repoRoot) {
18604
18795
  try {
18605
- await lstat9(path49.join(repoRoot, ".git"));
18796
+ await lstat9(path56.join(repoRoot, ".git"));
18606
18797
  return true;
18607
18798
  } catch {
18608
18799
  return false;
@@ -18651,10 +18842,10 @@ function execGit2(repoRoot, args) {
18651
18842
  }
18652
18843
  async function resolveGitPath(repoRoot, gitPath) {
18653
18844
  const trimmed = gitPath.trim();
18654
- if (path49.isAbsolute(trimmed)) {
18845
+ if (path56.isAbsolute(trimmed)) {
18655
18846
  return trimmed;
18656
18847
  }
18657
- return path49.join(repoRoot, trimmed);
18848
+ return path56.join(repoRoot, trimmed);
18658
18849
  }
18659
18850
  async function cloneBareWorktreeCopy(sourceRoot, destinationRoot) {
18660
18851
  await execGit2(sourceRoot, [
@@ -18696,7 +18887,7 @@ async function copyGitIndexState(sourceRoot, destinationRoot) {
18696
18887
  destinationRoot,
18697
18888
  await execGit2(destinationRoot, ["rev-parse", "--git-dir"])
18698
18889
  );
18699
- const destinationShared = path49.join(destinationGitDir, path49.basename(sourceShared));
18890
+ const destinationShared = path56.join(destinationGitDir, path56.basename(sourceShared));
18700
18891
  try {
18701
18892
  await copyFile3(sourceShared, destinationShared);
18702
18893
  } catch (error) {
@@ -18705,7 +18896,7 @@ async function copyGitIndexState(sourceRoot, destinationRoot) {
18705
18896
  }
18706
18897
  async function readGitFile(gitDir, relativePath) {
18707
18898
  try {
18708
- return await readFile14(path49.join(gitDir, relativePath));
18899
+ return await readFile14(path56.join(gitDir, relativePath));
18709
18900
  } catch {
18710
18901
  return null;
18711
18902
  }
@@ -18729,8 +18920,8 @@ async function hashResolvedGitPath(repoRoot, gitDir, gitPath, hash) {
18729
18920
  repoRoot,
18730
18921
  await execGit2(repoRoot, ["rev-parse", "--git-path", gitPath])
18731
18922
  );
18732
- const relative = path49.resolve(resolved).startsWith(path49.resolve(gitDir)) ? path49.relative(gitDir, resolved) : resolved;
18733
- const content = typeof relative === "string" && !relative.startsWith("..") && !path49.isAbsolute(relative) ? await readGitFile(gitDir, relative) : await readAbsoluteGitFile(resolved);
18923
+ const relative = path56.resolve(resolved).startsWith(path56.resolve(gitDir)) ? path56.relative(gitDir, resolved) : resolved;
18924
+ const content = typeof relative === "string" && !relative.startsWith("..") && !path56.isAbsolute(relative) ? await readGitFile(gitDir, relative) : await readAbsoluteGitFile(resolved);
18734
18925
  if (content !== null) {
18735
18926
  hashGitFileContent(hash, gitPath, content);
18736
18927
  }
@@ -18740,13 +18931,13 @@ async function hashResolvedGitPath(repoRoot, gitDir, gitPath, hash) {
18740
18931
  async function hashGitTree(gitDir, relativeDir, hash) {
18741
18932
  let names;
18742
18933
  try {
18743
- names = await readdir4(path49.join(gitDir, relativeDir));
18934
+ names = await readdir4(path56.join(gitDir, relativeDir));
18744
18935
  } catch {
18745
18936
  return;
18746
18937
  }
18747
18938
  for (const name of names.sort()) {
18748
- const relativePath = relativeDir ? path49.join(relativeDir, name) : name;
18749
- const absolutePath = path49.join(gitDir, relativePath);
18939
+ const relativePath = relativeDir ? path56.join(relativeDir, name) : name;
18940
+ const absolutePath = path56.join(gitDir, relativePath);
18750
18941
  let childNames = null;
18751
18942
  try {
18752
18943
  childNames = await readdir4(absolutePath);
@@ -18768,7 +18959,7 @@ async function hashGitTree(gitDir, relativeDir, hash) {
18768
18959
  }
18769
18960
  async function computeGitMetadataFingerprint(repoRoot) {
18770
18961
  const gitDirRel = (await execGit2(repoRoot, ["rev-parse", "--git-dir"])).trim();
18771
- const gitDir = path49.isAbsolute(gitDirRel) ? gitDirRel : path49.join(repoRoot, gitDirRel);
18962
+ const gitDir = path56.isAbsolute(gitDirRel) ? gitDirRel : path56.join(repoRoot, gitDirRel);
18772
18963
  const hash = createHash13("sha256");
18773
18964
  for (const file of [
18774
18965
  "HEAD",
@@ -18791,8 +18982,8 @@ async function computeGitMetadataFingerprint(repoRoot) {
18791
18982
  const sharedIndex = (await execGit2(repoRoot, ["rev-parse", "--shared-index-path"])).trim();
18792
18983
  if (sharedIndex) {
18793
18984
  const resolved = await resolveGitPath(repoRoot, sharedIndex);
18794
- const relative = path49.relative(gitDir, resolved);
18795
- const content = relative && !relative.startsWith("..") && !path49.isAbsolute(relative) ? await readGitFile(gitDir, relative) : await readAbsoluteGitFile(resolved);
18985
+ const relative = path56.relative(gitDir, resolved);
18986
+ const content = relative && !relative.startsWith("..") && !path56.isAbsolute(relative) ? await readGitFile(gitDir, relative) : await readAbsoluteGitFile(resolved);
18796
18987
  if (content !== null) {
18797
18988
  hashGitFileContent(hash, "shared-index", content);
18798
18989
  }
@@ -18803,7 +18994,7 @@ async function computeGitMetadataFingerprint(repoRoot) {
18803
18994
  await hashResolvedGitPath(repoRoot, gitDir, gitPath, hash);
18804
18995
  }
18805
18996
  try {
18806
- const rootGitPath = path49.join(repoRoot, ".git");
18997
+ const rootGitPath = path56.join(repoRoot, ".git");
18807
18998
  const rootGitInfo = await lstat9(rootGitPath);
18808
18999
  if (rootGitInfo.isFile()) {
18809
19000
  const content = await readAbsoluteGitFile(rootGitPath);
@@ -18819,14 +19010,14 @@ async function computeGitMetadataFingerprint(repoRoot) {
18819
19010
  function resolveExecutionCwdRelative(resourceRoot, cwd) {
18820
19011
  const resolvedCwd = canonicalPath(cwd);
18821
19012
  const resourceCanonical = canonicalPath(resourceRoot);
18822
- const relative = path49.relative(resourceCanonical, resolvedCwd);
19013
+ const relative = path56.relative(resourceCanonical, resolvedCwd);
18823
19014
  if (relative === "" || relative === ".") {
18824
19015
  return "";
18825
19016
  }
18826
- if (relative.startsWith("..") || path49.isAbsolute(relative)) {
19017
+ if (relative.startsWith("..") || path56.isAbsolute(relative)) {
18827
19018
  throw new Error(FILE_CHECKPOINT_CWD_OUTSIDE_ROOT);
18828
19019
  }
18829
- return relative.split(path49.sep).join("/");
19020
+ return relative.split(path56.sep).join("/");
18830
19021
  }
18831
19022
 
18832
19023
  // src/core/transactional/file-checkpoint-isolation.ts
@@ -18848,7 +19039,7 @@ function fileCheckpointIsolationReason(context) {
18848
19039
 
18849
19040
  // src/core/transactional/file-checkpoint-staging.ts
18850
19041
  import { readdir as readdir5, readFile as readFile15, rm as rm7, writeFile as writeFile8 } from "node:fs/promises";
18851
- import path50 from "node:path";
19042
+ import path57 from "node:path";
18852
19043
  function isOwnerProcessAlive(pid) {
18853
19044
  try {
18854
19045
  process.kill(pid, 0);
@@ -18858,12 +19049,12 @@ function isOwnerProcessAlive(pid) {
18858
19049
  }
18859
19050
  }
18860
19051
  async function writeOwnerMarker(stagingRoot, marker) {
18861
- await writeFile8(path50.join(stagingRoot, "owner.json"), `${JSON.stringify(marker)}
19052
+ await writeFile8(path57.join(stagingRoot, "owner.json"), `${JSON.stringify(marker)}
18862
19053
  `, "utf8");
18863
19054
  }
18864
19055
  async function readOwnerMarker(stagingRoot) {
18865
19056
  try {
18866
- const raw = await readFile15(path50.join(stagingRoot, "owner.json"), "utf8");
19057
+ const raw = await readFile15(path57.join(stagingRoot, "owner.json"), "utf8");
18867
19058
  return JSON.parse(raw.trim());
18868
19059
  } catch {
18869
19060
  return null;
@@ -18881,7 +19072,7 @@ async function collectDeadOwnerStaging(parentDir) {
18881
19072
  if (!name.startsWith("belay-file-checkpoint-")) {
18882
19073
  continue;
18883
19074
  }
18884
- const stagingRoot = path50.join(parentDir, name);
19075
+ const stagingRoot = path57.join(parentDir, name);
18885
19076
  const marker = await readOwnerMarker(stagingRoot);
18886
19077
  if (!marker) {
18887
19078
  dead.push(stagingRoot);
@@ -18913,7 +19104,7 @@ import {
18913
19104
  writeFile as writeFile9
18914
19105
  } from "node:fs/promises";
18915
19106
  import os4 from "node:os";
18916
- import path51 from "node:path";
19107
+ import path58 from "node:path";
18917
19108
  var FILE_CHECKPOINT_COPY_FAILED = "file_checkpoint_copy_failed";
18918
19109
  async function chmodSafe2(target, mode) {
18919
19110
  try {
@@ -18923,7 +19114,7 @@ async function chmodSafe2(target, mode) {
18923
19114
  }
18924
19115
  }
18925
19116
  async function copyRegularFile(sourcePath, destinationPath, mode, strategy) {
18926
- await mkdir12(path51.dirname(destinationPath), { recursive: true });
19117
+ await mkdir12(path58.dirname(destinationPath), { recursive: true });
18927
19118
  if (strategy === "clonefile" && fsConstants2.COPYFILE_FICLONE !== void 0) {
18928
19119
  try {
18929
19120
  await copyFile4(sourcePath, destinationPath, fsConstants2.COPYFILE_FICLONE);
@@ -18949,7 +19140,7 @@ async function copyNode(sourceRoot, destinationRoot, relativePath, strategy) {
18949
19140
  return strategy;
18950
19141
  }
18951
19142
  if (info.isSymbolicLink()) {
18952
- await mkdir12(path51.dirname(destinationPath), { recursive: true });
19143
+ await mkdir12(path58.dirname(destinationPath), { recursive: true });
18953
19144
  await symlink4(await readlink5(sourcePath), destinationPath);
18954
19145
  return strategy;
18955
19146
  }
@@ -19005,9 +19196,9 @@ async function mapWithConcurrency(items, concurrency, worker) {
19005
19196
  async function probeFileCloneStrategy() {
19006
19197
  let tempDir = null;
19007
19198
  try {
19008
- tempDir = await mkdtemp4(path51.join(os4.tmpdir(), "belay-clone-probe-"));
19009
- const source = path51.join(tempDir, "source.txt");
19010
- const destination = path51.join(tempDir, "dest.txt");
19199
+ tempDir = await mkdtemp4(path58.join(os4.tmpdir(), "belay-clone-probe-"));
19200
+ const source = path58.join(tempDir, "source.txt");
19201
+ const destination = path58.join(tempDir, "dest.txt");
19011
19202
  await writeFile9(source, "probe\n");
19012
19203
  if (fsConstants2.COPYFILE_FICLONE_FORCE !== void 0) {
19013
19204
  try {
@@ -19160,11 +19351,11 @@ async function protectedRootState(root) {
19160
19351
  return `directory:${node.hash}:${index.treeHash}`;
19161
19352
  }
19162
19353
  function executionProtectedRoot(resourceRoot, executionRoot, protectedRoot) {
19163
- const relative = path52.relative(path52.resolve(resourceRoot), path52.resolve(protectedRoot));
19164
- if (relative === "" || relative.startsWith("..") || path52.isAbsolute(relative)) {
19354
+ const relative = path59.relative(path59.resolve(resourceRoot), path59.resolve(protectedRoot));
19355
+ if (relative === "" || relative.startsWith("..") || path59.isAbsolute(relative)) {
19165
19356
  return null;
19166
19357
  }
19167
- return path52.join(executionRoot, relative);
19358
+ return path59.join(executionRoot, relative);
19168
19359
  }
19169
19360
  async function captureProtectedRootStates(resourceRoot, executionRoot, protectedRoots) {
19170
19361
  const states = /* @__PURE__ */ new Map();
@@ -19189,15 +19380,15 @@ async function directoryByteSize(root, deadlineMs) {
19189
19380
  }
19190
19381
  let total = 0;
19191
19382
  for (const name of await readdir6(root)) {
19192
- total += await directoryByteSize(path52.join(root, name), deadlineMs);
19383
+ total += await directoryByteSize(path59.join(root, name), deadlineMs);
19193
19384
  }
19194
19385
  return total;
19195
19386
  }
19196
19387
  async function copyGitMetadataDirectory(sourceRoot, destinationRoot) {
19197
19388
  const gitDirRel = (await execGit2(sourceRoot, ["rev-parse", "--git-dir"])).trim();
19198
- const sourceGitDir = path52.isAbsolute(gitDirRel) ? gitDirRel : path52.join(sourceRoot, gitDirRel);
19199
- const relativeGitDir = path52.relative(path52.resolve(sourceRoot), path52.resolve(sourceGitDir));
19200
- const destinationGitDir = relativeGitDir && !relativeGitDir.startsWith("..") ? path52.join(destinationRoot, relativeGitDir) : path52.join(destinationRoot, ".git");
19389
+ const sourceGitDir = path59.isAbsolute(gitDirRel) ? gitDirRel : path59.join(sourceRoot, gitDirRel);
19390
+ const relativeGitDir = path59.relative(path59.resolve(sourceRoot), path59.resolve(sourceGitDir));
19391
+ const destinationGitDir = relativeGitDir && !relativeGitDir.startsWith("..") ? path59.join(destinationRoot, relativeGitDir) : path59.join(destinationRoot, ".git");
19201
19392
  await cp(sourceGitDir, destinationGitDir, { recursive: true, force: true });
19202
19393
  }
19203
19394
  async function prepareDirtyGitSnapshot(context) {
@@ -19206,7 +19397,7 @@ async function prepareDirtyGitSnapshot(context) {
19206
19397
  const quotas = context.fileCheckpoint;
19207
19398
  const deadlineMs = Date.now() + quotas.prepareTimeoutMs;
19208
19399
  await removeDeadOwnerStaging(os5.tmpdir());
19209
- const stagingRoot = await mkdtemp5(path52.join(os5.tmpdir(), "belay-file-checkpoint-"));
19400
+ const stagingRoot = await mkdtemp5(path59.join(os5.tmpdir(), "belay-file-checkpoint-"));
19210
19401
  await writeOwnerMarker(stagingRoot, {
19211
19402
  version: 1,
19212
19403
  pid: process.pid,
@@ -19214,8 +19405,8 @@ async function prepareDirtyGitSnapshot(context) {
19214
19405
  resourceRoot: context.repoRoot,
19215
19406
  backend: "file_checkpoint"
19216
19407
  });
19217
- const baselineRoot = path52.join(stagingRoot, "baseline");
19218
- const executionRoot = path52.join(stagingRoot, "execution");
19408
+ const baselineRoot = path59.join(stagingRoot, "baseline");
19409
+ const executionRoot = path59.join(stagingRoot, "execution");
19219
19410
  try {
19220
19411
  resolveExecutionCwdRelative(context.repoRoot, context.cwd);
19221
19412
  const sourceGitMetadataFingerprint = await computeGitMetadataFingerprint(context.repoRoot);
@@ -19249,7 +19440,7 @@ async function prepareDirtyGitSnapshot(context) {
19249
19440
  throw new Error(FILE_CHECKPOINT_SOURCE_CHANGED);
19250
19441
  }
19251
19442
  await writeFile10(
19252
- path52.join(stagingRoot, "baseline-index.json"),
19443
+ path59.join(stagingRoot, "baseline-index.json"),
19253
19444
  `${JSON.stringify(baselineIndex)}
19254
19445
  `,
19255
19446
  "utf8"
@@ -19299,7 +19490,7 @@ async function prepareNonGitSnapshot(context) {
19299
19490
  const quotas = context.fileCheckpoint;
19300
19491
  const deadlineMs = Date.now() + quotas.prepareTimeoutMs;
19301
19492
  await removeDeadOwnerStaging(os5.tmpdir());
19302
- const stagingRoot = await mkdtemp5(path52.join(os5.tmpdir(), "belay-file-checkpoint-"));
19493
+ const stagingRoot = await mkdtemp5(path59.join(os5.tmpdir(), "belay-file-checkpoint-"));
19303
19494
  await writeOwnerMarker(stagingRoot, {
19304
19495
  version: 1,
19305
19496
  pid: process.pid,
@@ -19307,8 +19498,8 @@ async function prepareNonGitSnapshot(context) {
19307
19498
  resourceRoot: context.repoRoot,
19308
19499
  backend: "file_checkpoint"
19309
19500
  });
19310
- const baselineRoot = path52.join(stagingRoot, "baseline");
19311
- const executionRoot = path52.join(stagingRoot, "execution");
19501
+ const baselineRoot = path59.join(stagingRoot, "baseline");
19502
+ const executionRoot = path59.join(stagingRoot, "execution");
19312
19503
  try {
19313
19504
  resolveExecutionCwdRelative(context.repoRoot, context.cwd);
19314
19505
  const resourceIdentity = await currentRecoveryResourceIdentity(context.repoRoot, "directory");
@@ -19337,7 +19528,7 @@ async function prepareNonGitSnapshot(context) {
19337
19528
  throw new Error(FILE_CHECKPOINT_SOURCE_CHANGED);
19338
19529
  }
19339
19530
  await writeFile10(
19340
- path52.join(stagingRoot, "baseline-index.json"),
19531
+ path59.join(stagingRoot, "baseline-index.json"),
19341
19532
  `${JSON.stringify(baselineIndex)}
19342
19533
  `,
19343
19534
  "utf8"
@@ -19690,10 +19881,10 @@ async function selectTransactionalBackend(context) {
19690
19881
  }
19691
19882
 
19692
19883
  // src/core/transactional/diff-evaluator.ts
19693
- import path53 from "node:path";
19884
+ import path60 from "node:path";
19694
19885
  init_path_utils();
19695
19886
  function categorizeChange(change, ctx) {
19696
- const absolutePath = canonicalPath(path53.join(ctx.repoRoot, change.relativePath));
19887
+ const absolutePath = canonicalPath(path60.join(ctx.repoRoot, change.relativePath));
19697
19888
  if (!pathWithinRoot(ctx.repoRoot, absolutePath)) {
19698
19889
  return "repo_outside";
19699
19890
  }
@@ -20287,7 +20478,7 @@ async function notifyDeny(config, event) {
20287
20478
  init_path_utils();
20288
20479
 
20289
20480
  // src/adapters/layouts/protected-paths.ts
20290
- import path54 from "node:path";
20481
+ import path61 from "node:path";
20291
20482
  function protectedArtifactRoots(layout, repoRoot, controlPlaneDir) {
20292
20483
  const roots = [
20293
20484
  layout.configPath(repoRoot),
@@ -20299,7 +20490,7 @@ function protectedArtifactRoots(layout, repoRoot, controlPlaneDir) {
20299
20490
  if (controlPlaneDir) {
20300
20491
  roots.push(controlPlaneDir);
20301
20492
  }
20302
- return roots.map((entry) => path54.resolve(entry));
20493
+ return roots.map((entry) => path61.resolve(entry));
20303
20494
  }
20304
20495
 
20305
20496
  // src/adapters/shared/gate-runtime.ts
@@ -20352,8 +20543,8 @@ function createDefaultGateRuntimeDeps() {
20352
20543
  return loadJsonFile(configPath, {});
20353
20544
  },
20354
20545
  async appendAudit(ctx, event) {
20355
- const auditPath = path55.join(ctx.repoRoot, ctx.config.audit.logPath);
20356
- await mkdir14(path55.dirname(auditPath), { recursive: true });
20546
+ const auditPath = path62.join(ctx.repoRoot, ctx.config.audit.logPath);
20547
+ await mkdir14(path62.dirname(auditPath), { recursive: true });
20357
20548
  const provenance = auditProvenance(ctx.config);
20358
20549
  const record = {
20359
20550
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -20386,7 +20577,7 @@ function createDefaultGateRuntimeDeps() {
20386
20577
  };
20387
20578
  },
20388
20579
  async writeApprovals(filePath, state) {
20389
- await mkdir14(path55.dirname(filePath), { recursive: true });
20580
+ await mkdir14(path62.dirname(filePath), { recursive: true });
20390
20581
  await writeFile11(filePath, `${JSON.stringify(compactApprovals(state), null, 2)}
20391
20582
  `, "utf8");
20392
20583
  },
@@ -20548,7 +20739,7 @@ function deriveWorkspaceRootScopeHint(params) {
20548
20739
  if (!targetPath) {
20549
20740
  return void 0;
20550
20741
  }
20551
- const candidateRoot = canonicalPath(path55.dirname(targetPath));
20742
+ const candidateRoot = canonicalPath(path62.dirname(targetPath));
20552
20743
  const validation = validateTrustedWorkspaceRootCandidate({
20553
20744
  candidatePath: candidateRoot,
20554
20745
  repoRoot: action.repoRoot,
@@ -21124,6 +21315,7 @@ async function gateDecisionToVerdict(ctx, deps, kind, result, auditExtras = {})
21124
21315
  event: auditEvent,
21125
21316
  sourceEvent,
21126
21317
  kind,
21318
+ repoLabel: path62.basename(ctx.repoRoot),
21127
21319
  ...auditExtras.toolInvocationCorrelationId ? { toolInvocationCorrelationId: auditExtras.toolInvocationCorrelationId } : {},
21128
21320
  fingerprint: result.fingerprint,
21129
21321
  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 path56 from "node:path";
21766
+ import path63 from "node:path";
21575
21767
  function belayConfigPath(current, adapterName) {
21576
21768
  if (adapterName === "cursor") {
21577
- return path56.join(current, ".cursor", "belay.config.json");
21769
+ return path63.join(current, ".cursor", "belay.config.json");
21578
21770
  }
21579
21771
  if (adapterName === "claude") {
21580
- return path56.join(current, ".claude", "belay.config.json");
21772
+ return path63.join(current, ".claude", "belay.config.json");
21581
21773
  }
21582
- return path56.join(current, ".codex", "belay.config.json");
21774
+ return path63.join(current, ".codex", "belay.config.json");
21583
21775
  }
21584
21776
  function markerMatches(current, marker, layout) {
21585
- const markerPath = path56.join(current, marker);
21777
+ const markerPath = path63.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 = path56.resolve(startPath);
21787
+ let current = path63.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 = path56.dirname(current);
21794
+ const parent = path63.dirname(current);
21603
21795
  if (parent === current) {
21604
- return path56.resolve(startPath);
21796
+ return path63.resolve(startPath);
21605
21797
  }
21606
21798
  current = parent;
21607
21799
  }
@@ -21609,7 +21801,7 @@ function findRepoRoot(startPath, layout) {
21609
21801
 
21610
21802
  // src/adapters/cursor/cwd-resolution.ts
21611
21803
  import os6 from "node:os";
21612
- import path57 from "node:path";
21804
+ import path64 from "node:path";
21613
21805
  import { fileURLToPath as fileURLToPath2 } from "node:url";
21614
21806
  function nonEmptyPathString(value) {
21615
21807
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
@@ -21621,36 +21813,36 @@ function resolveCursorActionCwdDetails(payload, fallback = process.cwd()) {
21621
21813
  const workspaceRoot = Array.isArray(payload.workspace_roots) ? payload.workspace_roots.map(nonEmptyPathString).find(Boolean) : void 0;
21622
21814
  if (toolInputWorkingDirectory) {
21623
21815
  return {
21624
- cwd: path57.resolve(toolInputWorkingDirectory),
21816
+ cwd: path64.resolve(toolInputWorkingDirectory),
21625
21817
  source: "tool_input.working_directory",
21626
21818
  fromPayload: true
21627
21819
  };
21628
21820
  }
21629
21821
  if (topLevelCwd) {
21630
21822
  return {
21631
- cwd: path57.resolve(topLevelCwd),
21823
+ cwd: path64.resolve(topLevelCwd),
21632
21824
  source: "cwd",
21633
21825
  fromPayload: true
21634
21826
  };
21635
21827
  }
21636
21828
  if (workspaceRoot) {
21637
21829
  return {
21638
- cwd: path57.resolve(workspaceRoot),
21830
+ cwd: path64.resolve(workspaceRoot),
21639
21831
  source: "workspace_roots",
21640
21832
  fromPayload: true
21641
21833
  };
21642
21834
  }
21643
21835
  return {
21644
- cwd: path57.resolve(fallback),
21836
+ cwd: path64.resolve(fallback),
21645
21837
  source: "fallback",
21646
21838
  fromPayload: false
21647
21839
  };
21648
21840
  }
21649
21841
  function isGlobalCursorHookRuntime() {
21650
21842
  try {
21651
- const runtimePath = path57.resolve(fileURLToPath2(import.meta.url));
21652
- const globalRuntimeDir = path57.resolve(path57.join(os6.homedir(), ".cursor", "belay", "runtime"));
21653
- return runtimePath === globalRuntimeDir || runtimePath.startsWith(`${globalRuntimeDir}${path57.sep}`);
21843
+ const runtimePath = path64.resolve(fileURLToPath2(import.meta.url));
21844
+ const globalRuntimeDir = path64.resolve(path64.join(os6.homedir(), ".cursor", "belay", "runtime"));
21845
+ return runtimePath === globalRuntimeDir || runtimePath.startsWith(`${globalRuntimeDir}${path64.sep}`);
21654
21846
  } catch {
21655
21847
  return false;
21656
21848
  }
@@ -21700,7 +21892,7 @@ function resolveCursorToolActionCwdDetails(payload, fallback, eventName, toolNam
21700
21892
  function collectCandidateRepoRoots(payload, resolution) {
21701
21893
  const roots = [];
21702
21894
  const add = (value) => {
21703
- const resolved = path58.resolve(value);
21895
+ const resolved = path65.resolve(value);
21704
21896
  if (!roots.includes(resolved)) {
21705
21897
  roots.push(resolved);
21706
21898
  }
@@ -21762,17 +21954,15 @@ function isSubagentEvent(payload, eventName) {
21762
21954
  function isFileMutationTool(toolName) {
21763
21955
  return toolName === "Write" || toolName === "StrReplace" || toolName === "Delete";
21764
21956
  }
21765
- async function runBeforeSubmitPromptHook() {
21957
+ async function handleBeforeSubmitPromptHook(payload) {
21766
21958
  try {
21767
- const payload = await readStdinJson();
21768
21959
  const prompt = String(payload.prompt ?? "");
21769
21960
  const resolution = resolveCursorActionCwdDetails(payload);
21770
21961
  if (isUnsafeGlobalHookFallback(resolution)) {
21771
- jsonResponse({
21962
+ return {
21772
21963
  continue: false,
21773
21964
  user_message: GLOBAL_HOOK_WORKSPACE_MISSING_MESSAGE
21774
- });
21775
- return;
21965
+ };
21776
21966
  }
21777
21967
  const ctx = await loadRuntimeContext(resolution.cwd);
21778
21968
  const deps = createDefaultGateRuntimeDeps();
@@ -21783,29 +21973,30 @@ async function runBeforeSubmitPromptHook() {
21783
21973
  payload,
21784
21974
  resolution
21785
21975
  );
21786
- jsonResponse({
21976
+ return {
21787
21977
  continue: result.continue,
21788
21978
  ...result.user_message ? { user_message: result.user_message } : {},
21789
21979
  ...result.replay ? { replay: result.replay } : {}
21790
- });
21980
+ };
21791
21981
  } catch {
21792
- jsonResponse({
21982
+ return {
21793
21983
  continue: false,
21794
21984
  user_message: "belay failed while processing approval state. Run belay doctor, then retry."
21795
- });
21985
+ };
21796
21986
  }
21797
21987
  }
21798
- async function runShellGateHook() {
21988
+ async function runBeforeSubmitPromptHook() {
21989
+ jsonResponse(await handleBeforeSubmitPromptHook(await readStdinJson()));
21990
+ }
21991
+ async function handleShellGateHook(payload) {
21799
21992
  try {
21800
- const payload = await readStdinJson();
21801
21993
  const command = String(payload.command ?? "").trim();
21802
21994
  const resolution = resolveCursorActionCwdDetails(payload);
21803
21995
  if (isUnsafeGlobalHookFallback(resolution)) {
21804
- jsonResponse({
21996
+ return {
21805
21997
  permission: "deny",
21806
21998
  user_message: GLOBAL_HOOK_WORKSPACE_MISSING_MESSAGE
21807
- });
21808
- return;
21999
+ };
21809
22000
  }
21810
22001
  const cwd = resolution.cwd;
21811
22002
  const ctx = await loadRuntimeContext(cwd);
@@ -21817,17 +22008,19 @@ async function runShellGateHook() {
21817
22008
  payload,
21818
22009
  sourceEvent: "beforeShellExecution"
21819
22010
  });
21820
- jsonResponse(gateVerdictToCursorResponse(verdict2));
22011
+ return gateVerdictToCursorResponse(verdict2);
21821
22012
  } catch {
21822
- jsonResponse({
22013
+ return {
21823
22014
  permission: "deny",
21824
22015
  user_message: "belay failed while classifying this shell command. Run belay doctor, then retry."
21825
- });
22016
+ };
21826
22017
  }
21827
22018
  }
21828
- async function runToolGateHook(eventName) {
22019
+ async function runShellGateHook() {
22020
+ jsonResponse(await handleShellGateHook(await readStdinJson()));
22021
+ }
22022
+ async function handleToolGateHook(eventName, payload) {
21829
22023
  try {
21830
- const payload = await readStdinJson();
21831
22024
  const toolName = String(payload.tool_name ?? "");
21832
22025
  const resolution = resolveCursorToolActionCwdDetails(
21833
22026
  payload,
@@ -21836,11 +22029,10 @@ async function runToolGateHook(eventName) {
21836
22029
  toolName
21837
22030
  );
21838
22031
  if (isUnsafeGlobalHookFallback(resolution)) {
21839
- jsonResponse({
22032
+ return {
21840
22033
  permission: "deny",
21841
22034
  user_message: GLOBAL_HOOK_WORKSPACE_MISSING_MESSAGE
21842
- });
21843
- return;
22035
+ };
21844
22036
  }
21845
22037
  const cwd = resolution.cwd;
21846
22038
  const ctx = await loadRuntimeContext(cwd);
@@ -21852,8 +22044,7 @@ async function runToolGateHook(eventName) {
21852
22044
  payload,
21853
22045
  sourceEvent: eventName
21854
22046
  });
21855
- jsonResponse(gateVerdictToCursorResponse(verdict2));
21856
- return;
22047
+ return gateVerdictToCursorResponse(verdict2);
21857
22048
  }
21858
22049
  if (toolName === "Shell") {
21859
22050
  const verdict2 = await evaluateGatedAction(ctx, deps, {
@@ -21863,8 +22054,7 @@ async function runToolGateHook(eventName) {
21863
22054
  toolName,
21864
22055
  sourceEvent: eventName
21865
22056
  });
21866
- jsonResponse(gateVerdictToCursorResponse(verdict2));
21867
- return;
22057
+ return gateVerdictToCursorResponse(verdict2);
21868
22058
  }
21869
22059
  if (isFileMutationTool(toolName)) {
21870
22060
  const verdict2 = await evaluateGatedAction(ctx, deps, {
@@ -21874,8 +22064,7 @@ async function runToolGateHook(eventName) {
21874
22064
  toolName,
21875
22065
  sourceEvent: eventName
21876
22066
  });
21877
- jsonResponse(gateVerdictToCursorResponse(verdict2));
21878
- return;
22067
+ return gateVerdictToCursorResponse(verdict2);
21879
22068
  }
21880
22069
  if (payload.tool_name === "Task") {
21881
22070
  const verdict2 = await evaluateGatedAction(ctx, deps, {
@@ -21884,20 +22073,21 @@ async function runToolGateHook(eventName) {
21884
22073
  payload,
21885
22074
  sourceEvent: eventName
21886
22075
  });
21887
- jsonResponse(gateVerdictToCursorResponse(verdict2));
21888
- return;
22076
+ return gateVerdictToCursorResponse(verdict2);
21889
22077
  }
21890
- jsonResponse({ permission: "allow" });
22078
+ return { permission: "allow" };
21891
22079
  } catch {
21892
- jsonResponse({
22080
+ return {
21893
22081
  permission: "deny",
21894
22082
  user_message: "belay failed while classifying this tool action. Run belay doctor, then retry."
21895
- });
22083
+ };
21896
22084
  }
21897
22085
  }
21898
- async function runAuditHook(eventName) {
22086
+ async function runToolGateHook(eventName) {
22087
+ jsonResponse(await handleToolGateHook(eventName, await readStdinJson()));
22088
+ }
22089
+ async function handleAuditHook(eventName, payload) {
21899
22090
  try {
21900
- const payload = await readStdinJson();
21901
22091
  const toolName = String(payload.tool_name ?? "");
21902
22092
  const resolution = resolveCursorToolActionCwdDetails(
21903
22093
  payload,
@@ -21906,22 +22096,28 @@ async function runAuditHook(eventName) {
21906
22096
  toolName
21907
22097
  );
21908
22098
  if (isUnsafeGlobalHookFallback(resolution)) {
21909
- jsonResponse({});
21910
- return;
22099
+ return {};
21911
22100
  }
21912
22101
  const ctx = await loadRuntimeContext(resolution.cwd);
21913
22102
  const deps = createDefaultGateRuntimeDeps();
21914
22103
  await appendObservedAudit(ctx, deps, eventName, payload);
21915
- jsonResponse({});
22104
+ return {};
21916
22105
  } catch (error) {
21917
22106
  console.error(
21918
22107
  "belay audit hook failed:",
21919
22108
  error instanceof Error ? error.message : String(error)
21920
22109
  );
21921
- jsonResponse({});
22110
+ return {};
21922
22111
  }
21923
22112
  }
22113
+ async function runAuditHook(eventName) {
22114
+ jsonResponse(await handleAuditHook(eventName, await readStdinJson()));
22115
+ }
21924
22116
  export {
22117
+ handleAuditHook,
22118
+ handleBeforeSubmitPromptHook,
22119
+ handleShellGateHook,
22120
+ handleToolGateHook,
21925
22121
  resolveCursorActionCwd,
21926
22122
  runAuditHook,
21927
22123
  runBeforeSubmitPromptHook,