@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
@@ -4232,7 +4232,7 @@ init_approval_replay();
4232
4232
  import { randomUUID as randomUUID6 } from "node:crypto";
4233
4233
  import { existsSync as existsSync16 } from "node:fs";
4234
4234
  import { mkdir as mkdir14, readFile as readFile15, writeFile as writeFile11 } from "node:fs/promises";
4235
- import path54 from "node:path";
4235
+ import path61 from "node:path";
4236
4236
 
4237
4237
  // src/core/approval-service.ts
4238
4238
  init_config_io();
@@ -7239,31 +7239,44 @@ import { randomUUID as randomUUID3 } from "node:crypto";
7239
7239
  import path22 from "node:path";
7240
7240
 
7241
7241
  // src/core/glob.ts
7242
+ init_approval();
7243
+ function globPatternToRegexSource(pattern) {
7244
+ let result = "";
7245
+ let index = 0;
7246
+ while (index < pattern.length) {
7247
+ const char = pattern[index];
7248
+ if (char === "*" && pattern[index + 1] === "*") {
7249
+ index += 2;
7250
+ if (pattern[index] === "/") {
7251
+ result += "(?:.*/)?";
7252
+ index += 1;
7253
+ } else {
7254
+ result += ".*";
7255
+ }
7256
+ continue;
7257
+ }
7258
+ if (char === "*") {
7259
+ result += "[^/]*";
7260
+ index += 1;
7261
+ continue;
7262
+ }
7263
+ result += escapeRegex(char);
7264
+ index += 1;
7265
+ }
7266
+ return result;
7267
+ }
7268
+ function globPatternMatches(pattern, normalized, baseName) {
7269
+ const regex = new RegExp(`^${globPatternToRegexSource(pattern)}$`);
7270
+ return regex.test(normalized) || regex.test(baseName);
7271
+ }
7242
7272
  function matchesSensitivePath(filePath, patterns) {
7243
7273
  const normalized = filePath.replaceAll("\\", "/");
7244
7274
  const segments = normalized.split("/");
7245
7275
  const baseName = segments.at(-1) ?? normalized;
7246
7276
  for (const pattern of patterns) {
7247
7277
  const normalizedPattern = pattern.replaceAll("\\", "/");
7248
- if (normalizedPattern.includes("**")) {
7249
- const parts = normalizedPattern.split("**").map((part) => part.replace(/^\/+|\/+$/g, ""));
7250
- const prefix = parts[0]?.replace(/\/+$/, "") ?? "";
7251
- const suffix = parts[1]?.replace(/^\/+/, "") ?? "";
7252
- if (prefix && !normalized.startsWith(prefix)) {
7253
- continue;
7254
- }
7255
- if (suffix && !normalized.includes(suffix)) {
7256
- continue;
7257
- }
7258
- if (prefix || suffix) {
7259
- return true;
7260
- }
7261
- }
7262
7278
  if (normalizedPattern.includes("*")) {
7263
- const regex = new RegExp(
7264
- `^${normalizedPattern.replaceAll(".", "\\.").replaceAll("*", ".*")}$`
7265
- );
7266
- if (regex.test(normalized) || regex.test(baseName)) {
7279
+ if (globPatternMatches(normalizedPattern, normalized, baseName)) {
7267
7280
  return true;
7268
7281
  }
7269
7282
  continue;
@@ -11020,6 +11033,16 @@ function subagentFingerprintSource(payload, scrubOptions) {
11020
11033
  }
11021
11034
  return scrubValue(payload, scrubOptions);
11022
11035
  }
11036
+ function fingerprintToolInputSource(payload) {
11037
+ const toolInput = payload.tool_input;
11038
+ if (!toolInput || typeof toolInput !== "object") {
11039
+ return {};
11040
+ }
11041
+ return redactToolInvocationId(
11042
+ toolInput,
11043
+ typeof payload.tool_use_id === "string" ? payload.tool_use_id : void 0
11044
+ );
11045
+ }
11023
11046
  function fingerprintReplayPayload(kind, payload, scrubOptions) {
11024
11047
  if (!payload) {
11025
11048
  return void 0;
@@ -11129,7 +11152,7 @@ function classifySubagent(payload, repoRoot, options = {}, config) {
11129
11152
  }
11130
11153
 
11131
11154
  // src/core/classify-tool.ts
11132
- import path42 from "node:path";
11155
+ import path49 from "node:path";
11133
11156
  init_fingerprint2();
11134
11157
  init_path_utils();
11135
11158
  init_scrub();
@@ -11248,11 +11271,8 @@ function worstEffectDecision(decisions) {
11248
11271
  }
11249
11272
 
11250
11273
  // src/core/effect-ir/shell-lower.ts
11251
- init_git_resource_identity();
11252
- init_path_utils();
11253
11274
  init_shell_tokenizer();
11254
- import { lstatSync as lstatSync2, realpathSync as realpathSync4 } from "node:fs";
11255
- import path41 from "node:path";
11275
+ import path48 from "node:path";
11256
11276
 
11257
11277
  // src/core/verdict/docker-compose-run.ts
11258
11278
  import path36 from "node:path";
@@ -11592,6 +11612,17 @@ function decodeDockerComposeRun(tokens) {
11592
11612
 
11593
11613
  // src/core/verdict/egress-classify.ts
11594
11614
  import path37 from "node:path";
11615
+ var EGRESS_TOOL_HEADS = /* @__PURE__ */ new Set([
11616
+ "aws",
11617
+ "curl",
11618
+ "gh",
11619
+ "gcloud",
11620
+ "heroku",
11621
+ "kubectl",
11622
+ "netlify",
11623
+ "vercel",
11624
+ "wget"
11625
+ ]);
11595
11626
  var CURL_EFFECT_NEUTRAL_FLAGS = /* @__PURE__ */ new Set([
11596
11627
  "-f",
11597
11628
  "-L",
@@ -11625,6 +11656,9 @@ var GH_READ_COMMANDS = /* @__PURE__ */ new Set([
11625
11656
  "workflow list",
11626
11657
  "workflow view"
11627
11658
  ]);
11659
+ function isEgressToolHead(head) {
11660
+ return EGRESS_TOOL_HEADS.has(head);
11661
+ }
11628
11662
  function decodeEgressEffects(params) {
11629
11663
  const head = path37.basename(params.tokens[0] ?? "");
11630
11664
  if (head !== "curl" && head !== "wget" && head !== "gh") {
@@ -13887,7 +13921,7 @@ function extractBalancedParenContent(command, start) {
13887
13921
  index += 1;
13888
13922
  continue;
13889
13923
  }
13890
- if (char === "\\" && (inSingle || inDouble)) {
13924
+ if (char === "\\" && inDouble) {
13891
13925
  escaping = true;
13892
13926
  index += 1;
13893
13927
  continue;
@@ -13965,7 +13999,9 @@ function hasUnclosedQuote(command) {
13965
13999
  continue;
13966
14000
  }
13967
14001
  if (char === "\\") {
13968
- escaping = true;
14002
+ if (quote !== "'") {
14003
+ escaping = true;
14004
+ }
13969
14005
  continue;
13970
14006
  }
13971
14007
  if (quote) {
@@ -14482,6 +14518,50 @@ function redactCommand(command) {
14482
14518
  return command.replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [REDACTED]").replace(/sk-[A-Za-z0-9]{8,}/g, "sk-[REDACTED]").trim();
14483
14519
  }
14484
14520
 
14521
+ // src/core/effect-ir/argv-delegate.ts
14522
+ function innerRecipeFromArgvDelegate(peel) {
14523
+ if (peel.opaque || peel.innerTokens.length === 0) {
14524
+ return null;
14525
+ }
14526
+ return peel.innerTokens.join(" ");
14527
+ }
14528
+ function peelArgvDelegateArgv(tokens) {
14529
+ const launcher = tokens[0] ?? "";
14530
+ if (!launcher || tokens.length < 2) {
14531
+ return null;
14532
+ }
14533
+ const signals = ["process.argv_delegate"];
14534
+ let index = 1;
14535
+ while (index < tokens.length) {
14536
+ const token = tokens[index] ?? "";
14537
+ if (token === "--") {
14538
+ index += 1;
14539
+ break;
14540
+ }
14541
+ if (token.startsWith("-")) {
14542
+ return {
14543
+ launcher,
14544
+ innerTokens: [],
14545
+ opaque: true,
14546
+ reason: "argv_delegate_wrapper_options",
14547
+ signals: [...signals, "argv_delegate_wrapper_options"]
14548
+ };
14549
+ }
14550
+ break;
14551
+ }
14552
+ const innerTokens = tokens.slice(index);
14553
+ if (innerTokens.length === 0) {
14554
+ return null;
14555
+ }
14556
+ return {
14557
+ launcher,
14558
+ innerTokens,
14559
+ opaque: false,
14560
+ reason: "argv_delegate_peel",
14561
+ signals
14562
+ };
14563
+ }
14564
+
14485
14565
  // src/core/effect-ir/shell-build.ts
14486
14566
  function buildShellEffectPlan(params) {
14487
14567
  const nodes = params.segments.map(buildSegmentNode);
@@ -14539,933 +14619,416 @@ function mergeNodes(nodes) {
14539
14619
  return { kind: "merge", children: nodes };
14540
14620
  }
14541
14621
 
14542
- // src/core/effect-ir/shell-lower.ts
14543
- var MAX_LOWER_DEPTH = 8;
14544
- var ENV_PREFIX_PATTERN2 = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
14545
- var LOOPBACK_HOSTS2 = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1", "0:0:0:0:0:0:0:1"]);
14622
+ // src/core/effect-ir/shell-lower/argv-delegate-gate.ts
14623
+ import path41 from "node:path";
14624
+ var ARGV_DELEGATE_INNER_BLOCKLIST = /* @__PURE__ */ new Set([
14625
+ "sudo",
14626
+ "env",
14627
+ "command",
14628
+ "builtin",
14629
+ "exec",
14630
+ "time",
14631
+ "nice",
14632
+ "nohup",
14633
+ "stdbuf",
14634
+ "setsid",
14635
+ "("
14636
+ ]);
14637
+ function shouldApplyArgvDelegate(head, innerTokens, depth) {
14638
+ if (depth > 0 || isEgressToolHead(head) || head === "bundle") {
14639
+ return false;
14640
+ }
14641
+ if (ARGV_DELEGATE_INNER_BLOCKLIST.has(head)) {
14642
+ return false;
14643
+ }
14644
+ const innerHead = path41.basename(innerTokens[0] ?? "");
14645
+ if (ARGV_DELEGATE_INNER_BLOCKLIST.has(innerHead)) {
14646
+ return false;
14647
+ }
14648
+ return innerTokens.length >= 2;
14649
+ }
14650
+ function isGrammarUnknownOnly(requirements, head) {
14651
+ if (requirements.length !== 2) {
14652
+ return false;
14653
+ }
14654
+ const [spawn9, indeterminate] = requirements;
14655
+ 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");
14656
+ }
14657
+
14658
+ // src/core/effect-ir/shell-lower/augment.ts
14659
+ init_shell_tokenizer();
14660
+
14661
+ // src/core/effect-ir/shell-lower/requirement.ts
14546
14662
  var SECRET_PATH_PATTERN = /(?:^|[/\\])(?:\.env(?:\..*)?|credentials?|secrets?|id_rsa)(?:$|[/\\])/i;
14547
14663
  var CONTROL_PLANE_PATH_PATTERN = /^(?:\/etc(?:\/|$)|\/var\/run(?:\/|$)|.*(?:^|[/\\])\.(?:git|ssh|cursor|claude)(?:[/\\]|$))/;
14548
- function lowerShellEffectPlan(params) {
14549
- const context = { ...params, depth: 0 };
14550
- const segments = lowerTopLevelSegments(params.command, context);
14551
- return buildShellEffectPlan({
14552
- inputFingerprint: params.inputFingerprint,
14553
- segments,
14554
- signals: pipeToShell(params.command) ? ["pipe_to_shell"] : []
14555
- });
14664
+ function requirement2(tag, action, resource, segment, signals) {
14665
+ return {
14666
+ tag,
14667
+ action,
14668
+ resource,
14669
+ evidence: {
14670
+ level: tag === "indeterminate" ? "indeterminate" : "certain",
14671
+ signals: [...new Set(signals)].sort(),
14672
+ basis: ["shell_semantic_lowering"]
14673
+ },
14674
+ provenance: { segment }
14675
+ };
14556
14676
  }
14557
- function lowerTopLevelSegments(command, context) {
14558
- const commands = splitStructuralShellSegments(command);
14559
- if (commands.length === 0 && command.trim()) {
14560
- commands.push(command.trim());
14561
- }
14562
- const pipedToShell = pipeToShell(command);
14563
- const lowered = [];
14564
- let cwd = context.cwd;
14565
- let cwdKnown = true;
14566
- let inferredEnv = { ...context.env };
14567
- for (const segment of commands) {
14568
- let result = lowerSegment(segment, {
14569
- ...context,
14570
- cwd,
14571
- env: inferredEnv,
14572
- command: segment,
14573
- depth: context.depth,
14574
- inputFingerprint: context.inputFingerprint,
14575
- ...pipedToShell && isShellHead(parseSegment(segment).head) ? { pipeToShellSegment: true } : {}
14576
- });
14577
- if (!cwdKnown) {
14578
- const requiresCwd = result.requirements.some(requiresKnownCwd);
14579
- const requirements = [...result.requirements];
14580
- if (requiresCwd) {
14581
- requirements.push(
14582
- requirement2(
14583
- "indeterminate",
14584
- "indeterminate",
14585
- { kind: "unknown" },
14586
- result.commandRedacted,
14587
- ["shell.cwd_unknown"]
14588
- )
14589
- );
14590
- }
14591
- result = {
14592
- ...result,
14593
- requirements,
14594
- ...requiresCwd ? {
14595
- completeness: "partial",
14596
- opacity: joinEffectOpacity(result.opacity, "opaque")
14597
- } : {},
14598
- signals: [.../* @__PURE__ */ new Set([...result.signals, "shell.cwd_unknown"])]
14599
- };
14600
- }
14601
- lowered.push(result);
14602
- if (!inferredEnv.DATABASE_URL && startsLocalPostgresService(segment)) {
14603
- inferredEnv = {
14604
- ...inferredEnv,
14605
- DATABASE_URL: "postgresql://127.0.0.1:5432/local"
14606
- };
14607
- }
14608
- const nextCwd = resolveCdTransition(segment, cwd);
14609
- if (nextCwd) {
14610
- cwd = nextCwd.cwd;
14611
- cwdKnown = nextCwd.known;
14612
- }
14613
- }
14614
- if (commands.length > 1 && /[\r\n]/.test(command) && lowered.some(
14615
- (segment) => segment.requirements.some((entry) => entry.action === "network.connect")
14616
- )) {
14617
- lowered.push(
14618
- shellSegment(
14619
- "[multiline network boundary]",
14620
- "shell-boundary",
14621
- [
14622
- requirement2(
14623
- "indeterminate",
14624
- "indeterminate",
14625
- { kind: "unknown" },
14626
- "[multiline network boundary]",
14627
- ["shell.multiline_network_boundary"]
14628
- )
14629
- ],
14630
- "opaque",
14631
- /* @__PURE__ */ new Set(["shell.multiline_network_boundary"])
14677
+ function processRequirement(command, operation, segment, signals) {
14678
+ return requirement2(
14679
+ "process.exec",
14680
+ "process.exec",
14681
+ { kind: "executable", command, operation },
14682
+ segment,
14683
+ signals
14684
+ );
14685
+ }
14686
+ function unsupportedProcess(command, segment, signal) {
14687
+ return [
14688
+ processRequirement(command, "spawn", segment, [signal]),
14689
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [signal])
14690
+ ];
14691
+ }
14692
+ function addWriteEffects(requirements, resolved, segment, signals) {
14693
+ requirements.push(
14694
+ requirement2("fs.write", "fs.write", { kind: "path", path: resolved }, segment, signals)
14695
+ );
14696
+ if (CONTROL_PLANE_PATH_PATTERN.test(resolved)) {
14697
+ requirements.push(
14698
+ requirement2(
14699
+ "control_plane.write",
14700
+ "control_plane.write",
14701
+ { kind: "path", path: resolved },
14702
+ segment,
14703
+ [...signals, "protected_path"]
14632
14704
  )
14633
14705
  );
14634
14706
  }
14635
- return lowered;
14636
- }
14637
- function startsLocalPostgresService(command) {
14638
- const tokens = tokenizeShell(command);
14639
- return path41.basename(tokens[0] ?? "") === "docker" && tokens[1] === "compose" && ["up", "start", "restart"].includes(tokens[2] ?? "") && tokens.includes("postgres");
14640
14707
  }
14641
- function resolveCdTransition(command, currentCwd) {
14642
- const tokens = tokenizeShell(command);
14643
- if (path41.basename(tokens[0] ?? "") !== "cd") {
14644
- return null;
14645
- }
14646
- const target = tokens[1] ?? "~";
14647
- if (!target || target === "-" || target.includes("$") || target.includes("`")) {
14648
- return { cwd: currentCwd, known: false };
14708
+ function addSecretRead(requirements, resolved, segment) {
14709
+ if (SECRET_PATH_PATTERN.test(resolved)) {
14710
+ requirements.push(
14711
+ requirement2("secret.read", "secret.read", { kind: "path", path: resolved }, segment, [
14712
+ "secret_path_read"
14713
+ ])
14714
+ );
14649
14715
  }
14650
- return { cwd: resolvePathOperand(target, currentCwd), known: true };
14651
14716
  }
14652
- function requiresKnownCwd(requirementValue) {
14653
- if (requirementValue.action === "fs.write" || requirementValue.action === "git.ref.write" || requirementValue.action === "control_plane.write") {
14654
- return true;
14717
+ function ensureRequirement(requirements, candidate) {
14718
+ if (!requirements.some(
14719
+ (entry) => entry.action === candidate.action && JSON.stringify(entry.resource) === JSON.stringify(candidate.resource)
14720
+ )) {
14721
+ requirements.push(candidate);
14655
14722
  }
14656
- return requirementValue.action === "process.exec" && requirementValue.resource.kind === "executable" && requirementValue.resource.operation === "spawn";
14657
14723
  }
14658
- function joinNestedOpacity(outer, nested) {
14659
- const nestedOpacity = nested.completeness === "partial" && nested.opacity === "transparent" ? "recursive" : nested.opacity;
14660
- return joinEffectOpacity(outer, nestedOpacity);
14661
- }
14662
- function lowerSegment(command, context) {
14663
- const commandRedacted = redactCommand(command);
14664
- const lexed = lexShell(command);
14665
- const rawTokens = lexed.tokens.map((token) => token.value);
14666
- const environment = extractEnvironment(rawTokens, context.env);
14667
- const env = environment.env;
14668
- const parsed = parseSegment(command);
14669
- const parsedTokens = environment.commandTokens ?? parsed.tokens;
14670
- const tokens = stripRedirects(
14671
- parsedTokens.length === 0 && rawTokens.length > 0 && rawTokens.every((token) => ENV_PREFIX_PATTERN2.test(token)) ? rawTokens : parsedTokens
14672
- ).map((token) => expandKnownVariables(token, env));
14673
- const decoderTokens = alignStructuredTokens(
14674
- stripStructuredRedirects(lexed.tokens),
14675
- stripRedirects(parsedTokens)
14676
- );
14677
- const head = path41.basename(tokens[0] ?? parsed.head);
14678
- let opacity = segmentOpacity(command);
14679
- const signals = /* @__PURE__ */ new Set();
14680
- const requirements = [];
14681
- if (!lexed.complete) {
14682
- requirements.push(
14683
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
14684
- "shell.grammar_incomplete"
14685
- ])
14686
- );
14687
- signals.add("shell.grammar_incomplete");
14688
- opacity = joinEffectOpacity(opacity, "unparseable");
14689
- }
14690
- addRedirectEffects(requirements, rawTokens, env, context, commandRedacted);
14691
- addSubstitutionEffects(requirements, command, context, commandRedacted, signals);
14692
- if (environment.malformed) {
14693
- requirements.push(
14694
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
14695
- "shell.env_wrapper_incomplete"
14696
- ])
14697
- );
14698
- opacity = joinEffectOpacity(opacity, "opaque");
14699
- }
14700
- if (parsed.encounteredXargs) {
14701
- requirements.push(
14702
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
14703
- "shell.xargs_stdin_dynamic"
14704
- ])
14705
- );
14706
- signals.add("shell.xargs_stdin_dynamic");
14707
- opacity = joinEffectOpacity(opacity, "opaque");
14708
- }
14709
- if (context.depth > MAX_LOWER_DEPTH) {
14710
- requirements.push(
14711
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
14712
- "shell.lower_depth_exceeded"
14713
- ])
14714
- );
14715
- return shellSegment(
14716
- commandRedacted,
14717
- head,
14718
- requirements,
14719
- joinEffectOpacity(opacity, "opaque"),
14720
- signals
14721
- );
14722
- }
14723
- const packageExec = peelPackageExecArgv(tokens);
14724
- if (packageExec) {
14725
- requirements.push(
14726
- ...decodePackageExec(packageExec, context.cwd, context.repoRoot, commandRedacted)
14727
- );
14728
- for (const signal of packageExec.signals) {
14729
- signals.add(signal);
14730
- }
14731
- if (packageExec.opaque) {
14732
- opacity = joinEffectOpacity(opacity, "opaque");
14733
- }
14734
- const innerRecipe = packageExecInnerIsMetadata(packageExec) ? null : innerRecipeFromPeel(packageExec);
14735
- if (innerRecipe) {
14736
- const nested = lowerTopLevelSegments(innerRecipe, {
14737
- ...context,
14738
- command: innerRecipe,
14739
- env,
14740
- depth: context.depth + 1
14741
- });
14742
- for (const nestedSegment of nested) {
14743
- requirements.push(
14744
- ...nestedSegment.requirements.map(
14745
- (entry) => withInnerProvenance(entry, innerRecipe, packageExec.launcher, commandRedacted)
14746
- )
14747
- );
14748
- for (const signal of nestedSegment.signals) {
14749
- signals.add(signal);
14750
- }
14751
- opacity = joinNestedOpacity(opacity, nestedSegment);
14752
- }
14753
- }
14754
- return shellSegment(commandRedacted, head, requirements, opacity, signals);
14755
- }
14756
- const recursive = decodeRecursiveInvocationTokens(decoderTokens);
14757
- if (recursive.kind === "static" && opacity !== "opaque" && opacity !== "unparseable") {
14758
- requirements.push(
14759
- processRequirement(recursive.interpreter, "spawn", commandRedacted, [
14760
- "shell.recursive_wrapper",
14761
- "dynamic_shell_evaluation"
14762
- ])
14763
- );
14764
- if (recursive.script !== "") {
14765
- const nested = lowerTopLevelSegments(recursive.script, {
14766
- ...context,
14767
- command: recursive.script,
14768
- env,
14769
- depth: context.depth + 1
14770
- });
14771
- for (const nestedSegment of nested) {
14772
- requirements.push(
14773
- ...nestedSegment.requirements.map(
14774
- (entry) => withInnerProvenance(entry, recursive.script, head, commandRedacted)
14775
- )
14776
- );
14777
- for (const signal of nestedSegment.signals) signals.add(signal);
14778
- }
14779
- }
14780
- signals.add("shell.recursive_wrapper");
14781
- signals.add("dynamic_shell_evaluation");
14782
- return shellSegment(commandRedacted, head, requirements, "recursive", signals);
14783
- }
14784
- if (recursive.kind === "dynamic" || recursive.kind === "indeterminate") {
14785
- const recursiveSignals = [
14786
- recursive.signal,
14787
- ...recursive.kind === "dynamic" ? ["dynamic_shell_evaluation"] : []
14788
- ];
14789
- requirements.push(
14790
- processRequirement(recursive.interpreter, "spawn", commandRedacted, recursiveSignals),
14791
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
14792
- ...recursiveSignals
14793
- ])
14794
- );
14795
- for (const signal of recursiveSignals) signals.add(signal);
14796
- return shellSegment(
14797
- commandRedacted,
14798
- head,
14799
- requirements,
14800
- joinEffectOpacity(opacity, "opaque"),
14801
- signals
14802
- );
14803
- }
14804
- const compose = decodeDockerComposeRun(decoderTokens);
14805
- if (compose.kind === "recursive" && opacity !== "opaque" && opacity !== "unparseable") {
14806
- requirements.push(
14807
- processRequirement(head, "spawn", commandRedacted, ["process.docker_compose_run"])
14808
- );
14809
- if (compose.script !== "") {
14810
- const nested = lowerTopLevelSegments(compose.script, {
14811
- ...context,
14812
- command: compose.script,
14813
- env,
14814
- depth: context.depth + 1
14815
- });
14816
- for (const nestedSegment of nested) {
14817
- requirements.push(
14818
- ...nestedSegment.requirements.map(
14819
- (entry) => withInnerProvenance(entry, compose.script, head, commandRedacted)
14820
- )
14821
- );
14822
- for (const signal of nestedSegment.signals) signals.add(signal);
14823
- opacity = joinNestedOpacity(opacity, nestedSegment);
14824
- }
14825
- }
14826
- signals.add("process.docker_compose_run");
14827
- return shellSegment(commandRedacted, head, requirements, "recursive", signals);
14828
- }
14829
- if (compose.kind === "dynamic" || compose.kind === "indeterminate") {
14830
- requirements.push(
14831
- processRequirement(head, "spawn", commandRedacted, ["process.docker_compose_run"]),
14832
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
14833
- compose.signal
14834
- ])
14835
- );
14836
- signals.add("process.docker_compose_run");
14837
- signals.add(compose.signal);
14838
- return shellSegment(
14839
- commandRedacted,
14840
- head,
14841
- requirements,
14842
- joinEffectOpacity(opacity, "opaque"),
14843
- signals
14844
- );
14845
- }
14846
- const launcher = resolveLauncherRecipe({
14847
- tokens,
14848
- cwd: context.cwd,
14849
- repoRoot: context.repoRoot,
14850
- depth: context.depth
14851
- });
14852
- if (launcher) {
14853
- signals.add(`launcher.${launcher.reason}`);
14854
- requirements.push(
14855
- requirement2(
14856
- "process.exec",
14857
- "process.exec",
14858
- { kind: "executable", command: head, operation: "spawn" },
14859
- commandRedacted,
14860
- ["launcher.invoke"]
14861
- )
14862
- );
14863
- for (const recipe of launcher.recipes) {
14864
- const nested = lowerTopLevelSegments(recipe, {
14865
- ...context,
14866
- command: recipe,
14867
- env,
14868
- depth: context.depth + 1
14869
- });
14870
- for (const nestedSegment of nested) {
14871
- requirements.push(
14872
- ...nestedSegment.requirements.map(
14873
- (entry) => withInnerProvenance(entry, recipe, head, commandRedacted)
14874
- )
14875
- );
14876
- for (const signal of nestedSegment.signals) {
14877
- signals.add(signal);
14878
- }
14879
- opacity = joinNestedOpacity(opacity, nestedSegment);
14880
- }
14881
- }
14882
- if (launcher.opaque || launcher.recipes.length === 0) {
14883
- requirements.push(
14884
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
14885
- `launcher.${launcher.reason}`
14886
- ])
14887
- );
14888
- opacity = joinEffectOpacity(opacity, "opaque");
14889
- }
14890
- return shellSegment(commandRedacted, head, requirements, opacity, signals);
14891
- }
14892
- const egress = decodeEgressEffects({
14893
- tokens,
14894
- cwd: context.cwd,
14895
- segment: commandRedacted
14896
- });
14897
- if (egress) {
14898
- requirements.push(...egress);
14899
- for (const signal of egress.flatMap((entry) => entry.evidence.signals)) {
14900
- signals.add(signal);
14901
- }
14902
- } else {
14903
- const git = decodeGitEffects({
14904
- tokens,
14905
- cwd: context.cwd,
14906
- repoRoot: context.repoRoot,
14907
- segment: commandRedacted
14908
- });
14909
- if (git) {
14910
- requirements.push(...git);
14911
- for (const signal of git.flatMap((entry) => entry.evidence.signals)) {
14912
- signals.add(signal);
14913
- }
14914
- } else {
14915
- requirements.push(
14916
- ...decodeProcessOrFilesystem({
14917
- tokens,
14918
- head,
14919
- env,
14920
- cwd: context.cwd,
14921
- repoRoot: context.repoRoot,
14922
- segment: commandRedacted
14923
- })
14924
- );
14925
- }
14926
- }
14927
- const environmentSignals = effectChangingEnvironmentSignals(head, env, environment.changedNames);
14928
- if (environmentSignals.length > 0) {
14929
- requirements.push(
14930
- requirement2(
14931
- "indeterminate",
14932
- "indeterminate",
14933
- { kind: "unknown" },
14934
- commandRedacted,
14935
- environmentSignals
14936
- )
14937
- );
14938
- opacity = joinEffectOpacity(opacity, "opaque");
14939
- for (const signal of environmentSignals) {
14940
- signals.add(signal);
14941
- }
14942
- }
14943
- if (context.pipeToShellSegment) {
14944
- signals.add("pipe_to_shell");
14945
- ensureRequirement(
14946
- requirements,
14947
- requirement2(
14948
- "process.exec",
14949
- "process.exec",
14950
- { kind: "executable", command: head || "sh", operation: "spawn" },
14951
- commandRedacted,
14952
- ["pipe_to_shell"]
14953
- )
14954
- );
14955
- ensureRequirement(
14956
- requirements,
14957
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
14958
- "pipe_to_shell"
14959
- ])
14960
- );
14961
- opacity = joinEffectOpacity(opacity, "opaque");
14962
- }
14963
- if (opacity === "unparseable" || opacity === "opaque") {
14964
- ensureRequirement(
14965
- requirements,
14966
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
14967
- `shell.${opacity}`
14968
- ])
14969
- );
14970
- }
14971
- return shellSegment(commandRedacted, head, requirements, opacity, signals);
14972
- }
14973
- function decodePackageExec(peel, cwd, repoRoot, segment) {
14974
- if (peel.opaque) {
14975
- return [
14976
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
14977
- peel.reason,
14978
- ...peel.signals
14979
- ])
14980
- ];
14981
- }
14982
- if (peel.reason === "npx_wrapper_readonly") {
14983
- return [];
14984
- }
14985
- const binHead = peel.innerTokens[0] ?? "";
14986
- const metadataOnly = packageExecInnerIsMetadata(peel);
14987
- const local = !peel.forceAcquire ? resolveLocalBin(binHead, cwd, repoRoot) : null;
14988
- if (local) {
14989
- return metadataOnly ? [
14990
- requirement2(
14991
- "process.exec",
14992
- "process.exec",
14993
- { kind: "executable", command: local.path, operation: "inspect" },
14994
- segment,
14995
- ["package_exec.delegate", "package_exec.local_bin_resolved"]
14996
- )
14997
- ] : [];
14998
- }
14999
- const networkResources = peel.acquisitionSpecs.map((spec) => classifyPackageAcquisitionSpec(spec)).filter((source) => source.kind !== "local").map(
15000
- (source) => source.kind === "registry" ? {
15001
- kind: "network",
15002
- host: "registry.npmjs.org",
15003
- protocol: "registry",
15004
- mode: "read",
15005
- payload: "none"
15006
- } : {
15007
- kind: "network",
15008
- host: source.host,
15009
- ...source.port ? { port: source.port } : {},
15010
- protocol: source.protocol,
15011
- mode: "read",
15012
- payload: "none"
15013
- }
14724
+ function withProvenances(requirementValue, provenance, contributing) {
14725
+ const provenances = [
14726
+ ...requirementValue.provenances ?? [requirementValue.provenance],
14727
+ contributing,
14728
+ provenance
14729
+ ].filter(
14730
+ (candidate, index, all) => all.findIndex((entry) => JSON.stringify(entry) === JSON.stringify(candidate)) === index
15014
14731
  );
15015
- if (networkResources.length === 0) {
15016
- networkResources.push({
15017
- kind: "network",
15018
- host: "registry.npmjs.org",
15019
- protocol: "registry",
15020
- mode: "read",
15021
- payload: "none"
15022
- });
15023
- }
15024
- return [
15025
- ...networkResources.map(
15026
- (resource) => requirement2("network.acquire", "network.connect", resource, segment, [
15027
- "package_acquire_possible"
15028
- ])
15029
- ),
15030
- requirement2(
15031
- "fs.write",
15032
- "fs.write",
15033
- { kind: "package-cache", manager: peel.launcher === "pnpm" ? "pnpm" : "npm" },
15034
- segment,
15035
- ["package_cache_write"]
15036
- ),
15037
- ...metadataOnly ? [
15038
- requirement2(
15039
- "process.exec",
15040
- "process.exec",
15041
- { kind: "executable", command: binHead || peel.launcher, operation: "inspect" },
15042
- segment,
15043
- ["package_exec.delegate"]
15044
- )
15045
- ] : []
15046
- ];
14732
+ return { ...requirementValue, provenance, provenances };
15047
14733
  }
15048
- function packageExecInnerIsMetadata(peel) {
15049
- const args = peel.innerTokens.slice(1);
15050
- return args.length > 0 && args.every((arg) => ["--help", "--version", "-V", "-h", "-v"].includes(arg));
14734
+ function withInnerProvenance(requirementValue, innerCommand, launcher, outerSegment) {
14735
+ const provenance = {
14736
+ ...requirementValue.provenance,
14737
+ segment: outerSegment,
14738
+ innerCommand: requirementValue.provenance.innerCommand ?? innerCommand,
14739
+ ...launcher === "npm" || launcher === "pnpm" ? { launcher, phase: "delegate" } : {}
14740
+ };
14741
+ const launcherProvenance = {
14742
+ segment: outerSegment,
14743
+ innerCommand,
14744
+ ...launcher === "npm" || launcher === "pnpm" ? { launcher, phase: "delegate" } : {}
14745
+ };
14746
+ return withProvenances(requirementValue, provenance, launcherProvenance);
15051
14747
  }
14748
+
14749
+ // src/core/effect-ir/shell-lower/tokens.ts
14750
+ init_shell_tokenizer();
14751
+ import path42 from "node:path";
14752
+ var ENV_PREFIX_PATTERN2 = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
14753
+ var LOOPBACK_HOSTS2 = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1", "0:0:0:0:0:0:0:1"]);
15052
14754
  var METADATA_ONLY_FLAGS = /* @__PURE__ */ new Set(["--version", "-v", "-V", "--help", "-h"]);
15053
- function isMetadataOnlyArgv(argv) {
15054
- return argv.length > 0 && argv.every((token) => METADATA_ONLY_FLAGS.has(token));
15055
- }
15056
- function executableBaseName(head) {
15057
- return path41.basename(head);
15058
- }
15059
- var RAILS_READ_ONLY_SUBCOMMANDS = /* @__PURE__ */ new Set(["routes", "middleware", "stats", "about", "version"]);
15060
- function railsReadOnlySubcommand(args) {
15061
- const subcommand = args[0];
15062
- if (!subcommand || subcommand.startsWith("-")) {
15063
- return false;
15064
- }
15065
- return RAILS_READ_ONLY_SUBCOMMANDS.has(subcommand);
15066
- }
15067
- function isRubyTestScript(scriptPath) {
15068
- const base = path41.basename(scriptPath);
15069
- return base.endsWith("_test.rb") || base.endsWith("_spec.rb");
15070
- }
15071
- function parseRubyTestInvocation(args) {
15072
- const includePaths = [];
15073
- for (let index = 0; index < args.length; index += 1) {
15074
- const arg = args[index] ?? "";
15075
- if (arg === "-e" || arg === "-r") {
15076
- return null;
15077
- }
15078
- if (arg === "-I") {
15079
- const includePath = args[index + 1];
15080
- if (!includePath) {
15081
- return null;
14755
+ function extractEnvironment(tokens, inherited) {
14756
+ const env = { ...inherited };
14757
+ const changedNames = /* @__PURE__ */ new Set();
14758
+ if (tokens[0] === "env") {
14759
+ let index = 1;
14760
+ let malformed = false;
14761
+ while (index < tokens.length) {
14762
+ const option = tokens[index] ?? "";
14763
+ if (option === "-u" || option === "--unset") {
14764
+ const name = tokens[index + 1] ?? "";
14765
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
14766
+ malformed = true;
14767
+ index += 1;
14768
+ break;
14769
+ }
14770
+ delete env[name];
14771
+ changedNames.add(name);
14772
+ index += 2;
14773
+ continue;
15082
14774
  }
15083
- includePaths.push(includePath);
15084
- index += 1;
15085
- continue;
15086
- }
15087
- if (arg.startsWith("-I") && arg.length > 2) {
15088
- includePaths.push(arg.slice(2));
15089
- continue;
15090
- }
15091
- if (arg.startsWith("-")) {
15092
- if (arg === "-n") {
15093
- if (!args[index + 1]) {
15094
- return null;
14775
+ if (option.startsWith("--unset=")) {
14776
+ const name = option.slice("--unset=".length);
14777
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
14778
+ malformed = true;
14779
+ index += 1;
14780
+ break;
15095
14781
  }
14782
+ delete env[name];
14783
+ changedNames.add(name);
15096
14784
  index += 1;
15097
14785
  continue;
15098
14786
  }
15099
- if (arg.startsWith("-n")) {
14787
+ if (option === "-i" || option === "--ignore-environment") {
14788
+ for (const name of Object.keys(env)) {
14789
+ delete env[name];
14790
+ }
14791
+ changedNames.add("*");
14792
+ index += 1;
15100
14793
  continue;
15101
14794
  }
15102
- return null;
14795
+ if (option === "--") {
14796
+ index += 1;
14797
+ break;
14798
+ }
14799
+ if (option.startsWith("-")) {
14800
+ malformed = true;
14801
+ index += 1;
14802
+ break;
14803
+ }
14804
+ break;
15103
14805
  }
15104
- if (isRubyTestScript(arg)) {
15105
- return { includePaths, scriptPath: arg };
14806
+ while (index < tokens.length) {
14807
+ const token = tokens[index] ?? "";
14808
+ const match = ENV_PREFIX_PATTERN2.exec(token);
14809
+ if (!match) {
14810
+ break;
14811
+ }
14812
+ const [, name, value] = match;
14813
+ if (name) {
14814
+ env[name] = expandKnownVariables(value ?? "", env);
14815
+ changedNames.add(name);
14816
+ }
14817
+ index += 1;
15106
14818
  }
15107
- return null;
15108
- }
15109
- return null;
15110
- }
15111
- function isRubocopMutating(args) {
15112
- return args.some(
15113
- (arg) => arg === "-A" || arg === "-a" || arg === "--auto-correct" || arg === "--autocorrect" || arg.startsWith("--auto-correct-all") || arg.startsWith("--autocorrect-all")
15114
- );
15115
- }
15116
- function decodeBundleExecInner(innerHead, innerArgs, segment) {
15117
- const innerBase = executableBaseName(innerHead);
15118
- if (innerBase === "rubocop") {
15119
- const mutating = isRubocopMutating(innerArgs);
15120
- return [
15121
- processRequirement(
15122
- innerHead,
15123
- mutating ? "spawn" : "inspect",
15124
- segment,
15125
- mutating ? ["process.linter.mutating"] : ["process.inspect.linter"]
15126
- )
15127
- ];
14819
+ const commandTokens = tokens.slice(index);
14820
+ return {
14821
+ env,
14822
+ commandTokens,
14823
+ malformed: malformed || commandTokens.length === 0,
14824
+ changedNames
14825
+ };
15128
14826
  }
15129
- if (innerBase === "rspec") {
15130
- const targetArgs = innerArgs.filter((arg) => !arg.startsWith("-"));
15131
- if (targetArgs.length === 0) {
15132
- return null;
14827
+ for (const token of tokens) {
14828
+ const match = ENV_PREFIX_PATTERN2.exec(token);
14829
+ if (!match) {
14830
+ break;
14831
+ }
14832
+ const [, name, value] = match;
14833
+ if (name) {
14834
+ env[name] = expandKnownVariables(value ?? "", env);
14835
+ changedNames.add(name);
15133
14836
  }
15134
- return [processRequirement(innerHead, "spawn", segment, ["process.test_runner.rspec"])];
15135
14837
  }
15136
- return null;
14838
+ return { env, malformed: false, changedNames };
15137
14839
  }
15138
- function decodeRuby(args, cwd, repoRoot, segment) {
15139
- const parsed = parseRubyTestInvocation(args);
15140
- if (!parsed) {
15141
- return unsupportedProcess("ruby", segment, "process.ruby_grammar_incomplete");
15142
- }
15143
- const scriptPath = resolvePathOperand(parsed.scriptPath, cwd);
15144
- if (!pathWithinRoot(canonicalPath(repoRoot), canonicalPath(scriptPath))) {
15145
- return unsupportedProcess("ruby", segment, "process.ruby_outside_repo");
15146
- }
15147
- for (const includePath of parsed.includePaths) {
15148
- const resolvedInclude = resolvePathOperand(includePath, cwd);
15149
- if (!pathWithinRoot(canonicalPath(repoRoot), canonicalPath(resolvedInclude))) {
15150
- return unsupportedProcess("ruby", segment, "process.ruby_outside_repo");
14840
+ function expandKnownVariables(token, env) {
14841
+ return token.replace(
14842
+ /\$(?:\{([A-Za-z_][A-Za-z0-9_]*)\}|([A-Za-z_][A-Za-z0-9_]*))/g,
14843
+ (raw, a, b) => {
14844
+ const value = env[a ?? b];
14845
+ return value === void 0 ? raw : value;
15151
14846
  }
15152
- }
15153
- const lowered = [
15154
- processRequirement("ruby", "spawn", segment, ["process.test_runner.minitest"]),
15155
- requirement2("fs.read", "fs.read", { kind: "path", path: scriptPath }, segment, [
15156
- "ruby.minitest_script_read"
15157
- ])
15158
- ];
15159
- for (const includePath of parsed.includePaths) {
15160
- lowered.push(
15161
- requirement2(
15162
- "fs.read",
15163
- "fs.read",
15164
- { kind: "path", path: resolvePathOperand(includePath, cwd) },
15165
- segment,
15166
- ["ruby.minitest_load_path_read"]
15167
- )
15168
- );
15169
- }
15170
- return lowered;
14847
+ );
15171
14848
  }
15172
- function decodeRuntimeMetadataProcess(head, args, segment) {
15173
- if (head === "bundle") {
15174
- if (args.length === 1 && isMetadataOnlyArgv(args)) {
15175
- return [processRequirement(head, "inspect", segment, ["process.inspect.runtime_metadata"])];
14849
+ function stripRedirects(tokens) {
14850
+ const stripped = [];
14851
+ for (let index = 0; index < tokens.length; index += 1) {
14852
+ const token = tokens[index] ?? "";
14853
+ if (isFdDuplication(token)) {
14854
+ continue;
15176
14855
  }
15177
- if (args[0] === "exec" && args.length >= 2) {
15178
- const innerHead = args[1] ?? "";
15179
- const innerArgs = args.slice(2);
15180
- const innerBase = executableBaseName(innerHead);
15181
- if ((innerBase === "rails" || innerBase === "rake") && railsReadOnlySubcommand(innerArgs)) {
15182
- return [
15183
- processRequirement(innerHead, "inspect", segment, ["process.inspect.rails_read_only"])
15184
- ];
15185
- }
15186
- if (isMetadataOnlyArgv(innerArgs)) {
15187
- return [
15188
- processRequirement(innerHead, "inspect", segment, [
15189
- "process.inspect.bundle_exec_metadata"
15190
- ])
15191
- ];
15192
- }
15193
- const bundleExecInner = decodeBundleExecInner(innerHead, innerArgs, segment);
15194
- if (bundleExecInner) {
15195
- return bundleExecInner;
15196
- }
14856
+ if (!isRedirectOperator(token)) {
14857
+ stripped.push(token);
14858
+ continue;
15197
14859
  }
15198
- return null;
15199
- }
15200
- if ((head === "ruby" || head === "yarn") && args.length >= 1 && isMetadataOnlyArgv(args)) {
15201
- return [processRequirement(head, "inspect", segment, ["process.inspect.runtime_metadata"])];
15202
- }
15203
- if (head === "make" && args.includes("-n")) {
15204
- return [processRequirement(head, "inspect", segment, ["process.inspect.make_dry_run"])];
15205
- }
15206
- const base = executableBaseName(head);
15207
- if ((base === "rails" || base === "rake") && railsReadOnlySubcommand(args)) {
15208
- return [processRequirement(head, "inspect", segment, ["process.inspect.rails_read_only"])];
14860
+ index += 1;
15209
14861
  }
15210
- return null;
14862
+ return stripped;
15211
14863
  }
15212
- function decodeSetBuiltin(args) {
15213
- let index = 0;
15214
- while (index < args.length) {
15215
- const arg = args[index] ?? "";
15216
- if (arg === "--") {
15217
- index += 1;
14864
+ function stripStructuredRedirects(tokens) {
14865
+ const stripped = [];
14866
+ for (let index = 0; index < tokens.length; index += 1) {
14867
+ const token = tokens[index];
14868
+ if (!token) continue;
14869
+ if (isFdDuplication(token.value)) {
15218
14870
  continue;
15219
14871
  }
15220
- if (arg === "-o" || arg === "+o") {
15221
- if (!args[index + 1]) {
15222
- return false;
15223
- }
15224
- index += 2;
14872
+ if (!isRedirectOperator(token.value)) {
14873
+ stripped.push(token);
15225
14874
  continue;
15226
14875
  }
15227
- if (/^[-+][A-Za-z0-9]+$/.test(arg)) {
15228
- index += 1;
15229
- continue;
14876
+ index += 1;
14877
+ }
14878
+ return stripped;
14879
+ }
14880
+ function alignStructuredTokens(tokens, values) {
14881
+ if (values.length === 0) return [];
14882
+ for (let start = tokens.length - values.length; start >= 0; start -= 1) {
14883
+ const candidate = tokens.slice(start);
14884
+ if (candidate.length === values.length && candidate.every((token, index) => token.value === values[index])) {
14885
+ return candidate;
15230
14886
  }
15231
- return false;
15232
14887
  }
15233
- return true;
14888
+ return [];
15234
14889
  }
15235
- function decodeShellControlBuiltin(head, args) {
15236
- if (head === "set") {
15237
- return decodeSetBuiltin(args) ? [] : null;
14890
+ function resolvePathOperand(operand, cwd) {
14891
+ if (operand === "~") {
14892
+ return process.env.HOME ?? operand;
15238
14893
  }
15239
- if (head === "wait") {
15240
- if (args.length === 0 || args.every((arg) => /^\d+$/.test(arg))) {
15241
- return [];
15242
- }
14894
+ if (operand.startsWith("~/")) {
14895
+ return path42.join(process.env.HOME ?? "~", operand.slice(2));
14896
+ }
14897
+ return path42.resolve(cwd, operand);
14898
+ }
14899
+ function isOptionToken(value) {
14900
+ return value.startsWith("-") || value.startsWith("+");
14901
+ }
14902
+ function isShellHead(head) {
14903
+ return head === "bash" || head === "sh" || head === "zsh" || head === "dash" || head === "fish";
14904
+ }
14905
+ function pipeToShell(command) {
14906
+ return /(?:^|[|;&]\s*)(?:bash|sh|zsh|dash|fish)(?:\s|$)/.test(command) && /\|/.test(command);
14907
+ }
14908
+ function executableBaseName(head) {
14909
+ return path42.basename(head);
14910
+ }
14911
+ function isMetadataOnlyArgv(argv) {
14912
+ return argv.length > 0 && argv.every((token) => METADATA_ONLY_FLAGS.has(token));
14913
+ }
14914
+ function databaseEndpoint(raw) {
14915
+ if (!raw || raw.includes("$")) {
15243
14916
  return null;
15244
14917
  }
15245
- if (head === "exit") {
15246
- if (args.length === 0 || args.length === 1 && /^-?\d+$/.test(args[0] ?? "")) {
15247
- return [];
14918
+ try {
14919
+ const url = new URL(raw);
14920
+ if (!url.hostname || !url.protocol) {
14921
+ return null;
15248
14922
  }
14923
+ return {
14924
+ host: url.hostname.toLowerCase(),
14925
+ protocol: url.protocol.slice(0, -1),
14926
+ ...url.port ? { port: Number(url.port) } : {}
14927
+ };
14928
+ } catch {
15249
14929
  return null;
15250
14930
  }
15251
- return null;
15252
- }
15253
- function decodeDockerComposeRun2(head, args, segment) {
15254
- let composeArgs = null;
15255
- let command = head;
15256
- if (head === "docker-compose") {
15257
- composeArgs = args;
15258
- } else if (head === "docker" && args[0] === "compose") {
15259
- composeArgs = args.slice(1);
15260
- command = "docker";
15261
- }
15262
- if (!composeArgs) {
15263
- return null;
15264
- }
15265
- if (composeArgs.includes("run")) {
15266
- return [processRequirement(command, "spawn", segment, ["process.docker_compose_run"])];
15267
- }
15268
- return unsupportedProcess(command, segment, "process.docker_compose_grammar_incomplete");
15269
14931
  }
15270
- function decodeProcessOrFilesystem(params) {
15271
- const { tokens, head, env, cwd, repoRoot, segment } = params;
15272
- const args = tokens.slice(1);
15273
- if (tokens.length > 0 && tokens.every((token) => ENV_PREFIX_PATTERN2.test(token))) {
15274
- return [];
15275
- }
15276
- const shellControl = decodeShellControlBuiltin(head, args);
15277
- if (shellControl) {
15278
- return shellControl;
15279
- }
15280
- if (isCommandInspection(tokens)) {
15281
- return [processRequirement(head, "inspect", segment, ["process.inspect.command_lookup"])];
15282
- }
15283
- if (head === "lsof") {
15284
- return validLsof(args) ? [processRequirement(head, "inspect", segment, ["process.inspect.lsof"])] : unsupportedProcess(head, segment, "process.lsof_grammar_incomplete");
15285
- }
15286
- if (head === "ps") {
15287
- return validPs(args) ? [processRequirement(head, "inspect", segment, ["process.inspect.ps"])] : unsupportedProcess(head, segment, "process.ps_grammar_incomplete");
15288
- }
15289
- if (head === "test" || head === "[") {
15290
- return validFilesystemTest(head, args) ? [processRequirement(head, "inspect", segment, ["process.inspect.filesystem_test"])] : unsupportedProcess(head, segment, "process.filesystem_test_grammar_incomplete");
15291
- }
15292
- if (head === "docker") {
15293
- if (args[0] === "info") {
15294
- return validDockerInfo(args.slice(1)) ? [processRequirement(head, "inspect", segment, ["process.inspect.docker_info"])] : unsupportedProcess(head, segment, "process.docker_info_grammar_incomplete");
14932
+
14933
+ // src/core/effect-ir/shell-lower/augment.ts
14934
+ function addRedirectEffects(requirements, tokens, env, context, segment) {
14935
+ for (let index = 0; index < tokens.length; index += 1) {
14936
+ const operator = tokens[index] ?? "";
14937
+ if (!isRedirectOperator(operator) || isFdDuplication(operator)) {
14938
+ continue;
15295
14939
  }
15296
- if (args[0] === "compose" && ["up", "down", "start", "stop", "restart", "create", "rm"].includes(args[1] ?? "")) {
15297
- return [processRequirement(head, "spawn", segment, ["service.local_mutation"])];
14940
+ const rawTarget = tokens[index + 1];
14941
+ if (!rawTarget) {
14942
+ requirements.push(
14943
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
14944
+ "shell.redirect_target_missing"
14945
+ ])
14946
+ );
14947
+ continue;
15298
14948
  }
15299
- if (args[0] === "push" || args[0] === "build" && args.includes("--push") || args[0] === "buildx" && args[1] === "build" && (args.includes("--push") || args.some(
15300
- (arg) => arg.startsWith("--output=type=registry") || arg.startsWith("-o=type=registry")
15301
- ))) {
15302
- return [
15303
- processRequirement(head, "spawn", segment, ["tier0_external", "docker.remote_mutation"]),
15304
- requirement2(
15305
- "network.connect",
15306
- "network.connect",
15307
- {
15308
- kind: "network",
15309
- host: "registry",
15310
- protocol: "container-registry",
15311
- mode: "mutate",
15312
- payload: "present"
15313
- },
15314
- segment,
15315
- ["tier0_external", "docker.remote_mutation"]
15316
- )
15317
- ];
14949
+ if (operator.includes("<<")) {
14950
+ requirements.push(
14951
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
14952
+ "shell.heredoc_opaque"
14953
+ ])
14954
+ );
14955
+ index += 1;
14956
+ continue;
15318
14957
  }
15319
- if (args[0] === "run" || args[0] === "create" || args[0] === "pull" || args[0] === "build" || args[0] === "buildx" && args[1] === "build") {
15320
- return [
15321
- processRequirement(head, "spawn", segment, ["process.docker_spawn"]),
15322
- requirement2(
15323
- "network.acquire",
15324
- "network.connect",
15325
- {
15326
- kind: "network",
15327
- host: "unknown",
15328
- protocol: "container-registry",
15329
- mode: "ambiguous",
15330
- payload: "none"
15331
- },
15332
- segment,
15333
- ["docker.image_acquisition_possible"]
15334
- )
15335
- ];
14958
+ const target = expandKnownVariables(rawTarget, env);
14959
+ const resolved = resolvePathOperand(target, context.cwd);
14960
+ if (resolved === "/dev/null") {
14961
+ index += 1;
14962
+ continue;
15336
14963
  }
15337
- return [processRequirement(head, "spawn", segment, ["process.docker_spawn"])];
15338
- }
15339
- if (isShellHead(head)) {
15340
- const syntax = bashSyntaxTarget(args);
15341
- if (syntax) {
15342
- return [
15343
- processRequirement(head, "inspect", segment, ["process.inspect.shell_syntax"]),
15344
- requirement2(
15345
- "fs.read",
15346
- "fs.read",
15347
- { kind: "path", path: path41.resolve(cwd, syntax) },
15348
- segment,
15349
- ["shell.syntax_source_read"]
15350
- )
15351
- ];
14964
+ if (operator.includes("<")) {
14965
+ requirements.push(
14966
+ requirement2("fs.read", "fs.read", { kind: "path", path: resolved }, segment, [
14967
+ "shell.input_redirect"
14968
+ ])
14969
+ );
14970
+ addSecretRead(requirements, resolved, segment);
15352
14971
  }
15353
- return unsupportedProcess(head, segment, "process.shell_grammar_incomplete");
15354
- }
15355
- if (head === "prisma") {
15356
- return decodePrisma(args, env, repoRoot, segment);
15357
- }
15358
- const runtimeMetadata = decodeRuntimeMetadataProcess(head, args, segment);
15359
- if (runtimeMetadata) {
15360
- return runtimeMetadata;
15361
- }
15362
- if (head === "ruby") {
15363
- return decodeRuby(args, cwd, repoRoot, segment);
15364
- }
15365
- if (head === "rubocop" || head === "rspec") {
15366
- const decoded = decodeBundleExecInner(head, args, segment);
15367
- if (decoded) {
15368
- return decoded;
14972
+ if (operator.includes(">")) {
14973
+ addWriteEffects(requirements, resolved, segment, ["shell.output_redirect"]);
15369
14974
  }
14975
+ index += 1;
15370
14976
  }
15371
- const dockerCompose = decodeDockerComposeRun2(head, args, segment);
15372
- if (dockerCompose) {
15373
- return dockerCompose;
15374
- }
15375
- if ((head === "npm" || head === "pnpm") && args.length === 1 && isMetadataOnlyArgv(args)) {
15376
- return [processRequirement(head, "inspect", segment, ["process.inspect.package_manager"])];
15377
- }
15378
- if (head === "belay") {
15379
- return decodeBelay(args, repoRoot, segment);
15380
- }
15381
- if (head === "tsc") {
15382
- return decodeTsc(args, cwd, segment);
15383
- }
15384
- if (head === "go") {
15385
- return decodeGo(args, segment);
15386
- }
15387
- if (head === "rsync") {
15388
- return decodeRsync(args, cwd, segment);
15389
- }
15390
- if (head === "sed") {
15391
- return decodeSed(args, cwd, segment);
15392
- }
15393
- if (head === "base64" && args.length === 1 && ["-d", "-D", "--decode"].includes(args[0] ?? "")) {
15394
- return [processRequirement(head, "inspect", segment, ["process.inspect.base64_stdin"])];
15395
- }
15396
- if (head === "node") {
15397
- return decodeNode2(args, cwd, segment);
15398
- }
15399
- if (head === "vite" || head === "vite-node") {
15400
- return [processRequirement(head, "spawn", segment, ["process.local_dev_spawn"])];
14977
+ }
14978
+ function effectChangingEnvironmentSignals(head, env, changedNames) {
14979
+ if (head === "curl" && (env.CURL_HOME || changedNames.has("CURL_HOME") || changedNames.has("HOME") || changedNames.has("XDG_CONFIG_HOME"))) {
14980
+ return ["egress.curl.environment_config_override"];
15401
14981
  }
15402
- if ((head === "npm" || head === "pnpm") && args[0] === "publish" || head === "terraform" && args[0] === "apply") {
15403
- return [
15404
- processRequirement(head, "spawn", segment, ["tier0_external"]),
15405
- requirement2(
15406
- "network.connect",
15407
- "network.connect",
15408
- {
15409
- kind: "network",
15410
- host: "control-plane",
15411
- protocol: head,
15412
- mode: "mutate",
15413
- payload: "present"
15414
- },
15415
- segment,
15416
- ["tier0_external"]
15417
- )
15418
- ];
14982
+ if (head === "wget" && (env.WGETRC || changedNames.has("WGETRC") || changedNames.has("HOME"))) {
14983
+ return ["egress.wget.environment_config_override"];
15419
14984
  }
15420
- if (head === "cd") {
14985
+ if (head !== "git") {
15421
14986
  return [];
15422
14987
  }
15423
- if (head === "cp" || head === "mv") {
15424
- return decodeCopyMove(head, args, cwd, segment);
15425
- }
15426
- if (head === "rm") {
15427
- return decodeRm(args, cwd, repoRoot, segment);
15428
- }
15429
- if (head === "vitest" || head === "eslint" || head === "biome" || head === "belay") {
15430
- return [processRequirement(head, "spawn", segment, ["process.known_local_spawn"])];
15431
- }
15432
- if (head === "pwd" || head === "which" || head === "whoami") {
15433
- return [processRequirement(head, "inspect", segment, ["process.pure_inspection"])];
15434
- }
15435
- const readOperands = filesystemReadOperands(head, args);
15436
- if (readOperands !== null) {
15437
- const lowered = [processRequirement(head, "inspect", segment, ["process.filesystem_inspect"])];
15438
- for (const operand of readOperands) {
15439
- const resolved = resolvePathOperand(operand, cwd);
15440
- lowered.push(
15441
- requirement2("fs.read", "fs.read", { kind: "path", path: resolved }, segment, [
15442
- "filesystem.read"
15443
- ])
15444
- );
15445
- addSecretRead(lowered, resolved, segment);
14988
+ const hazardousNames = /* @__PURE__ */ new Set([
14989
+ "GIT_ASKPASS",
14990
+ "GIT_ALTERNATE_OBJECT_DIRECTORIES",
14991
+ "GIT_CEILING_DIRECTORIES",
14992
+ "GIT_COMMON_DIR",
14993
+ "GIT_CONFIG",
14994
+ "GIT_CONFIG_GLOBAL",
14995
+ "GIT_CONFIG_SYSTEM",
14996
+ "GIT_DIR",
14997
+ "GIT_DISCOVERY_ACROSS_FILESYSTEM",
14998
+ "GIT_EDITOR",
14999
+ "GIT_EXEC_PATH",
15000
+ "GIT_EXTERNAL_DIFF",
15001
+ "GIT_INDEX_FILE",
15002
+ "GIT_NAMESPACE",
15003
+ "GIT_OBJECT_DIRECTORY",
15004
+ "GIT_PAGER",
15005
+ "GIT_PROXY_COMMAND",
15006
+ "GIT_QUARANTINE_PATH",
15007
+ "GIT_SEQUENCE_EDITOR",
15008
+ "GIT_SHALLOW_FILE",
15009
+ "GIT_SSH",
15010
+ "GIT_SSH_COMMAND",
15011
+ "GIT_TEMPLATE_DIR",
15012
+ "GIT_WORK_TREE",
15013
+ "SSH_ASKPASS"
15014
+ ]);
15015
+ const overridden = changedNames.has("HOME") || changedNames.has("XDG_CONFIG_HOME") || Object.entries(env).some(([name, value]) => {
15016
+ if (!value) {
15017
+ return false;
15446
15018
  }
15447
- return lowered;
15448
- }
15449
- const writeOperands = filesystemWriteOperands(head, args);
15450
- if (writeOperands !== null) {
15451
- const lowered = [processRequirement(head, "spawn", segment, ["process.filesystem_mutation"])];
15452
- for (const operand of writeOperands) {
15453
- addWriteEffects(lowered, resolvePathOperand(operand, cwd), segment, ["filesystem.write"]);
15019
+ if (hazardousNames.has(name) || /^GIT_CONFIG_(?:KEY|VALUE)_\d+$/.test(name) || /^GIT_TRACE/.test(name)) {
15020
+ return true;
15454
15021
  }
15455
- return lowered;
15456
- }
15457
- if (head === "printf" || head === "echo" || head === "true" || head === "false" || head === ":") {
15458
- return [];
15459
- }
15460
- if (!head) {
15461
- return [
15462
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
15463
- "shell.segment_empty"
15464
- ])
15465
- ];
15466
- }
15467
- return unsupportedProcess(head, segment, "process.grammar_unknown");
15022
+ return name === "GIT_CONFIG_COUNT" && value !== "0";
15023
+ });
15024
+ return overridden ? ["git.environment_execution_or_config_override"] : [];
15468
15025
  }
15026
+
15027
+ // src/core/effect-ir/shell-lower/decode-process.ts
15028
+ import path46 from "node:path";
15029
+
15030
+ // src/core/effect-ir/shell-lower/decoders/belay.ts
15031
+ import path43 from "node:path";
15469
15032
  function decodeBelay(args, repoRoot, segment) {
15470
15033
  const [section, operation, key] = args;
15471
15034
  const judgeCommand = section === "judge" && operation !== void 0 && ["consent", "list", "status", "test", "use"].includes(operation);
@@ -15484,7 +15047,7 @@ function decodeBelay(args, repoRoot, segment) {
15484
15047
  requirement2(
15485
15048
  "control_plane.write",
15486
15049
  "control_plane.write",
15487
- { kind: "path", path: path41.join(repoRoot, ".belay-control-plane") },
15050
+ { kind: "path", path: path43.join(repoRoot, ".belay-control-plane") },
15488
15051
  segment,
15489
15052
  ["belay.config_non_judge_mutation"]
15490
15053
  )
@@ -15492,33 +15055,240 @@ function decodeBelay(args, repoRoot, segment) {
15492
15055
  }
15493
15056
  return [processRequirement("belay", "spawn", segment, ["process.known_local_spawn"])];
15494
15057
  }
15495
- function decodeTsc(args, cwd, segment) {
15496
- const metadataOnly = args.length > 0 && args.every((arg) => ["--help", "--version", "-h", "-v"].includes(arg));
15497
- const requirements = [];
15498
- let hasOutput = false;
15058
+
15059
+ // src/core/effect-ir/shell-lower/decoders/builtins.ts
15060
+ function decodeSetBuiltin(args) {
15061
+ let index = 0;
15062
+ while (index < args.length) {
15063
+ const arg = args[index] ?? "";
15064
+ if (arg === "--") {
15065
+ index += 1;
15066
+ continue;
15067
+ }
15068
+ if (arg === "-o" || arg === "+o") {
15069
+ if (!args[index + 1]) {
15070
+ return false;
15071
+ }
15072
+ index += 2;
15073
+ continue;
15074
+ }
15075
+ if (/^[-+][A-Za-z0-9]+$/.test(arg)) {
15076
+ index += 1;
15077
+ continue;
15078
+ }
15079
+ return false;
15080
+ }
15081
+ return true;
15082
+ }
15083
+ function decodeShellControlBuiltin(head, args) {
15084
+ if (head === "set") {
15085
+ return decodeSetBuiltin(args) ? [] : null;
15086
+ }
15087
+ if (head === "wait") {
15088
+ if (args.length === 0 || args.every((arg) => /^\d+$/.test(arg))) {
15089
+ return [];
15090
+ }
15091
+ return null;
15092
+ }
15093
+ if (head === "exit") {
15094
+ if (args.length === 0 || args.length === 1 && /^-?\d+$/.test(args[0] ?? "")) {
15095
+ return [];
15096
+ }
15097
+ return null;
15098
+ }
15099
+ return null;
15100
+ }
15101
+ function validLsof(args) {
15102
+ const flagsWithoutValues = /* @__PURE__ */ new Set(["-a", "-b", "-l", "-n", "-P", "-R", "-t", "-V"]);
15103
+ const flagsWithValues = /* @__PURE__ */ new Set(["-c", "-d", "-F", "-g", "-p", "-T", "-u", "+d", "+D"]);
15499
15104
  for (let index = 0; index < args.length; index += 1) {
15500
15105
  const arg = args[index] ?? "";
15501
- const inline = arg.match(/^--(?:outDir|outFile)=(.+)$/)?.[1];
15502
- const separate = (arg === "--outDir" || arg === "--outFile") && args[index + 1] ? args[index + 1] : null;
15503
- const output = inline ?? separate;
15504
- if (!output) {
15106
+ if (flagsWithoutValues.has(arg) || arg === "-i") {
15107
+ continue;
15108
+ }
15109
+ if (flagsWithValues.has(arg)) {
15110
+ if (!args[index + 1] || isOptionToken(args[index + 1] ?? "")) {
15111
+ return false;
15112
+ }
15113
+ index += 1;
15114
+ continue;
15115
+ }
15116
+ 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)) {
15117
+ continue;
15118
+ }
15119
+ return false;
15120
+ }
15121
+ return true;
15122
+ }
15123
+ function validPs(args) {
15124
+ const bareForms = /* @__PURE__ */ new Set(["aux", "ax", "x", "u"]);
15125
+ const flagsWithoutValues = /* @__PURE__ */ new Set(["-A", "-a", "-d", "-e", "-f", "-j", "-l", "-T", "-x"]);
15126
+ const flagsWithValues = /* @__PURE__ */ new Set([
15127
+ "--format",
15128
+ "--group",
15129
+ "--pid",
15130
+ "--ppid",
15131
+ "--sort",
15132
+ "--tty",
15133
+ "--user"
15134
+ ]);
15135
+ for (let index = 0; index < args.length; index += 1) {
15136
+ const arg = args[index] ?? "";
15137
+ if (bareForms.has(arg) || flagsWithoutValues.has(arg)) {
15138
+ continue;
15139
+ }
15140
+ if (flagsWithValues.has(arg)) {
15141
+ if (!args[index + 1] || isOptionToken(args[index + 1] ?? "")) {
15142
+ return false;
15143
+ }
15144
+ index += 1;
15145
+ continue;
15146
+ }
15147
+ if (/^(?:--format|--group|--pid|--ppid|--sort|--tty|--user)=.+$/.test(arg)) {
15148
+ continue;
15149
+ }
15150
+ if (/^-[^-]/.test(arg)) {
15151
+ const noValue = /* @__PURE__ */ new Set(["A", "a", "d", "e", "f", "j", "l", "T", "x"]);
15152
+ const withValue = /* @__PURE__ */ new Set(["G", "g", "N", "o", "p", "t", "U", "u"]);
15153
+ const characters = arg.slice(1);
15154
+ let valid = true;
15155
+ for (let optionIndex = 0; optionIndex < characters.length; optionIndex += 1) {
15156
+ const option = characters[optionIndex] ?? "";
15157
+ if (noValue.has(option)) {
15158
+ continue;
15159
+ }
15160
+ if (withValue.has(option)) {
15161
+ if (optionIndex === characters.length - 1) {
15162
+ if (!args[index + 1] || isOptionToken(args[index + 1] ?? "")) {
15163
+ return false;
15164
+ }
15165
+ index += 1;
15166
+ }
15167
+ break;
15168
+ }
15169
+ valid = false;
15170
+ break;
15171
+ }
15172
+ if (valid) {
15173
+ continue;
15174
+ }
15175
+ }
15176
+ return false;
15177
+ }
15178
+ return true;
15179
+ }
15180
+ function validFilesystemTest(head, args) {
15181
+ const operands = head === "[" && args.at(-1) === "]" ? args.slice(0, -1) : args;
15182
+ if (head === "[" && args.at(-1) !== "]") {
15183
+ return false;
15184
+ }
15185
+ return operands.length === 2 && (/* @__PURE__ */ new Set(["-b", "-c", "-d", "-e", "-f", "-h", "-L", "-r", "-s", "-w", "-x"])).has(
15186
+ operands[0] ?? ""
15187
+ ) && Boolean(operands[1]);
15188
+ }
15189
+ function bashSyntaxTarget(args) {
15190
+ let syntaxOnly = false;
15191
+ let script = null;
15192
+ const shortFlags = new Set("abefhkmnptuvxBCEHPT".split(""));
15193
+ const longFlags = /* @__PURE__ */ new Set([
15194
+ "--debugger",
15195
+ "--dump-po-strings",
15196
+ "--dump-strings",
15197
+ "--help",
15198
+ "--login",
15199
+ "--noediting",
15200
+ "--noprofile",
15201
+ "--norc",
15202
+ "--posix",
15203
+ "--restricted",
15204
+ "--verbose",
15205
+ "--version"
15206
+ ]);
15207
+ for (let index = 0; index < args.length; index += 1) {
15208
+ const arg = args[index] ?? "";
15209
+ if (arg === "-c" || arg === "-lc") {
15210
+ return null;
15211
+ }
15212
+ if (arg === "-O" || arg === "+O" || arg === "--rcfile" || arg === "--init-file") {
15213
+ if (!args[index + 1] || isOptionToken(args[index + 1] ?? "")) {
15214
+ return null;
15215
+ }
15216
+ index += 1;
15217
+ continue;
15218
+ }
15219
+ 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) {
15220
+ continue;
15221
+ }
15222
+ if (longFlags.has(arg)) {
15223
+ continue;
15224
+ }
15225
+ if (/^[+-][A-Za-z]+$/.test(arg)) {
15226
+ const options = arg.slice(1);
15227
+ if ([...options].some((option) => !shortFlags.has(option))) {
15228
+ return null;
15229
+ }
15230
+ syntaxOnly ||= options.includes("n");
15231
+ continue;
15232
+ }
15233
+ if (arg === "--") {
15234
+ script = args[index + 1] ?? null;
15235
+ break;
15236
+ }
15237
+ if (arg.startsWith("-") || arg.startsWith("+")) {
15238
+ return null;
15239
+ }
15240
+ script = arg;
15241
+ break;
15242
+ }
15243
+ return syntaxOnly ? script : null;
15244
+ }
15245
+
15246
+ // src/core/effect-ir/shell-lower/decoders/docker.ts
15247
+ function decodeDockerComposeRun2(head, args, segment) {
15248
+ let composeArgs = null;
15249
+ let command = head;
15250
+ if (head === "docker-compose") {
15251
+ composeArgs = args;
15252
+ } else if (head === "docker" && args[0] === "compose") {
15253
+ composeArgs = args.slice(1);
15254
+ command = "docker";
15255
+ }
15256
+ if (!composeArgs) {
15257
+ return null;
15258
+ }
15259
+ if (composeArgs.includes("run")) {
15260
+ return [processRequirement(command, "spawn", segment, ["process.docker_compose_run"])];
15261
+ }
15262
+ return unsupportedProcess(command, segment, "process.docker_compose_grammar_incomplete");
15263
+ }
15264
+ function validDockerInfo(args) {
15265
+ for (let index = 0; index < args.length; index += 1) {
15266
+ const arg = args[index] ?? "";
15267
+ if (arg === "--help") {
15505
15268
  continue;
15506
15269
  }
15507
- hasOutput = true;
15508
- addWriteEffects(requirements, resolvePathOperand(output, cwd), segment, ["tsc.output"]);
15509
- if (separate) {
15270
+ if (arg === "--format" || arg === "-f") {
15271
+ if (!args[index + 1] || isOptionToken(args[index + 1] ?? "")) {
15272
+ return false;
15273
+ }
15510
15274
  index += 1;
15275
+ continue;
15276
+ }
15277
+ if (arg.startsWith("--format=")) {
15278
+ if (arg.slice("--format=".length).length === 0) {
15279
+ return false;
15280
+ }
15281
+ continue;
15511
15282
  }
15283
+ return false;
15512
15284
  }
15513
- const typecheckOnly = args.includes("--noEmit") && !hasOutput;
15514
- const inspect = metadataOnly || typecheckOnly;
15515
- requirements.unshift(
15516
- processRequirement("tsc", inspect ? "inspect" : "spawn", segment, [
15517
- metadataOnly ? "process.inspect.tsc_metadata" : typecheckOnly ? "process.inspect.tsc_typecheck" : "process.known_local_spawn"
15518
- ])
15519
- );
15520
- return requirements;
15285
+ return true;
15521
15286
  }
15287
+
15288
+ // src/core/effect-ir/shell-lower/decoders/filesystem.ts
15289
+ init_git_resource_identity();
15290
+ import { lstatSync as lstatSync2, realpathSync as realpathSync4 } from "node:fs";
15291
+ import path44 from "node:path";
15522
15292
  function decodeCopyMove(head, args, cwd, segment) {
15523
15293
  const requirements = [processRequirement(head, "spawn", segment, ["process.filesystem_mutation"])];
15524
15294
  const operands = [];
@@ -15641,28 +15411,349 @@ function decodeRm(args, cwd, repoRoot, segment) {
15641
15411
  );
15642
15412
  }
15643
15413
  }
15644
- return requirements;
15414
+ return requirements;
15415
+ }
15416
+ function canonicalRmOperand(targetPath, finalOperandIsSymlink) {
15417
+ try {
15418
+ if (finalOperandIsSymlink) {
15419
+ return path44.join(realpathSync4.native(path44.dirname(targetPath)), path44.basename(targetPath));
15420
+ }
15421
+ return realpathSync4.native(targetPath);
15422
+ } catch {
15423
+ return path44.resolve(targetPath);
15424
+ }
15425
+ }
15426
+ function isSymbolicLink(targetPath) {
15427
+ try {
15428
+ return lstatSync2(targetPath).isSymbolicLink();
15429
+ } catch {
15430
+ return false;
15431
+ }
15432
+ }
15433
+ function pathContains(ancestor, candidate) {
15434
+ const relative = path44.relative(path44.resolve(ancestor), path44.resolve(candidate));
15435
+ return relative === "" || !relative.startsWith("..") && !path44.isAbsolute(relative);
15436
+ }
15437
+ function filesystemReadOperands(head, args) {
15438
+ switch (head) {
15439
+ case "ls": {
15440
+ const operands = args.filter((arg) => arg && !arg.startsWith("-"));
15441
+ return operands.length > 0 ? operands : ["."];
15442
+ }
15443
+ case "find":
15444
+ return findReadOperands(args);
15445
+ case "cat":
15446
+ case "head":
15447
+ case "tail":
15448
+ case "less":
15449
+ case "stat":
15450
+ case "file":
15451
+ case "du":
15452
+ case "wc":
15453
+ return args.filter((arg) => arg && !arg.startsWith("-"));
15454
+ case "grep":
15455
+ case "rg":
15456
+ return args.slice(1).filter((arg) => arg && !arg.startsWith("-"));
15457
+ case "jq":
15458
+ return args.slice(1).filter((arg) => arg && !arg.startsWith("-"));
15459
+ default:
15460
+ return null;
15461
+ }
15462
+ }
15463
+ function findReadOperands(args) {
15464
+ const mutatingPrimaries = /* @__PURE__ */ new Set([
15465
+ "-delete",
15466
+ "-exec",
15467
+ "-execdir",
15468
+ "-fls",
15469
+ "-fprint",
15470
+ "-fprintf",
15471
+ "-ok",
15472
+ "-okdir"
15473
+ ]);
15474
+ if (args.some(
15475
+ (arg) => mutatingPrimaries.has(arg) || [...mutatingPrimaries].some((primary) => arg.startsWith(`${primary}=`))
15476
+ )) {
15477
+ return null;
15478
+ }
15479
+ const paths = [];
15480
+ for (const arg of args) {
15481
+ if (arg.startsWith("-") || arg === "!" || arg === "(") {
15482
+ break;
15483
+ }
15484
+ paths.push(arg);
15485
+ }
15486
+ return paths.length > 0 ? paths : ["."];
15487
+ }
15488
+ function filesystemWriteOperands(head, args) {
15489
+ const positional = args.filter((arg) => arg && !arg.startsWith("-"));
15490
+ switch (head) {
15491
+ case "touch":
15492
+ case "mkdir":
15493
+ case "rm":
15494
+ case "truncate":
15495
+ case "chmod":
15496
+ return positional;
15497
+ case "cp":
15498
+ case "mv":
15499
+ return positional.length > 0 ? [positional[positional.length - 1] ?? ""] : [];
15500
+ case "tee":
15501
+ return positional;
15502
+ default:
15503
+ return null;
15504
+ }
15505
+ }
15506
+
15507
+ // src/core/effect-ir/shell-lower/decoders/prisma.ts
15508
+ function decodePrisma(args, env, repoRoot, segment) {
15509
+ const processEffect = processRequirement("prisma", "spawn", segment, ["process.prisma"]);
15510
+ if (args[0] === "generate") {
15511
+ return [
15512
+ processEffect,
15513
+ requirement2("fs.write", "fs.write", { kind: "path", path: repoRoot }, segment, [
15514
+ "prisma.generate.repo_local"
15515
+ ])
15516
+ ];
15517
+ }
15518
+ const databaseMutation = args[0] === "migrate" || args[0] === "db" && args[1] === "seed" || args[0] === "seed";
15519
+ if (!databaseMutation) {
15520
+ return [
15521
+ processEffect,
15522
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
15523
+ "prisma.grammar_incomplete"
15524
+ ])
15525
+ ];
15526
+ }
15527
+ const endpoint = databaseEndpoint(env.DATABASE_URL);
15528
+ if (!endpoint) {
15529
+ return [
15530
+ processEffect,
15531
+ requirement2(
15532
+ "network.connect",
15533
+ "network.connect",
15534
+ {
15535
+ kind: "network",
15536
+ host: "unknown",
15537
+ protocol: "database",
15538
+ mode: "ambiguous",
15539
+ payload: "present"
15540
+ },
15541
+ segment,
15542
+ ["prisma.database_endpoint_unknown"]
15543
+ ),
15544
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
15545
+ "prisma.database_endpoint_unknown"
15546
+ ])
15547
+ ];
15548
+ }
15549
+ const network = requirement2(
15550
+ "network.connect",
15551
+ "network.connect",
15552
+ {
15553
+ kind: "network",
15554
+ ...endpoint,
15555
+ mode: "mutate",
15556
+ payload: "present"
15557
+ },
15558
+ segment,
15559
+ [LOOPBACK_HOSTS2.has(endpoint.host) ? "prisma.database_local" : "prisma.database_remote"]
15560
+ );
15561
+ if (LOOPBACK_HOSTS2.has(endpoint.host)) {
15562
+ return [processEffect, network];
15563
+ }
15564
+ return [
15565
+ processEffect,
15566
+ network,
15567
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
15568
+ "prisma.remote_database_mutation"
15569
+ ])
15570
+ ];
15571
+ }
15572
+
15573
+ // src/core/effect-ir/shell-lower/decoders/ruby.ts
15574
+ init_path_utils();
15575
+ import path45 from "node:path";
15576
+ var RAILS_READ_ONLY_SUBCOMMANDS = /* @__PURE__ */ new Set(["routes", "middleware", "stats", "about", "version"]);
15577
+ function railsReadOnlySubcommand(args) {
15578
+ const subcommand = args[0];
15579
+ if (!subcommand || subcommand.startsWith("-")) {
15580
+ return false;
15581
+ }
15582
+ return RAILS_READ_ONLY_SUBCOMMANDS.has(subcommand);
15583
+ }
15584
+ function isRubyTestScript(scriptPath) {
15585
+ const base = path45.basename(scriptPath);
15586
+ return base.endsWith("_test.rb") || base.endsWith("_spec.rb");
15587
+ }
15588
+ function parseRubyTestInvocation(args) {
15589
+ const includePaths = [];
15590
+ for (let index = 0; index < args.length; index += 1) {
15591
+ const arg = args[index] ?? "";
15592
+ if (arg === "-e" || arg === "-r") {
15593
+ return null;
15594
+ }
15595
+ if (arg === "-I") {
15596
+ const includePath = args[index + 1];
15597
+ if (!includePath) {
15598
+ return null;
15599
+ }
15600
+ includePaths.push(includePath);
15601
+ index += 1;
15602
+ continue;
15603
+ }
15604
+ if (arg.startsWith("-I") && arg.length > 2) {
15605
+ includePaths.push(arg.slice(2));
15606
+ continue;
15607
+ }
15608
+ if (arg.startsWith("-")) {
15609
+ if (arg === "-n") {
15610
+ if (!args[index + 1]) {
15611
+ return null;
15612
+ }
15613
+ index += 1;
15614
+ continue;
15615
+ }
15616
+ if (arg.startsWith("-n")) {
15617
+ continue;
15618
+ }
15619
+ return null;
15620
+ }
15621
+ if (isRubyTestScript(arg)) {
15622
+ return { includePaths, scriptPath: arg };
15623
+ }
15624
+ return null;
15625
+ }
15626
+ return null;
15627
+ }
15628
+ function isRubocopMutating(args) {
15629
+ return args.some(
15630
+ (arg) => arg === "-A" || arg === "-a" || arg === "--auto-correct" || arg === "--autocorrect" || arg.startsWith("--auto-correct-all") || arg.startsWith("--autocorrect-all")
15631
+ );
15632
+ }
15633
+ function decodeBundleExecInner(innerHead, innerArgs, segment) {
15634
+ const innerBase = executableBaseName(innerHead);
15635
+ if (innerBase === "rubocop") {
15636
+ const mutating = isRubocopMutating(innerArgs);
15637
+ return [
15638
+ processRequirement(
15639
+ innerHead,
15640
+ mutating ? "spawn" : "inspect",
15641
+ segment,
15642
+ mutating ? ["process.linter.mutating"] : ["process.inspect.linter"]
15643
+ )
15644
+ ];
15645
+ }
15646
+ if (innerBase === "rspec") {
15647
+ const targetArgs = innerArgs.filter((arg) => !arg.startsWith("-"));
15648
+ if (targetArgs.length === 0) {
15649
+ return null;
15650
+ }
15651
+ return [processRequirement(innerHead, "spawn", segment, ["process.test_runner.rspec"])];
15652
+ }
15653
+ return null;
15645
15654
  }
15646
- function canonicalRmOperand(targetPath, finalOperandIsSymlink) {
15647
- try {
15648
- if (finalOperandIsSymlink) {
15649
- return path41.join(realpathSync4.native(path41.dirname(targetPath)), path41.basename(targetPath));
15655
+ function decodeRuby(args, cwd, repoRoot, segment) {
15656
+ const parsed = parseRubyTestInvocation(args);
15657
+ if (!parsed) {
15658
+ return unsupportedProcess("ruby", segment, "process.ruby_grammar_incomplete");
15659
+ }
15660
+ const scriptPath = resolvePathOperand(parsed.scriptPath, cwd);
15661
+ if (!pathWithinRoot(canonicalPath(repoRoot), canonicalPath(scriptPath))) {
15662
+ return unsupportedProcess("ruby", segment, "process.ruby_outside_repo");
15663
+ }
15664
+ for (const includePath of parsed.includePaths) {
15665
+ const resolvedInclude = resolvePathOperand(includePath, cwd);
15666
+ if (!pathWithinRoot(canonicalPath(repoRoot), canonicalPath(resolvedInclude))) {
15667
+ return unsupportedProcess("ruby", segment, "process.ruby_outside_repo");
15650
15668
  }
15651
- return realpathSync4.native(targetPath);
15652
- } catch {
15653
- return path41.resolve(targetPath);
15654
15669
  }
15670
+ const lowered = [
15671
+ processRequirement("ruby", "spawn", segment, ["process.test_runner.minitest"]),
15672
+ requirement2("fs.read", "fs.read", { kind: "path", path: scriptPath }, segment, [
15673
+ "ruby.minitest_script_read"
15674
+ ])
15675
+ ];
15676
+ for (const includePath of parsed.includePaths) {
15677
+ lowered.push(
15678
+ requirement2(
15679
+ "fs.read",
15680
+ "fs.read",
15681
+ { kind: "path", path: resolvePathOperand(includePath, cwd) },
15682
+ segment,
15683
+ ["ruby.minitest_load_path_read"]
15684
+ )
15685
+ );
15686
+ }
15687
+ return lowered;
15655
15688
  }
15656
- function isSymbolicLink(targetPath) {
15657
- try {
15658
- return lstatSync2(targetPath).isSymbolicLink();
15659
- } catch {
15660
- return false;
15689
+ function decodeRuntimeMetadataProcess(head, args, segment) {
15690
+ if (head === "bundle") {
15691
+ if (args.length === 1 && isMetadataOnlyArgv(args)) {
15692
+ return [processRequirement(head, "inspect", segment, ["process.inspect.runtime_metadata"])];
15693
+ }
15694
+ if (args[0] === "exec" && args.length >= 2) {
15695
+ const innerHead = args[1] ?? "";
15696
+ const innerArgs = args.slice(2);
15697
+ const innerBase = executableBaseName(innerHead);
15698
+ if ((innerBase === "rails" || innerBase === "rake") && railsReadOnlySubcommand(innerArgs)) {
15699
+ return [
15700
+ processRequirement(innerHead, "inspect", segment, ["process.inspect.rails_read_only"])
15701
+ ];
15702
+ }
15703
+ if (isMetadataOnlyArgv(innerArgs)) {
15704
+ return [
15705
+ processRequirement(innerHead, "inspect", segment, [
15706
+ "process.inspect.bundle_exec_metadata"
15707
+ ])
15708
+ ];
15709
+ }
15710
+ const bundleExecInner = decodeBundleExecInner(innerHead, innerArgs, segment);
15711
+ if (bundleExecInner) {
15712
+ return bundleExecInner;
15713
+ }
15714
+ }
15715
+ return null;
15716
+ }
15717
+ if ((head === "ruby" || head === "yarn") && args.length >= 1 && isMetadataOnlyArgv(args)) {
15718
+ return [processRequirement(head, "inspect", segment, ["process.inspect.runtime_metadata"])];
15719
+ }
15720
+ if (head === "make" && args.includes("-n")) {
15721
+ return [processRequirement(head, "inspect", segment, ["process.inspect.make_dry_run"])];
15722
+ }
15723
+ const base = executableBaseName(head);
15724
+ if ((base === "rails" || base === "rake") && railsReadOnlySubcommand(args)) {
15725
+ return [processRequirement(head, "inspect", segment, ["process.inspect.rails_read_only"])];
15661
15726
  }
15727
+ return null;
15662
15728
  }
15663
- function pathContains(ancestor, candidate) {
15664
- const relative = path41.relative(path41.resolve(ancestor), path41.resolve(candidate));
15665
- return relative === "" || !relative.startsWith("..") && !path41.isAbsolute(relative);
15729
+
15730
+ // src/core/effect-ir/shell-lower/decoders/toolchain.ts
15731
+ function decodeTsc(args, cwd, segment) {
15732
+ const metadataOnly = args.length > 0 && args.every((arg) => ["--help", "--version", "-h", "-v"].includes(arg));
15733
+ const requirements = [];
15734
+ let hasOutput = false;
15735
+ for (let index = 0; index < args.length; index += 1) {
15736
+ const arg = args[index] ?? "";
15737
+ const inline = arg.match(/^--(?:outDir|outFile)=(.+)$/)?.[1];
15738
+ const separate = (arg === "--outDir" || arg === "--outFile") && args[index + 1] ? args[index + 1] : null;
15739
+ const output = inline ?? separate;
15740
+ if (!output) {
15741
+ continue;
15742
+ }
15743
+ hasOutput = true;
15744
+ addWriteEffects(requirements, resolvePathOperand(output, cwd), segment, ["tsc.output"]);
15745
+ if (separate) {
15746
+ index += 1;
15747
+ }
15748
+ }
15749
+ const typecheckOnly = args.includes("--noEmit") && !hasOutput;
15750
+ const inspect = metadataOnly || typecheckOnly;
15751
+ requirements.unshift(
15752
+ processRequirement("tsc", inspect ? "inspect" : "spawn", segment, [
15753
+ metadataOnly ? "process.inspect.tsc_metadata" : typecheckOnly ? "process.inspect.tsc_typecheck" : "process.known_local_spawn"
15754
+ ])
15755
+ );
15756
+ return requirements;
15666
15757
  }
15667
15758
  function decodeGo(args, segment) {
15668
15759
  if (["test", "list", "vet"].includes(args[0] ?? "")) {
@@ -15793,316 +15884,351 @@ function decodeSed(args, cwd, segment) {
15793
15884
  }
15794
15885
  files.push(arg);
15795
15886
  }
15796
- const operation = inline ? "spawn" : "inspect";
15797
- const lowered = [processRequirement("sed", operation, segment, ["process.sed"])];
15798
- for (const file of files) {
15799
- lowered.push(
15800
- requirement2(
15801
- inline ? "fs.write" : "fs.read",
15802
- inline ? "fs.write" : "fs.read",
15803
- { kind: "path", path: resolvePathOperand(file, cwd) },
15804
- segment,
15805
- [inline ? "sed.in_place_write" : "sed.file_read"]
15806
- )
15807
- );
15887
+ const operation = inline ? "spawn" : "inspect";
15888
+ const lowered = [processRequirement("sed", operation, segment, ["process.sed"])];
15889
+ for (const file of files) {
15890
+ lowered.push(
15891
+ requirement2(
15892
+ inline ? "fs.write" : "fs.read",
15893
+ inline ? "fs.write" : "fs.read",
15894
+ { kind: "path", path: resolvePathOperand(file, cwd) },
15895
+ segment,
15896
+ [inline ? "sed.in_place_write" : "sed.file_read"]
15897
+ )
15898
+ );
15899
+ }
15900
+ return lowered;
15901
+ }
15902
+ function decodeNode2(args, cwd, segment) {
15903
+ if (args.length > 0 && args.every((arg) => ["--help", "--version", "-h", "-v"].includes(arg))) {
15904
+ return [processRequirement("node", "inspect", segment, ["process.inspect.node_metadata"])];
15905
+ }
15906
+ if ((args[0] === "--check" || args[0] === "-c") && args.length === 2 && args[1]) {
15907
+ return [
15908
+ processRequirement("node", "inspect", segment, ["process.inspect.node_syntax"]),
15909
+ requirement2(
15910
+ "fs.read",
15911
+ "fs.read",
15912
+ { kind: "path", path: resolvePathOperand(args[1], cwd) },
15913
+ segment,
15914
+ ["node.syntax_source_read"]
15915
+ )
15916
+ ];
15917
+ }
15918
+ return unsupportedProcess("node", segment, "process.node_grammar_incomplete");
15919
+ }
15920
+
15921
+ // src/core/effect-ir/shell-lower/decode-process.ts
15922
+ function decodeProcessOrFilesystem(params) {
15923
+ const { tokens, head, env, cwd, repoRoot, segment } = params;
15924
+ const args = tokens.slice(1);
15925
+ if (tokens.length > 0 && tokens.every((token) => ENV_PREFIX_PATTERN2.test(token))) {
15926
+ return [];
15927
+ }
15928
+ const shellControl = decodeShellControlBuiltin(head, args);
15929
+ if (shellControl) {
15930
+ return shellControl;
15931
+ }
15932
+ if (isCommandInspection(tokens)) {
15933
+ return [processRequirement(head, "inspect", segment, ["process.inspect.command_lookup"])];
15934
+ }
15935
+ if (head === "lsof") {
15936
+ return validLsof(args) ? [processRequirement(head, "inspect", segment, ["process.inspect.lsof"])] : unsupportedProcess(head, segment, "process.lsof_grammar_incomplete");
15937
+ }
15938
+ if (head === "ps") {
15939
+ return validPs(args) ? [processRequirement(head, "inspect", segment, ["process.inspect.ps"])] : unsupportedProcess(head, segment, "process.ps_grammar_incomplete");
15940
+ }
15941
+ if (head === "test" || head === "[") {
15942
+ return validFilesystemTest(head, args) ? [processRequirement(head, "inspect", segment, ["process.inspect.filesystem_test"])] : unsupportedProcess(head, segment, "process.filesystem_test_grammar_incomplete");
15943
+ }
15944
+ if (head === "docker") {
15945
+ if (args[0] === "info") {
15946
+ return validDockerInfo(args.slice(1)) ? [processRequirement(head, "inspect", segment, ["process.inspect.docker_info"])] : unsupportedProcess(head, segment, "process.docker_info_grammar_incomplete");
15947
+ }
15948
+ if (args[0] === "compose" && ["up", "down", "start", "stop", "restart", "create", "rm"].includes(args[1] ?? "")) {
15949
+ return [processRequirement(head, "spawn", segment, ["service.local_mutation"])];
15950
+ }
15951
+ if (args[0] === "push" || args[0] === "build" && args.includes("--push") || args[0] === "buildx" && args[1] === "build" && (args.includes("--push") || args.some(
15952
+ (arg) => arg.startsWith("--output=type=registry") || arg.startsWith("-o=type=registry")
15953
+ ))) {
15954
+ return [
15955
+ processRequirement(head, "spawn", segment, ["tier0_external", "docker.remote_mutation"]),
15956
+ requirement2(
15957
+ "network.connect",
15958
+ "network.connect",
15959
+ {
15960
+ kind: "network",
15961
+ host: "registry",
15962
+ protocol: "container-registry",
15963
+ mode: "mutate",
15964
+ payload: "present"
15965
+ },
15966
+ segment,
15967
+ ["tier0_external", "docker.remote_mutation"]
15968
+ )
15969
+ ];
15970
+ }
15971
+ if (args[0] === "run" || args[0] === "create" || args[0] === "pull" || args[0] === "build" || args[0] === "buildx" && args[1] === "build") {
15972
+ return [
15973
+ processRequirement(head, "spawn", segment, ["process.docker_spawn"]),
15974
+ requirement2(
15975
+ "network.acquire",
15976
+ "network.connect",
15977
+ {
15978
+ kind: "network",
15979
+ host: "unknown",
15980
+ protocol: "container-registry",
15981
+ mode: "ambiguous",
15982
+ payload: "none"
15983
+ },
15984
+ segment,
15985
+ ["docker.image_acquisition_possible"]
15986
+ )
15987
+ ];
15988
+ }
15989
+ return [processRequirement(head, "spawn", segment, ["process.docker_spawn"])];
15990
+ }
15991
+ if (isShellHead(head)) {
15992
+ const syntax = bashSyntaxTarget(args);
15993
+ if (syntax) {
15994
+ return [
15995
+ processRequirement(head, "inspect", segment, ["process.inspect.shell_syntax"]),
15996
+ requirement2(
15997
+ "fs.read",
15998
+ "fs.read",
15999
+ { kind: "path", path: path46.resolve(cwd, syntax) },
16000
+ segment,
16001
+ ["shell.syntax_source_read"]
16002
+ )
16003
+ ];
16004
+ }
16005
+ return unsupportedProcess(head, segment, "process.shell_grammar_incomplete");
16006
+ }
16007
+ if (head === "prisma") {
16008
+ return decodePrisma(args, env, repoRoot, segment);
16009
+ }
16010
+ const runtimeMetadata = decodeRuntimeMetadataProcess(head, args, segment);
16011
+ if (runtimeMetadata) {
16012
+ return runtimeMetadata;
16013
+ }
16014
+ if (head === "ruby") {
16015
+ return decodeRuby(args, cwd, repoRoot, segment);
16016
+ }
16017
+ if (head === "rubocop" || head === "rspec") {
16018
+ const decoded = decodeBundleExecInner(head, args, segment);
16019
+ if (decoded) {
16020
+ return decoded;
16021
+ }
16022
+ }
16023
+ const dockerCompose = decodeDockerComposeRun2(head, args, segment);
16024
+ if (dockerCompose) {
16025
+ return dockerCompose;
16026
+ }
16027
+ if ((head === "npm" || head === "pnpm") && args.length === 1 && isMetadataOnlyArgv(args)) {
16028
+ return [processRequirement(head, "inspect", segment, ["process.inspect.package_manager"])];
16029
+ }
16030
+ if (head === "belay") {
16031
+ return decodeBelay(args, repoRoot, segment);
16032
+ }
16033
+ if (head === "tsc") {
16034
+ return decodeTsc(args, cwd, segment);
15808
16035
  }
15809
- return lowered;
15810
- }
15811
- function decodeNode2(args, cwd, segment) {
15812
- if (args.length > 0 && args.every((arg) => ["--help", "--version", "-h", "-v"].includes(arg))) {
15813
- return [processRequirement("node", "inspect", segment, ["process.inspect.node_metadata"])];
16036
+ if (head === "go") {
16037
+ return decodeGo(args, segment);
15814
16038
  }
15815
- if ((args[0] === "--check" || args[0] === "-c") && args.length === 2 && args[1]) {
15816
- return [
15817
- processRequirement("node", "inspect", segment, ["process.inspect.node_syntax"]),
15818
- requirement2(
15819
- "fs.read",
15820
- "fs.read",
15821
- { kind: "path", path: resolvePathOperand(args[1], cwd) },
15822
- segment,
15823
- ["node.syntax_source_read"]
15824
- )
15825
- ];
16039
+ if (head === "rsync") {
16040
+ return decodeRsync(args, cwd, segment);
15826
16041
  }
15827
- return unsupportedProcess("node", segment, "process.node_grammar_incomplete");
15828
- }
15829
- function decodePrisma(args, env, repoRoot, segment) {
15830
- const processEffect = processRequirement("prisma", "spawn", segment, ["process.prisma"]);
15831
- if (args[0] === "generate") {
15832
- return [
15833
- processEffect,
15834
- requirement2("fs.write", "fs.write", { kind: "path", path: repoRoot }, segment, [
15835
- "prisma.generate.repo_local"
15836
- ])
15837
- ];
16042
+ if (head === "sed") {
16043
+ return decodeSed(args, cwd, segment);
15838
16044
  }
15839
- const databaseMutation = args[0] === "migrate" || args[0] === "db" && args[1] === "seed" || args[0] === "seed";
15840
- if (!databaseMutation) {
15841
- return [
15842
- processEffect,
15843
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
15844
- "prisma.grammar_incomplete"
15845
- ])
15846
- ];
16045
+ if (head === "base64" && args.length === 1 && ["-d", "-D", "--decode"].includes(args[0] ?? "")) {
16046
+ return [processRequirement(head, "inspect", segment, ["process.inspect.base64_stdin"])];
15847
16047
  }
15848
- const endpoint = databaseEndpoint(env.DATABASE_URL);
15849
- if (!endpoint) {
16048
+ if (head === "node") {
16049
+ return decodeNode2(args, cwd, segment);
16050
+ }
16051
+ if (head === "vite" || head === "vite-node") {
16052
+ return [processRequirement(head, "spawn", segment, ["process.local_dev_spawn"])];
16053
+ }
16054
+ if ((head === "npm" || head === "pnpm") && args[0] === "publish" || head === "terraform" && args[0] === "apply") {
15850
16055
  return [
15851
- processEffect,
16056
+ processRequirement(head, "spawn", segment, ["tier0_external"]),
15852
16057
  requirement2(
15853
16058
  "network.connect",
15854
16059
  "network.connect",
15855
16060
  {
15856
16061
  kind: "network",
15857
- host: "unknown",
15858
- protocol: "database",
15859
- mode: "ambiguous",
16062
+ host: "control-plane",
16063
+ protocol: head,
16064
+ mode: "mutate",
15860
16065
  payload: "present"
15861
16066
  },
15862
16067
  segment,
15863
- ["prisma.database_endpoint_unknown"]
15864
- ),
15865
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
15866
- "prisma.database_endpoint_unknown"
15867
- ])
16068
+ ["tier0_external"]
16069
+ )
15868
16070
  ];
15869
16071
  }
15870
- const network = requirement2(
15871
- "network.connect",
15872
- "network.connect",
15873
- {
15874
- kind: "network",
15875
- ...endpoint,
15876
- mode: "mutate",
15877
- payload: "present"
15878
- },
15879
- segment,
15880
- [LOOPBACK_HOSTS2.has(endpoint.host) ? "prisma.database_local" : "prisma.database_remote"]
15881
- );
15882
- if (LOOPBACK_HOSTS2.has(endpoint.host)) {
15883
- return [processEffect, network];
15884
- }
15885
- return [
15886
- processEffect,
15887
- network,
15888
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
15889
- "prisma.remote_database_mutation"
15890
- ])
15891
- ];
15892
- }
15893
- function addRedirectEffects(requirements, tokens, env, context, segment) {
15894
- for (let index = 0; index < tokens.length; index += 1) {
15895
- const operator = tokens[index] ?? "";
15896
- if (!isRedirectOperator(operator) || isFdDuplication(operator)) {
15897
- continue;
15898
- }
15899
- const rawTarget = tokens[index + 1];
15900
- if (!rawTarget) {
15901
- requirements.push(
15902
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
15903
- "shell.redirect_target_missing"
15904
- ])
15905
- );
15906
- continue;
15907
- }
15908
- if (operator.includes("<<")) {
15909
- requirements.push(
15910
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
15911
- "shell.heredoc_opaque"
15912
- ])
15913
- );
15914
- index += 1;
15915
- continue;
15916
- }
15917
- const target = expandKnownVariables(rawTarget, env);
15918
- const resolved = resolvePathOperand(target, context.cwd);
15919
- if (resolved === "/dev/null") {
15920
- index += 1;
15921
- continue;
15922
- }
15923
- if (operator.includes("<")) {
15924
- requirements.push(
15925
- requirement2("fs.read", "fs.read", { kind: "path", path: resolved }, segment, [
15926
- "shell.input_redirect"
15927
- ])
15928
- );
15929
- addSecretRead(requirements, resolved, segment);
15930
- }
15931
- if (operator.includes(">")) {
15932
- addWriteEffects(requirements, resolved, segment, ["shell.output_redirect"]);
15933
- }
15934
- index += 1;
15935
- }
15936
- }
15937
- function addSubstitutionEffects(requirements, command, context, segment, signals) {
15938
- const inners = structuralSubstitutionInners(command);
15939
- for (const inner of inners) {
15940
- signals.add("command_substitution");
15941
- if (context.depth >= MAX_LOWER_DEPTH) {
15942
- requirements.push(
15943
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
15944
- "shell.substitution_depth_exceeded"
15945
- ])
15946
- );
15947
- continue;
15948
- }
15949
- const nestedSegments = lowerTopLevelSegments(inner, {
15950
- ...context,
15951
- command: inner,
15952
- depth: context.depth + 1
15953
- });
15954
- for (const nested of nestedSegments) {
15955
- requirements.push(
15956
- ...nested.requirements.map((entry) => {
15957
- const outerProvenance = {
15958
- segment,
15959
- innerCommand: inner
15960
- };
15961
- const provenance = {
15962
- ...entry.provenance,
15963
- segment,
15964
- innerCommand: entry.provenance.innerCommand ?? inner
15965
- };
15966
- return withProvenances(entry, provenance, outerProvenance);
15967
- })
15968
- );
15969
- }
15970
- }
15971
- }
15972
- function extractEnvironment(tokens, inherited) {
15973
- const env = { ...inherited };
15974
- const changedNames = /* @__PURE__ */ new Set();
15975
- if (tokens[0] === "env") {
15976
- let index = 1;
15977
- let malformed = false;
15978
- while (index < tokens.length) {
15979
- const option = tokens[index] ?? "";
15980
- if (option === "-u" || option === "--unset") {
15981
- const name = tokens[index + 1] ?? "";
15982
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
15983
- malformed = true;
15984
- index += 1;
15985
- break;
15986
- }
15987
- delete env[name];
15988
- changedNames.add(name);
15989
- index += 2;
15990
- continue;
15991
- }
15992
- if (option.startsWith("--unset=")) {
15993
- const name = option.slice("--unset=".length);
15994
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
15995
- malformed = true;
15996
- index += 1;
15997
- break;
15998
- }
15999
- delete env[name];
16000
- changedNames.add(name);
16001
- index += 1;
16002
- continue;
16003
- }
16004
- if (option === "-i" || option === "--ignore-environment") {
16005
- for (const name of Object.keys(env)) {
16006
- delete env[name];
16007
- }
16008
- changedNames.add("*");
16009
- index += 1;
16010
- continue;
16011
- }
16012
- if (option === "--") {
16013
- index += 1;
16014
- break;
16015
- }
16016
- if (option.startsWith("-")) {
16017
- malformed = true;
16018
- index += 1;
16019
- break;
16020
- }
16021
- break;
16022
- }
16023
- while (index < tokens.length) {
16024
- const token = tokens[index] ?? "";
16025
- const match = ENV_PREFIX_PATTERN2.exec(token);
16026
- if (!match) {
16027
- break;
16028
- }
16029
- const [, name, value] = match;
16030
- if (name) {
16031
- env[name] = expandKnownVariables(value ?? "", env);
16032
- changedNames.add(name);
16033
- }
16034
- index += 1;
16035
- }
16036
- const commandTokens = tokens.slice(index);
16037
- return {
16038
- env,
16039
- commandTokens,
16040
- malformed: malformed || commandTokens.length === 0,
16041
- changedNames
16042
- };
16072
+ if (head === "cd") {
16073
+ return [];
16043
16074
  }
16044
- for (const token of tokens) {
16045
- const match = ENV_PREFIX_PATTERN2.exec(token);
16046
- if (!match) {
16047
- break;
16075
+ if (head === "cp" || head === "mv") {
16076
+ return decodeCopyMove(head, args, cwd, segment);
16077
+ }
16078
+ if (head === "rm") {
16079
+ return decodeRm(args, cwd, repoRoot, segment);
16080
+ }
16081
+ if (head === "vitest" || head === "eslint" || head === "biome" || head === "belay") {
16082
+ return [processRequirement(head, "spawn", segment, ["process.known_local_spawn"])];
16083
+ }
16084
+ if (head === "pwd" || head === "which" || head === "whoami") {
16085
+ return [processRequirement(head, "inspect", segment, ["process.pure_inspection"])];
16086
+ }
16087
+ const readOperands = filesystemReadOperands(head, args);
16088
+ if (readOperands !== null) {
16089
+ const lowered = [processRequirement(head, "inspect", segment, ["process.filesystem_inspect"])];
16090
+ for (const operand of readOperands) {
16091
+ const resolved = resolvePathOperand(operand, cwd);
16092
+ lowered.push(
16093
+ requirement2("fs.read", "fs.read", { kind: "path", path: resolved }, segment, [
16094
+ "filesystem.read"
16095
+ ])
16096
+ );
16097
+ addSecretRead(lowered, resolved, segment);
16048
16098
  }
16049
- const [, name, value] = match;
16050
- if (name) {
16051
- env[name] = expandKnownVariables(value ?? "", env);
16052
- changedNames.add(name);
16099
+ return lowered;
16100
+ }
16101
+ const writeOperands = filesystemWriteOperands(head, args);
16102
+ if (writeOperands !== null) {
16103
+ const lowered = [processRequirement(head, "spawn", segment, ["process.filesystem_mutation"])];
16104
+ for (const operand of writeOperands) {
16105
+ addWriteEffects(lowered, resolvePathOperand(operand, cwd), segment, ["filesystem.write"]);
16053
16106
  }
16107
+ return lowered;
16054
16108
  }
16055
- return { env, malformed: false, changedNames };
16109
+ if (head === "printf" || head === "echo" || head === "true" || head === "false" || head === ":") {
16110
+ return [];
16111
+ }
16112
+ if (!head) {
16113
+ return [
16114
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
16115
+ "shell.segment_empty"
16116
+ ])
16117
+ ];
16118
+ }
16119
+ return unsupportedProcess(head, segment, "process.grammar_unknown");
16056
16120
  }
16057
- function expandKnownVariables(token, env) {
16058
- return token.replace(
16059
- /\$(?:\{([A-Za-z_][A-Za-z0-9_]*)\}|([A-Za-z_][A-Za-z0-9_]*))/g,
16060
- (raw, a, b) => {
16061
- const value = env[a ?? b];
16062
- return value === void 0 ? raw : value;
16121
+
16122
+ // src/core/effect-ir/shell-lower/decoders/decode-package-exec.ts
16123
+ function decodePackageExec(peel, cwd, repoRoot, segment) {
16124
+ if (peel.opaque) {
16125
+ return [
16126
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
16127
+ peel.reason,
16128
+ ...peel.signals
16129
+ ])
16130
+ ];
16131
+ }
16132
+ if (peel.reason === "npx_wrapper_readonly") {
16133
+ return [];
16134
+ }
16135
+ const binHead = peel.innerTokens[0] ?? "";
16136
+ const metadataOnly = packageExecInnerIsMetadata(peel);
16137
+ const local = !peel.forceAcquire ? resolveLocalBin(binHead, cwd, repoRoot) : null;
16138
+ if (local) {
16139
+ return metadataOnly ? [
16140
+ requirement2(
16141
+ "process.exec",
16142
+ "process.exec",
16143
+ { kind: "executable", command: local.path, operation: "inspect" },
16144
+ segment,
16145
+ ["package_exec.delegate", "package_exec.local_bin_resolved"]
16146
+ )
16147
+ ] : [];
16148
+ }
16149
+ const networkResources = peel.acquisitionSpecs.map((spec) => classifyPackageAcquisitionSpec(spec)).filter((source) => source.kind !== "local").map(
16150
+ (source) => source.kind === "registry" ? {
16151
+ kind: "network",
16152
+ host: "registry.npmjs.org",
16153
+ protocol: "registry",
16154
+ mode: "read",
16155
+ payload: "none"
16156
+ } : {
16157
+ kind: "network",
16158
+ host: source.host,
16159
+ ...source.port ? { port: source.port } : {},
16160
+ protocol: source.protocol,
16161
+ mode: "read",
16162
+ payload: "none"
16063
16163
  }
16064
16164
  );
16065
- }
16066
- function stripRedirects(tokens) {
16067
- const stripped = [];
16068
- for (let index = 0; index < tokens.length; index += 1) {
16069
- const token = tokens[index] ?? "";
16070
- if (isFdDuplication(token)) {
16071
- continue;
16072
- }
16073
- if (!isRedirectOperator(token)) {
16074
- stripped.push(token);
16075
- continue;
16076
- }
16077
- index += 1;
16165
+ if (networkResources.length === 0) {
16166
+ networkResources.push({
16167
+ kind: "network",
16168
+ host: "registry.npmjs.org",
16169
+ protocol: "registry",
16170
+ mode: "read",
16171
+ payload: "none"
16172
+ });
16078
16173
  }
16079
- return stripped;
16174
+ return [
16175
+ ...networkResources.map(
16176
+ (resource) => requirement2("network.acquire", "network.connect", resource, segment, [
16177
+ "package_acquire_possible"
16178
+ ])
16179
+ ),
16180
+ requirement2(
16181
+ "fs.write",
16182
+ "fs.write",
16183
+ { kind: "package-cache", manager: peel.launcher === "pnpm" ? "pnpm" : "npm" },
16184
+ segment,
16185
+ ["package_cache_write"]
16186
+ ),
16187
+ ...metadataOnly ? [
16188
+ requirement2(
16189
+ "process.exec",
16190
+ "process.exec",
16191
+ { kind: "executable", command: binHead || peel.launcher, operation: "inspect" },
16192
+ segment,
16193
+ ["package_exec.delegate"]
16194
+ )
16195
+ ] : []
16196
+ ];
16080
16197
  }
16081
- function stripStructuredRedirects(tokens) {
16082
- const stripped = [];
16083
- for (let index = 0; index < tokens.length; index += 1) {
16084
- const token = tokens[index];
16085
- if (!token) continue;
16086
- if (isFdDuplication(token.value)) {
16087
- continue;
16088
- }
16089
- if (!isRedirectOperator(token.value)) {
16090
- stripped.push(token);
16091
- continue;
16092
- }
16093
- index += 1;
16198
+ function packageExecInnerIsMetadata(peel) {
16199
+ const args = peel.innerTokens.slice(1);
16200
+ return args.length > 0 && args.every((arg) => ["--help", "--version", "-V", "-h", "-v"].includes(arg));
16201
+ }
16202
+
16203
+ // src/core/effect-ir/shell-lower/segment.ts
16204
+ init_shell_tokenizer();
16205
+ import path47 from "node:path";
16206
+ var DYNAMIC_SHELL_VALUE_PATTERN = /(?:\$\(|`|\$(?:\d+|[@*#?$!-]|\{[^}]*\}|[A-Za-z_][A-Za-z0-9_]*))/;
16207
+ var SHELL_GLOB_PATTERN = /[*?[]/;
16208
+ function requiresKnownCwd(requirementValue) {
16209
+ if (requirementValue.action === "fs.write" || requirementValue.action === "git.ref.write" || requirementValue.action === "control_plane.write") {
16210
+ return true;
16094
16211
  }
16095
- return stripped;
16212
+ return requirementValue.action === "process.exec" && requirementValue.resource.kind === "executable" && requirementValue.resource.operation === "spawn";
16096
16213
  }
16097
- function alignStructuredTokens(tokens, values) {
16098
- if (values.length === 0) return [];
16099
- for (let start = tokens.length - values.length; start >= 0; start -= 1) {
16100
- const candidate = tokens.slice(start);
16101
- if (candidate.length === values.length && candidate.every((token, index) => token.value === values[index])) {
16102
- return candidate;
16103
- }
16214
+ function joinNestedOpacity(outer, nested) {
16215
+ const nestedOpacity = nested.completeness === "partial" && nested.opacity === "transparent" ? "recursive" : nested.opacity;
16216
+ return joinEffectOpacity(outer, nestedOpacity);
16217
+ }
16218
+ function startsLocalPostgresService(command) {
16219
+ const tokens = tokenizeShell(command);
16220
+ return path47.basename(tokens[0] ?? "") === "docker" && tokens[1] === "compose" && ["up", "start", "restart"].includes(tokens[2] ?? "") && tokens.includes("postgres");
16221
+ }
16222
+ function resolveCdTransition(command, currentCwd) {
16223
+ const tokens = tokenizeShell(command);
16224
+ if (path47.basename(tokens[0] ?? "") !== "cd") {
16225
+ return null;
16104
16226
  }
16105
- return [];
16227
+ const target = tokens[1] ?? "~";
16228
+ if (!target || target === "-" || target.includes("$") || target.includes("`")) {
16229
+ return { cwd: currentCwd, known: false };
16230
+ }
16231
+ return { cwd: resolvePathOperand(target, currentCwd), known: true };
16106
16232
  }
16107
16233
  function shellSegment(commandRedacted, segmentHead, requirements, opacity, signals) {
16108
16234
  const normalizedRequirements = requirements.flatMap((entry) => {
@@ -16117,71 +16243,21 @@ function shellSegment(commandRedacted, segmentHead, requirements, opacity, signa
16117
16243
  entry.provenance.segment ?? commandRedacted,
16118
16244
  [...entry.evidence.signals, dynamicSignal]
16119
16245
  )
16120
- ];
16121
- }
16122
- return [entry];
16123
- });
16124
- const partial = normalizedRequirements.some(
16125
- (entry) => entry.tag === "indeterminate" || entry.action === "indeterminate"
16126
- );
16127
- return {
16128
- commandRedacted,
16129
- segmentHead,
16130
- requirements: normalizedRequirements,
16131
- completeness: partial ? "partial" : "complete",
16132
- opacity,
16133
- signals: [...signals].sort()
16134
- };
16135
- }
16136
- var DYNAMIC_SHELL_VALUE_PATTERN = /(?:\$\(|`|\$(?:\d+|[@*#?$!-]|\{[^}]*\}|[A-Za-z_][A-Za-z0-9_]*))/;
16137
- var SHELL_GLOB_PATTERN = /[*?[]/;
16138
- function effectChangingEnvironmentSignals(head, env, changedNames) {
16139
- if (head === "curl" && (env.CURL_HOME || changedNames.has("CURL_HOME") || changedNames.has("HOME") || changedNames.has("XDG_CONFIG_HOME"))) {
16140
- return ["egress.curl.environment_config_override"];
16141
- }
16142
- if (head === "wget" && (env.WGETRC || changedNames.has("WGETRC") || changedNames.has("HOME"))) {
16143
- return ["egress.wget.environment_config_override"];
16144
- }
16145
- if (head !== "git") {
16146
- return [];
16147
- }
16148
- const hazardousNames = /* @__PURE__ */ new Set([
16149
- "GIT_ASKPASS",
16150
- "GIT_ALTERNATE_OBJECT_DIRECTORIES",
16151
- "GIT_CEILING_DIRECTORIES",
16152
- "GIT_COMMON_DIR",
16153
- "GIT_CONFIG",
16154
- "GIT_CONFIG_GLOBAL",
16155
- "GIT_CONFIG_SYSTEM",
16156
- "GIT_DIR",
16157
- "GIT_DISCOVERY_ACROSS_FILESYSTEM",
16158
- "GIT_EDITOR",
16159
- "GIT_EXEC_PATH",
16160
- "GIT_EXTERNAL_DIFF",
16161
- "GIT_INDEX_FILE",
16162
- "GIT_NAMESPACE",
16163
- "GIT_OBJECT_DIRECTORY",
16164
- "GIT_PAGER",
16165
- "GIT_PROXY_COMMAND",
16166
- "GIT_QUARANTINE_PATH",
16167
- "GIT_SEQUENCE_EDITOR",
16168
- "GIT_SHALLOW_FILE",
16169
- "GIT_SSH",
16170
- "GIT_SSH_COMMAND",
16171
- "GIT_TEMPLATE_DIR",
16172
- "GIT_WORK_TREE",
16173
- "SSH_ASKPASS"
16174
- ]);
16175
- const overridden = changedNames.has("HOME") || changedNames.has("XDG_CONFIG_HOME") || Object.entries(env).some(([name, value]) => {
16176
- if (!value) {
16177
- return false;
16178
- }
16179
- if (hazardousNames.has(name) || /^GIT_CONFIG_(?:KEY|VALUE)_\d+$/.test(name) || /^GIT_TRACE/.test(name)) {
16180
- return true;
16246
+ ];
16181
16247
  }
16182
- return name === "GIT_CONFIG_COUNT" && value !== "0";
16248
+ return [entry];
16183
16249
  });
16184
- return overridden ? ["git.environment_execution_or_config_override"] : [];
16250
+ const partial = normalizedRequirements.some(
16251
+ (entry) => entry.tag === "indeterminate" || entry.action === "indeterminate"
16252
+ );
16253
+ return {
16254
+ commandRedacted,
16255
+ segmentHead,
16256
+ requirements: normalizedRequirements,
16257
+ completeness: partial ? "partial" : "complete",
16258
+ opacity,
16259
+ signals: [...signals].sort()
16260
+ };
16185
16261
  }
16186
16262
  function dynamicResourceSignal(resource) {
16187
16263
  const expanded = (value) => Boolean(value && DYNAMIC_SHELL_VALUE_PATTERN.test(value));
@@ -16201,362 +16277,502 @@ function dynamicResourceSignal(resource) {
16201
16277
  return null;
16202
16278
  }
16203
16279
  }
16204
- function processRequirement(command, operation, segment, signals) {
16205
- return requirement2(
16206
- "process.exec",
16207
- "process.exec",
16208
- { kind: "executable", command, operation },
16209
- segment,
16210
- signals
16211
- );
16212
- }
16213
- function unsupportedProcess(command, segment, signal) {
16214
- return [
16215
- processRequirement(command, "spawn", segment, [signal]),
16216
- requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [signal])
16217
- ];
16280
+
16281
+ // src/core/effect-ir/shell-lower.ts
16282
+ var MAX_LOWER_DEPTH = 8;
16283
+ function lowerShellEffectPlan(params) {
16284
+ const context = { ...params, depth: 0 };
16285
+ const segments = lowerTopLevelSegments(params.command, context);
16286
+ return buildShellEffectPlan({
16287
+ inputFingerprint: params.inputFingerprint,
16288
+ segments,
16289
+ signals: pipeToShell(params.command) ? ["pipe_to_shell"] : []
16290
+ });
16218
16291
  }
16219
- function addWriteEffects(requirements, resolved, segment, signals) {
16220
- requirements.push(
16221
- requirement2("fs.write", "fs.write", { kind: "path", path: resolved }, segment, signals)
16222
- );
16223
- if (CONTROL_PLANE_PATH_PATTERN.test(resolved)) {
16224
- requirements.push(
16225
- requirement2(
16226
- "control_plane.write",
16227
- "control_plane.write",
16228
- { kind: "path", path: resolved },
16229
- segment,
16230
- [...signals, "protected_path"]
16292
+ function lowerTopLevelSegments(command, context) {
16293
+ const commands = splitStructuralShellSegments(command);
16294
+ if (commands.length === 0 && command.trim()) {
16295
+ commands.push(command.trim());
16296
+ }
16297
+ const pipedToShell = pipeToShell(command);
16298
+ const lowered = [];
16299
+ let cwd = context.cwd;
16300
+ let cwdKnown = true;
16301
+ let inferredEnv = { ...context.env };
16302
+ for (const segment of commands) {
16303
+ let result = lowerSegment(segment, {
16304
+ ...context,
16305
+ cwd,
16306
+ env: inferredEnv,
16307
+ command: segment,
16308
+ depth: context.depth,
16309
+ inputFingerprint: context.inputFingerprint,
16310
+ ...pipedToShell && isShellHead(parseSegment(segment).head) ? { pipeToShellSegment: true } : {}
16311
+ });
16312
+ if (!cwdKnown) {
16313
+ const requiresCwd = result.requirements.some(requiresKnownCwd);
16314
+ const requirements = [...result.requirements];
16315
+ if (requiresCwd) {
16316
+ requirements.push(
16317
+ requirement2(
16318
+ "indeterminate",
16319
+ "indeterminate",
16320
+ { kind: "unknown" },
16321
+ result.commandRedacted,
16322
+ ["shell.cwd_unknown"]
16323
+ )
16324
+ );
16325
+ }
16326
+ result = {
16327
+ ...result,
16328
+ requirements,
16329
+ ...requiresCwd ? {
16330
+ completeness: "partial",
16331
+ opacity: joinEffectOpacity(result.opacity, "opaque")
16332
+ } : {},
16333
+ signals: [.../* @__PURE__ */ new Set([...result.signals, "shell.cwd_unknown"])]
16334
+ };
16335
+ }
16336
+ lowered.push(result);
16337
+ if (!inferredEnv.DATABASE_URL && startsLocalPostgresService(segment)) {
16338
+ inferredEnv = {
16339
+ ...inferredEnv,
16340
+ DATABASE_URL: "postgresql://127.0.0.1:5432/local"
16341
+ };
16342
+ }
16343
+ const nextCwd = resolveCdTransition(segment, cwd);
16344
+ if (nextCwd) {
16345
+ cwd = nextCwd.cwd;
16346
+ cwdKnown = nextCwd.known;
16347
+ }
16348
+ }
16349
+ if (commands.length > 1 && /[\r\n]/.test(command) && lowered.some(
16350
+ (segment) => segment.requirements.some((entry) => entry.action === "network.connect")
16351
+ )) {
16352
+ lowered.push(
16353
+ shellSegment(
16354
+ "[multiline network boundary]",
16355
+ "shell-boundary",
16356
+ [
16357
+ requirement2(
16358
+ "indeterminate",
16359
+ "indeterminate",
16360
+ { kind: "unknown" },
16361
+ "[multiline network boundary]",
16362
+ ["shell.multiline_network_boundary"]
16363
+ )
16364
+ ],
16365
+ "opaque",
16366
+ /* @__PURE__ */ new Set(["shell.multiline_network_boundary"])
16231
16367
  )
16232
16368
  );
16233
16369
  }
16370
+ return lowered;
16234
16371
  }
16235
- function addSecretRead(requirements, resolved, segment) {
16236
- if (SECRET_PATH_PATTERN.test(resolved)) {
16372
+ function lowerSegment(command, context) {
16373
+ const commandRedacted = redactCommand(command);
16374
+ const lexed = lexShell(command);
16375
+ const rawTokens = lexed.tokens.map((token) => token.value);
16376
+ const environment = extractEnvironment(rawTokens, context.env);
16377
+ const env = environment.env;
16378
+ const parsed = parseSegment(command);
16379
+ const parsedTokens = environment.commandTokens ?? parsed.tokens;
16380
+ const tokens = stripRedirects(
16381
+ parsedTokens.length === 0 && rawTokens.length > 0 && rawTokens.every((token) => ENV_PREFIX_PATTERN2.test(token)) ? rawTokens : parsedTokens
16382
+ ).map((token) => expandKnownVariables(token, env));
16383
+ const decoderTokens = alignStructuredTokens(
16384
+ stripStructuredRedirects(lexed.tokens),
16385
+ stripRedirects(parsedTokens)
16386
+ );
16387
+ const head = path48.basename(tokens[0] ?? parsed.head);
16388
+ let opacity = segmentOpacity(command);
16389
+ const signals = /* @__PURE__ */ new Set();
16390
+ const requirements = [];
16391
+ if (!lexed.complete) {
16237
16392
  requirements.push(
16238
- requirement2("secret.read", "secret.read", { kind: "path", path: resolved }, segment, [
16239
- "secret_path_read"
16393
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
16394
+ "shell.grammar_incomplete"
16240
16395
  ])
16241
16396
  );
16397
+ signals.add("shell.grammar_incomplete");
16398
+ opacity = joinEffectOpacity(opacity, "unparseable");
16242
16399
  }
16243
- }
16244
- function filesystemReadOperands(head, args) {
16245
- switch (head) {
16246
- case "ls": {
16247
- const operands = args.filter((arg) => arg && !arg.startsWith("-"));
16248
- return operands.length > 0 ? operands : ["."];
16249
- }
16250
- case "find":
16251
- return findReadOperands(args);
16252
- case "cat":
16253
- case "head":
16254
- case "tail":
16255
- case "less":
16256
- case "stat":
16257
- case "file":
16258
- case "du":
16259
- case "wc":
16260
- return args.filter((arg) => arg && !arg.startsWith("-"));
16261
- case "grep":
16262
- case "rg":
16263
- return args.slice(1).filter((arg) => arg && !arg.startsWith("-"));
16264
- case "jq":
16265
- return args.slice(1).filter((arg) => arg && !arg.startsWith("-"));
16266
- default:
16267
- return null;
16268
- }
16269
- }
16270
- function findReadOperands(args) {
16271
- const mutatingPrimaries = /* @__PURE__ */ new Set([
16272
- "-delete",
16273
- "-exec",
16274
- "-execdir",
16275
- "-fls",
16276
- "-fprint",
16277
- "-fprintf",
16278
- "-ok",
16279
- "-okdir"
16280
- ]);
16281
- if (args.some(
16282
- (arg) => mutatingPrimaries.has(arg) || [...mutatingPrimaries].some((primary) => arg.startsWith(`${primary}=`))
16283
- )) {
16284
- return null;
16285
- }
16286
- const paths = [];
16287
- for (const arg of args) {
16288
- if (arg.startsWith("-") || arg === "!" || arg === "(") {
16289
- break;
16290
- }
16291
- paths.push(arg);
16292
- }
16293
- return paths.length > 0 ? paths : ["."];
16294
- }
16295
- function filesystemWriteOperands(head, args) {
16296
- const positional = args.filter((arg) => arg && !arg.startsWith("-"));
16297
- switch (head) {
16298
- case "touch":
16299
- case "mkdir":
16300
- case "rm":
16301
- case "truncate":
16302
- case "chmod":
16303
- return positional;
16304
- case "cp":
16305
- case "mv":
16306
- return positional.length > 0 ? [positional[positional.length - 1] ?? ""] : [];
16307
- case "tee":
16308
- return positional;
16309
- default:
16310
- return null;
16400
+ addRedirectEffects(requirements, rawTokens, env, context, commandRedacted);
16401
+ addSubstitutionEffects(requirements, command, context, commandRedacted, signals);
16402
+ if (environment.malformed) {
16403
+ requirements.push(
16404
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
16405
+ "shell.env_wrapper_incomplete"
16406
+ ])
16407
+ );
16408
+ opacity = joinEffectOpacity(opacity, "opaque");
16311
16409
  }
16312
- }
16313
- function validLsof(args) {
16314
- const flagsWithoutValues = /* @__PURE__ */ new Set(["-a", "-b", "-l", "-n", "-P", "-R", "-t", "-V"]);
16315
- const flagsWithValues = /* @__PURE__ */ new Set(["-c", "-d", "-F", "-g", "-p", "-T", "-u", "+d", "+D"]);
16316
- for (let index = 0; index < args.length; index += 1) {
16317
- const arg = args[index] ?? "";
16318
- if (flagsWithoutValues.has(arg) || arg === "-i") {
16319
- continue;
16320
- }
16321
- if (flagsWithValues.has(arg)) {
16322
- if (!args[index + 1] || isOptionToken(args[index + 1] ?? "")) {
16323
- return false;
16324
- }
16325
- index += 1;
16326
- continue;
16327
- }
16328
- 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)) {
16329
- continue;
16330
- }
16331
- return false;
16410
+ if (parsed.encounteredXargs) {
16411
+ requirements.push(
16412
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
16413
+ "shell.xargs_stdin_dynamic"
16414
+ ])
16415
+ );
16416
+ signals.add("shell.xargs_stdin_dynamic");
16417
+ opacity = joinEffectOpacity(opacity, "opaque");
16332
16418
  }
16333
- return true;
16334
- }
16335
- function validPs(args) {
16336
- const bareForms = /* @__PURE__ */ new Set(["aux", "ax", "x", "u"]);
16337
- const flagsWithoutValues = /* @__PURE__ */ new Set(["-A", "-a", "-d", "-e", "-f", "-j", "-l", "-T", "-x"]);
16338
- const flagsWithValues = /* @__PURE__ */ new Set([
16339
- "--format",
16340
- "--group",
16341
- "--pid",
16342
- "--ppid",
16343
- "--sort",
16344
- "--tty",
16345
- "--user"
16346
- ]);
16347
- for (let index = 0; index < args.length; index += 1) {
16348
- const arg = args[index] ?? "";
16349
- if (bareForms.has(arg) || flagsWithoutValues.has(arg)) {
16350
- continue;
16351
- }
16352
- if (flagsWithValues.has(arg)) {
16353
- if (!args[index + 1] || isOptionToken(args[index + 1] ?? "")) {
16354
- return false;
16355
- }
16356
- index += 1;
16357
- continue;
16419
+ if (context.depth > MAX_LOWER_DEPTH) {
16420
+ requirements.push(
16421
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
16422
+ "shell.lower_depth_exceeded"
16423
+ ])
16424
+ );
16425
+ return shellSegment(
16426
+ commandRedacted,
16427
+ head,
16428
+ requirements,
16429
+ joinEffectOpacity(opacity, "opaque"),
16430
+ signals
16431
+ );
16432
+ }
16433
+ const packageExec = peelPackageExecArgv(tokens);
16434
+ if (packageExec) {
16435
+ requirements.push(
16436
+ ...decodePackageExec(packageExec, context.cwd, context.repoRoot, commandRedacted)
16437
+ );
16438
+ for (const signal of packageExec.signals) {
16439
+ signals.add(signal);
16358
16440
  }
16359
- if (/^(?:--format|--group|--pid|--ppid|--sort|--tty|--user)=.+$/.test(arg)) {
16360
- continue;
16441
+ if (packageExec.opaque) {
16442
+ opacity = joinEffectOpacity(opacity, "opaque");
16361
16443
  }
16362
- if (/^-[^-]/.test(arg)) {
16363
- const noValue = /* @__PURE__ */ new Set(["A", "a", "d", "e", "f", "j", "l", "T", "x"]);
16364
- const withValue = /* @__PURE__ */ new Set(["G", "g", "N", "o", "p", "t", "U", "u"]);
16365
- const characters = arg.slice(1);
16366
- let valid = true;
16367
- for (let optionIndex = 0; optionIndex < characters.length; optionIndex += 1) {
16368
- const option = characters[optionIndex] ?? "";
16369
- if (noValue.has(option)) {
16370
- continue;
16371
- }
16372
- if (withValue.has(option)) {
16373
- if (optionIndex === characters.length - 1) {
16374
- if (!args[index + 1] || isOptionToken(args[index + 1] ?? "")) {
16375
- return false;
16376
- }
16377
- index += 1;
16378
- }
16379
- break;
16444
+ const innerRecipe = packageExecInnerIsMetadata(packageExec) ? null : innerRecipeFromPeel(packageExec);
16445
+ if (innerRecipe) {
16446
+ const nested = lowerTopLevelSegments(innerRecipe, {
16447
+ ...context,
16448
+ command: innerRecipe,
16449
+ env,
16450
+ depth: context.depth + 1
16451
+ });
16452
+ for (const nestedSegment of nested) {
16453
+ requirements.push(
16454
+ ...nestedSegment.requirements.map(
16455
+ (entry) => withInnerProvenance(entry, innerRecipe, packageExec.launcher, commandRedacted)
16456
+ )
16457
+ );
16458
+ for (const signal of nestedSegment.signals) {
16459
+ signals.add(signal);
16380
16460
  }
16381
- valid = false;
16382
- break;
16383
- }
16384
- if (valid) {
16385
- continue;
16461
+ opacity = joinNestedOpacity(opacity, nestedSegment);
16386
16462
  }
16387
16463
  }
16388
- return false;
16389
- }
16390
- return true;
16391
- }
16392
- function validFilesystemTest(head, args) {
16393
- const operands = head === "[" && args.at(-1) === "]" ? args.slice(0, -1) : args;
16394
- if (head === "[" && args.at(-1) !== "]") {
16395
- return false;
16464
+ return shellSegment(commandRedacted, head, requirements, opacity, signals);
16396
16465
  }
16397
- return operands.length === 2 && (/* @__PURE__ */ new Set(["-b", "-c", "-d", "-e", "-f", "-h", "-L", "-r", "-s", "-w", "-x"])).has(
16398
- operands[0] ?? ""
16399
- ) && Boolean(operands[1]);
16400
- }
16401
- function validDockerInfo(args) {
16402
- for (let index = 0; index < args.length; index += 1) {
16403
- const arg = args[index] ?? "";
16404
- if (arg === "--help") {
16405
- continue;
16406
- }
16407
- if (arg === "--format" || arg === "-f") {
16408
- if (!args[index + 1] || isOptionToken(args[index + 1] ?? "")) {
16409
- return false;
16466
+ const recursive = decodeRecursiveInvocationTokens(decoderTokens);
16467
+ if (recursive.kind === "static" && opacity !== "opaque" && opacity !== "unparseable") {
16468
+ requirements.push(
16469
+ processRequirement(recursive.interpreter, "spawn", commandRedacted, [
16470
+ "shell.recursive_wrapper",
16471
+ "dynamic_shell_evaluation"
16472
+ ])
16473
+ );
16474
+ if (recursive.script !== "") {
16475
+ const nested = lowerTopLevelSegments(recursive.script, {
16476
+ ...context,
16477
+ command: recursive.script,
16478
+ env,
16479
+ depth: context.depth + 1
16480
+ });
16481
+ for (const nestedSegment of nested) {
16482
+ requirements.push(
16483
+ ...nestedSegment.requirements.map(
16484
+ (entry) => withInnerProvenance(entry, recursive.script, head, commandRedacted)
16485
+ )
16486
+ );
16487
+ for (const signal of nestedSegment.signals) signals.add(signal);
16410
16488
  }
16411
- index += 1;
16412
- continue;
16413
16489
  }
16414
- if (arg.startsWith("--format=")) {
16415
- if (arg.slice("--format=".length).length === 0) {
16416
- return false;
16490
+ signals.add("shell.recursive_wrapper");
16491
+ signals.add("dynamic_shell_evaluation");
16492
+ return shellSegment(commandRedacted, head, requirements, "recursive", signals);
16493
+ }
16494
+ if (recursive.kind === "dynamic" || recursive.kind === "indeterminate") {
16495
+ const recursiveSignals = [
16496
+ recursive.signal,
16497
+ ...recursive.kind === "dynamic" ? ["dynamic_shell_evaluation"] : []
16498
+ ];
16499
+ requirements.push(
16500
+ processRequirement(recursive.interpreter, "spawn", commandRedacted, recursiveSignals),
16501
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
16502
+ ...recursiveSignals
16503
+ ])
16504
+ );
16505
+ for (const signal of recursiveSignals) signals.add(signal);
16506
+ return shellSegment(
16507
+ commandRedacted,
16508
+ head,
16509
+ requirements,
16510
+ joinEffectOpacity(opacity, "opaque"),
16511
+ signals
16512
+ );
16513
+ }
16514
+ const compose = decodeDockerComposeRun(decoderTokens);
16515
+ if (compose.kind === "recursive" && opacity !== "opaque" && opacity !== "unparseable") {
16516
+ requirements.push(
16517
+ processRequirement(head, "spawn", commandRedacted, ["process.docker_compose_run"])
16518
+ );
16519
+ if (compose.script !== "") {
16520
+ const nested = lowerTopLevelSegments(compose.script, {
16521
+ ...context,
16522
+ command: compose.script,
16523
+ env,
16524
+ depth: context.depth + 1
16525
+ });
16526
+ for (const nestedSegment of nested) {
16527
+ requirements.push(
16528
+ ...nestedSegment.requirements.map(
16529
+ (entry) => withInnerProvenance(entry, compose.script, head, commandRedacted)
16530
+ )
16531
+ );
16532
+ for (const signal of nestedSegment.signals) signals.add(signal);
16533
+ opacity = joinNestedOpacity(opacity, nestedSegment);
16417
16534
  }
16418
- continue;
16419
16535
  }
16420
- return false;
16536
+ signals.add("process.docker_compose_run");
16537
+ return shellSegment(commandRedacted, head, requirements, "recursive", signals);
16421
16538
  }
16422
- return true;
16423
- }
16424
- function bashSyntaxTarget(args) {
16425
- let syntaxOnly = false;
16426
- let script = null;
16427
- const shortFlags = new Set("abefhkmnptuvxBCEHPT".split(""));
16428
- const longFlags = /* @__PURE__ */ new Set([
16429
- "--debugger",
16430
- "--dump-po-strings",
16431
- "--dump-strings",
16432
- "--help",
16433
- "--login",
16434
- "--noediting",
16435
- "--noprofile",
16436
- "--norc",
16437
- "--posix",
16438
- "--restricted",
16439
- "--verbose",
16440
- "--version"
16441
- ]);
16442
- for (let index = 0; index < args.length; index += 1) {
16443
- const arg = args[index] ?? "";
16444
- if (arg === "-c" || arg === "-lc") {
16445
- return null;
16446
- }
16447
- if (arg === "-O" || arg === "+O" || arg === "--rcfile" || arg === "--init-file") {
16448
- if (!args[index + 1] || isOptionToken(args[index + 1] ?? "")) {
16449
- return null;
16539
+ if (compose.kind === "dynamic" || compose.kind === "indeterminate") {
16540
+ requirements.push(
16541
+ processRequirement(head, "spawn", commandRedacted, ["process.docker_compose_run"]),
16542
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
16543
+ compose.signal
16544
+ ])
16545
+ );
16546
+ signals.add("process.docker_compose_run");
16547
+ signals.add(compose.signal);
16548
+ return shellSegment(
16549
+ commandRedacted,
16550
+ head,
16551
+ requirements,
16552
+ joinEffectOpacity(opacity, "opaque"),
16553
+ signals
16554
+ );
16555
+ }
16556
+ const launcher = resolveLauncherRecipe({
16557
+ tokens,
16558
+ cwd: context.cwd,
16559
+ repoRoot: context.repoRoot,
16560
+ depth: context.depth
16561
+ });
16562
+ if (launcher) {
16563
+ signals.add(`launcher.${launcher.reason}`);
16564
+ requirements.push(
16565
+ requirement2(
16566
+ "process.exec",
16567
+ "process.exec",
16568
+ { kind: "executable", command: head, operation: "spawn" },
16569
+ commandRedacted,
16570
+ ["launcher.invoke"]
16571
+ )
16572
+ );
16573
+ for (const recipe of launcher.recipes) {
16574
+ const nested = lowerTopLevelSegments(recipe, {
16575
+ ...context,
16576
+ command: recipe,
16577
+ env,
16578
+ depth: context.depth + 1
16579
+ });
16580
+ for (const nestedSegment of nested) {
16581
+ requirements.push(
16582
+ ...nestedSegment.requirements.map(
16583
+ (entry) => withInnerProvenance(entry, recipe, head, commandRedacted)
16584
+ )
16585
+ );
16586
+ for (const signal of nestedSegment.signals) {
16587
+ signals.add(signal);
16588
+ }
16589
+ opacity = joinNestedOpacity(opacity, nestedSegment);
16450
16590
  }
16451
- index += 1;
16452
- continue;
16453
16591
  }
16454
- 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) {
16455
- continue;
16592
+ if (launcher.opaque || launcher.recipes.length === 0) {
16593
+ requirements.push(
16594
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
16595
+ `launcher.${launcher.reason}`
16596
+ ])
16597
+ );
16598
+ opacity = joinEffectOpacity(opacity, "opaque");
16456
16599
  }
16457
- if (longFlags.has(arg)) {
16458
- continue;
16600
+ return shellSegment(commandRedacted, head, requirements, opacity, signals);
16601
+ }
16602
+ const egress = decodeEgressEffects({
16603
+ tokens,
16604
+ cwd: context.cwd,
16605
+ segment: commandRedacted
16606
+ });
16607
+ if (egress) {
16608
+ requirements.push(...egress);
16609
+ for (const signal of egress.flatMap((entry) => entry.evidence.signals)) {
16610
+ signals.add(signal);
16459
16611
  }
16460
- if (/^[+-][A-Za-z]+$/.test(arg)) {
16461
- const options = arg.slice(1);
16462
- if ([...options].some((option) => !shortFlags.has(option))) {
16463
- return null;
16612
+ } else {
16613
+ const git = decodeGitEffects({
16614
+ tokens,
16615
+ cwd: context.cwd,
16616
+ repoRoot: context.repoRoot,
16617
+ segment: commandRedacted
16618
+ });
16619
+ if (git) {
16620
+ requirements.push(...git);
16621
+ for (const signal of git.flatMap((entry) => entry.evidence.signals)) {
16622
+ signals.add(signal);
16464
16623
  }
16465
- syntaxOnly ||= options.includes("n");
16466
- continue;
16467
- }
16468
- if (arg === "--") {
16469
- script = args[index + 1] ?? null;
16470
- break;
16471
- }
16472
- if (arg.startsWith("-") || arg.startsWith("+")) {
16473
- return null;
16624
+ } else {
16625
+ const processRequirements = decodeProcessOrFilesystem({
16626
+ tokens,
16627
+ head,
16628
+ env,
16629
+ cwd: context.cwd,
16630
+ repoRoot: context.repoRoot,
16631
+ segment: commandRedacted
16632
+ });
16633
+ if (isGrammarUnknownOnly(processRequirements, head)) {
16634
+ const argvDelegate = peelArgvDelegateArgv(tokens);
16635
+ if (argvDelegate && !argvDelegate.opaque && shouldApplyArgvDelegate(head, argvDelegate.innerTokens, context.depth)) {
16636
+ const innerRecipe = innerRecipeFromArgvDelegate(argvDelegate);
16637
+ if (innerRecipe) {
16638
+ requirements.push(
16639
+ processRequirement(head, "inspect", commandRedacted, [
16640
+ "process.argv_delegate",
16641
+ ...argvDelegate.signals
16642
+ ])
16643
+ );
16644
+ for (const signal of argvDelegate.signals) {
16645
+ signals.add(signal);
16646
+ }
16647
+ const nested = lowerTopLevelSegments(innerRecipe, {
16648
+ ...context,
16649
+ command: innerRecipe,
16650
+ env,
16651
+ depth: context.depth + 1
16652
+ });
16653
+ for (const nestedSegment of nested) {
16654
+ requirements.push(
16655
+ ...nestedSegment.requirements.map(
16656
+ (entry) => withInnerProvenance(entry, innerRecipe, head, commandRedacted)
16657
+ )
16658
+ );
16659
+ for (const signal of nestedSegment.signals) {
16660
+ signals.add(signal);
16661
+ }
16662
+ opacity = joinNestedOpacity(opacity, nestedSegment);
16663
+ }
16664
+ return shellSegment(commandRedacted, head, requirements, opacity, signals);
16665
+ }
16666
+ }
16667
+ if (argvDelegate?.opaque) {
16668
+ requirements.push(
16669
+ processRequirement(head, "spawn", commandRedacted, [
16670
+ "process.argv_delegate",
16671
+ argvDelegate.reason,
16672
+ ...argvDelegate.signals
16673
+ ]),
16674
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
16675
+ argvDelegate.reason,
16676
+ "process.argv_delegate_opaque"
16677
+ ])
16678
+ );
16679
+ for (const signal of argvDelegate.signals) {
16680
+ signals.add(signal);
16681
+ }
16682
+ signals.add("process.argv_delegate_opaque");
16683
+ return shellSegment(
16684
+ commandRedacted,
16685
+ head,
16686
+ requirements,
16687
+ joinEffectOpacity(opacity, "opaque"),
16688
+ signals
16689
+ );
16690
+ }
16691
+ }
16692
+ requirements.push(...processRequirements);
16474
16693
  }
16475
- script = arg;
16476
- break;
16477
- }
16478
- return syntaxOnly ? script : null;
16479
- }
16480
- function isOptionToken(value) {
16481
- return value.startsWith("-") || value.startsWith("+");
16482
- }
16483
- function databaseEndpoint(raw) {
16484
- if (!raw || raw.includes("$")) {
16485
- return null;
16486
16694
  }
16487
- try {
16488
- const url = new URL(raw);
16489
- if (!url.hostname || !url.protocol) {
16490
- return null;
16695
+ const environmentSignals = effectChangingEnvironmentSignals(head, env, environment.changedNames);
16696
+ if (environmentSignals.length > 0) {
16697
+ requirements.push(
16698
+ requirement2(
16699
+ "indeterminate",
16700
+ "indeterminate",
16701
+ { kind: "unknown" },
16702
+ commandRedacted,
16703
+ environmentSignals
16704
+ )
16705
+ );
16706
+ opacity = joinEffectOpacity(opacity, "opaque");
16707
+ for (const signal of environmentSignals) {
16708
+ signals.add(signal);
16491
16709
  }
16492
- return {
16493
- host: url.hostname.toLowerCase(),
16494
- protocol: url.protocol.slice(0, -1),
16495
- ...url.port ? { port: Number(url.port) } : {}
16496
- };
16497
- } catch {
16498
- return null;
16499
16710
  }
16500
- }
16501
- function resolvePathOperand(operand, cwd) {
16502
- if (operand === "~") {
16503
- return process.env.HOME ?? operand;
16711
+ if (context.pipeToShellSegment) {
16712
+ signals.add("pipe_to_shell");
16713
+ ensureRequirement(
16714
+ requirements,
16715
+ requirement2(
16716
+ "process.exec",
16717
+ "process.exec",
16718
+ { kind: "executable", command: head || "sh", operation: "spawn" },
16719
+ commandRedacted,
16720
+ ["pipe_to_shell"]
16721
+ )
16722
+ );
16723
+ ensureRequirement(
16724
+ requirements,
16725
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
16726
+ "pipe_to_shell"
16727
+ ])
16728
+ );
16729
+ opacity = joinEffectOpacity(opacity, "opaque");
16504
16730
  }
16505
- if (operand.startsWith("~/")) {
16506
- return path41.join(process.env.HOME ?? "~", operand.slice(2));
16731
+ if (opacity === "unparseable" || opacity === "opaque") {
16732
+ ensureRequirement(
16733
+ requirements,
16734
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, commandRedacted, [
16735
+ `shell.${opacity}`
16736
+ ])
16737
+ );
16507
16738
  }
16508
- return path41.resolve(cwd, operand);
16509
- }
16510
- function isShellHead(head) {
16511
- return head === "bash" || head === "sh" || head === "zsh" || head === "dash" || head === "fish";
16512
- }
16513
- function pipeToShell(command) {
16514
- return /(?:^|[|;&]\s*)(?:bash|sh|zsh|dash|fish)(?:\s|$)/.test(command) && /\|/.test(command);
16515
- }
16516
- function withInnerProvenance(requirementValue, innerCommand, launcher, outerSegment) {
16517
- const provenance = {
16518
- ...requirementValue.provenance,
16519
- segment: outerSegment,
16520
- innerCommand: requirementValue.provenance.innerCommand ?? innerCommand,
16521
- ...launcher === "npm" || launcher === "pnpm" ? { launcher, phase: "delegate" } : {}
16522
- };
16523
- const launcherProvenance = {
16524
- segment: outerSegment,
16525
- innerCommand,
16526
- ...launcher === "npm" || launcher === "pnpm" ? { launcher, phase: "delegate" } : {}
16527
- };
16528
- return withProvenances(requirementValue, provenance, launcherProvenance);
16529
- }
16530
- function withProvenances(requirementValue, provenance, contributing) {
16531
- const provenances = [
16532
- ...requirementValue.provenances ?? [requirementValue.provenance],
16533
- contributing,
16534
- provenance
16535
- ].filter(
16536
- (candidate, index, all) => all.findIndex((entry) => JSON.stringify(entry) === JSON.stringify(candidate)) === index
16537
- );
16538
- return { ...requirementValue, provenance, provenances };
16739
+ return shellSegment(commandRedacted, head, requirements, opacity, signals);
16539
16740
  }
16540
- function ensureRequirement(requirements, candidate) {
16541
- if (!requirements.some(
16542
- (entry) => entry.action === candidate.action && JSON.stringify(entry.resource) === JSON.stringify(candidate.resource)
16543
- )) {
16544
- requirements.push(candidate);
16741
+ function addSubstitutionEffects(requirements, command, context, segment, signals) {
16742
+ const inners = structuralSubstitutionInners(command);
16743
+ for (const inner of inners) {
16744
+ signals.add("command_substitution");
16745
+ if (context.depth >= MAX_LOWER_DEPTH) {
16746
+ requirements.push(
16747
+ requirement2("indeterminate", "indeterminate", { kind: "unknown" }, segment, [
16748
+ "shell.substitution_depth_exceeded"
16749
+ ])
16750
+ );
16751
+ continue;
16752
+ }
16753
+ const nestedSegments = lowerTopLevelSegments(inner, {
16754
+ ...context,
16755
+ command: inner,
16756
+ depth: context.depth + 1
16757
+ });
16758
+ for (const nested of nestedSegments) {
16759
+ requirements.push(
16760
+ ...nested.requirements.map((entry) => {
16761
+ const outerProvenance = {
16762
+ segment,
16763
+ innerCommand: inner
16764
+ };
16765
+ const provenance = {
16766
+ ...entry.provenance,
16767
+ segment,
16768
+ innerCommand: entry.provenance.innerCommand ?? inner
16769
+ };
16770
+ return withProvenances(entry, provenance, outerProvenance);
16771
+ })
16772
+ );
16773
+ }
16545
16774
  }
16546
16775
  }
16547
- function requirement2(tag, action, resource, segment, signals) {
16548
- return {
16549
- tag,
16550
- action,
16551
- resource,
16552
- evidence: {
16553
- level: tag === "indeterminate" ? "indeterminate" : "certain",
16554
- signals: [...new Set(signals)].sort(),
16555
- basis: ["shell_semantic_lowering"]
16556
- },
16557
- provenance: { segment }
16558
- };
16559
- }
16560
16776
 
16561
16777
  // src/core/verdict/verdict.ts
16562
16778
  init_path_utils();
@@ -16776,6 +16992,13 @@ function policyAuth(options) {
16776
16992
  function scrubPayload(value, options) {
16777
16993
  return scrubValue(value, options.scrubOptions);
16778
16994
  }
16995
+ function fingerprintPayload(payload, options) {
16996
+ const toolInput = fingerprintToolInputSource(payload);
16997
+ if (Object.keys(toolInput).length > 0) {
16998
+ return toolInput;
16999
+ }
17000
+ return scrubPayload(payload.tool_input ?? {}, options);
17001
+ }
16779
17002
  function extractFilePath(payload) {
16780
17003
  const toolInput = payload.tool_input;
16781
17004
  if (!toolInput || typeof toolInput !== "object") {
@@ -16950,11 +17173,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
16950
17173
  verdict: "deny_pending_approval",
16951
17174
  reason: "tool_shell_missing_command",
16952
17175
  summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
16953
- fingerprint: toolFingerprint(
16954
- toolName,
16955
- scrubPayload(payload.tool_input ?? {}, options),
16956
- repoRoot
16957
- ),
17176
+ fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
16958
17177
  assessment: {
16959
17178
  reversibility: "irreversible",
16960
17179
  external: false,
@@ -16968,11 +17187,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
16968
17187
  verdict: "allow_flagged",
16969
17188
  reason: "tool_shell_missing_command",
16970
17189
  summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
16971
- fingerprint: toolFingerprint(
16972
- toolName,
16973
- scrubPayload(payload.tool_input ?? {}, options),
16974
- repoRoot
16975
- ),
17190
+ fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
16976
17191
  assessment: {
16977
17192
  reversibility: "recoverable_with_cost",
16978
17193
  external: false,
@@ -16996,11 +17211,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
16996
17211
  verdict: "deny_pending_approval",
16997
17212
  reason: "file_mutation_missing_path",
16998
17213
  summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
16999
- fingerprint: toolFingerprint(
17000
- toolName,
17001
- scrubPayload(payload.tool_input ?? {}, options),
17002
- repoRoot
17003
- ),
17214
+ fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17004
17215
  assessment: {
17005
17216
  reversibility: "irreversible",
17006
17217
  external: false,
@@ -17014,11 +17225,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17014
17225
  verdict: "allow_flagged",
17015
17226
  reason: "file_mutation_missing_path",
17016
17227
  summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17017
- fingerprint: toolFingerprint(
17018
- toolName,
17019
- scrubPayload(payload.tool_input ?? {}, options),
17020
- repoRoot
17021
- ),
17228
+ fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17022
17229
  assessment: {
17023
17230
  reversibility: "recoverable_with_cost",
17024
17231
  external: false,
@@ -17029,7 +17236,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17029
17236
  };
17030
17237
  }
17031
17238
  const signals = [];
17032
- const resolvedPath = path42.isAbsolute(filePath) ? filePath : path42.resolve(cwd, filePath);
17239
+ const resolvedPath = path49.isAbsolute(filePath) ? filePath : path49.resolve(cwd, filePath);
17033
17240
  const hitsProtectedRoot = protectedRoots.some((root) => pathWithinRoot(root, resolvedPath));
17034
17241
  if (hitsProtectedRoot) {
17035
17242
  signals.push("control_plane_path");
@@ -17168,11 +17375,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17168
17375
  verdict: "deny_pending_approval",
17169
17376
  reason: "apply_patch_missing_path",
17170
17377
  summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17171
- fingerprint: toolFingerprint(
17172
- toolName,
17173
- scrubPayload(payload.tool_input ?? {}, options),
17174
- repoRoot
17175
- ),
17378
+ fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17176
17379
  assessment: {
17177
17380
  reversibility: "irreversible",
17178
17381
  external: false,
@@ -17186,11 +17389,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17186
17389
  verdict: "allow_flagged",
17187
17390
  reason: "apply_patch_missing_path",
17188
17391
  summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17189
- fingerprint: toolFingerprint(
17190
- toolName,
17191
- scrubPayload(payload.tool_input ?? {}, options),
17192
- repoRoot
17193
- ),
17392
+ fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17194
17393
  assessment: {
17195
17394
  reversibility: "recoverable_with_cost",
17196
17395
  external: false,
@@ -17221,11 +17420,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17221
17420
  verdict: "allow_flagged",
17222
17421
  reason: sawDelete ? "file_delete" : "file_mutation",
17223
17422
  summary: targets.map((target) => target.path).join(", "),
17224
- fingerprint: toolFingerprint(
17225
- toolName,
17226
- scrubPayload(payload.tool_input ?? {}, options),
17227
- repoRoot
17228
- ),
17423
+ fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17229
17424
  assessment: {
17230
17425
  reversibility: "recoverable_with_cost",
17231
17426
  external: false,
@@ -17239,11 +17434,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17239
17434
  verdict: "allow",
17240
17435
  reason: "unclassified_tool",
17241
17436
  summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17242
- fingerprint: toolFingerprint(
17243
- toolName,
17244
- scrubPayload(payload.tool_input ?? {}, options),
17245
- repoRoot
17246
- ),
17437
+ fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17247
17438
  assessment: {
17248
17439
  reversibility: "reversible",
17249
17440
  external: false,
@@ -17631,7 +17822,7 @@ function hashDecisionConfig(config) {
17631
17822
  init_fingerprint2();
17632
17823
 
17633
17824
  // src/version.ts
17634
- var PACKAGE_VERSION = "0.9.3";
17825
+ var PACKAGE_VERSION = "0.9.4";
17635
17826
 
17636
17827
  // src/runtime-provenance.ts
17637
17828
  function resolveRuntimeArtifactHash(artifactHash) {
@@ -17706,7 +17897,7 @@ init_path_utils();
17706
17897
  import { randomUUID as randomUUID5 } from "node:crypto";
17707
17898
  import { existsSync as existsSync15 } from "node:fs";
17708
17899
  import { mkdir as mkdir11, readdir as readdir3, readFile as readFile12, rename as rename3, rm as rm6 } from "node:fs/promises";
17709
- import path47 from "node:path";
17900
+ import path54 from "node:path";
17710
17901
 
17711
17902
  // src/core/recovery/artifact-store.ts
17712
17903
  init_fingerprint2();
@@ -17714,7 +17905,7 @@ init_path_utils();
17714
17905
  import { randomUUID as randomUUID4 } from "node:crypto";
17715
17906
  import { existsSync as existsSync13 } from "node:fs";
17716
17907
  import { lstat as lstat7, mkdir as mkdir10, open as open5, readdir as readdir2, readFile as readFile9, rename as rename2, rm as rm5, writeFile as writeFile7 } from "node:fs/promises";
17717
- import path44 from "node:path";
17908
+ import path51 from "node:path";
17718
17909
 
17719
17910
  // src/core/recovery/snapshot-node.ts
17720
17911
  init_fingerprint2();
@@ -17734,25 +17925,25 @@ import {
17734
17925
  symlink as symlink3,
17735
17926
  writeFile as writeFile6
17736
17927
  } from "node:fs/promises";
17737
- import path43 from "node:path";
17928
+ import path50 from "node:path";
17738
17929
  var RECOVERY_UNSUPPORTED_FILE_KIND = "recovery_unsupported_file_kind";
17739
17930
  function validRecoveryRelativePath(relativePath) {
17740
- if (!relativePath || relativePath.includes("\0") || path43.isAbsolute(relativePath)) return false;
17741
- const normalized = path43.normalize(relativePath);
17742
- return normalized !== "." && normalized !== ".." && !normalized.startsWith(`..${path43.sep}`);
17931
+ if (!relativePath || relativePath.includes("\0") || path50.isAbsolute(relativePath)) return false;
17932
+ const normalized = path50.normalize(relativePath);
17933
+ return normalized !== "." && normalized !== ".." && !normalized.startsWith(`..${path50.sep}`);
17743
17934
  }
17744
17935
  async function assertRecoverySafeTarget(resourceRoot, relativePath) {
17745
17936
  if (!validRecoveryRelativePath(relativePath)) throw new Error("recovery_path_escape");
17746
17937
  const root = canonicalPath(resourceRoot);
17747
- const target = path43.resolve(root, relativePath);
17748
- const relative = path43.relative(root, target);
17749
- if (relative === ".." || relative.startsWith(`..${path43.sep}`) || path43.isAbsolute(relative)) {
17938
+ const target = path50.resolve(root, relativePath);
17939
+ const relative = path50.relative(root, target);
17940
+ if (relative === ".." || relative.startsWith(`..${path50.sep}`) || path50.isAbsolute(relative)) {
17750
17941
  throw new Error("recovery_path_escape");
17751
17942
  }
17752
17943
  let current = root;
17753
- const parentParts = path43.relative(root, path43.dirname(target)).split(path43.sep).filter(Boolean);
17944
+ const parentParts = path50.relative(root, path50.dirname(target)).split(path50.sep).filter(Boolean);
17754
17945
  for (const part of parentParts) {
17755
- current = path43.join(current, part);
17946
+ current = path50.join(current, part);
17756
17947
  if (!existsSync12(current)) break;
17757
17948
  const info = await lstat6(current);
17758
17949
  if (info.isSymbolicLink()) throw new Error("recovery_symlink_escape");
@@ -17808,7 +17999,7 @@ async function captureRecoverySnapshot(filePath, options) {
17808
17999
  let blob;
17809
18000
  if (options?.blobDir) {
17810
18001
  await mkdir9(options.blobDir, { recursive: true, mode: 448 });
17811
- const blobPath = path43.join(options.blobDir, hash);
18002
+ const blobPath = path50.join(options.blobDir, hash);
17812
18003
  if (!existsSync12(blobPath)) {
17813
18004
  await writeFile6(blobPath, content, { mode: 384 });
17814
18005
  await fsyncPath(blobPath);
@@ -17863,7 +18054,7 @@ async function validateRecoverySnapshot(params) {
17863
18054
  if (record.blob !== `blobs/${record.hash}`) throw new Error(params.corruptReason);
17864
18055
  let content;
17865
18056
  try {
17866
- content = await readFile8(path43.join(params.artifactDir, record.blob));
18057
+ content = await readFile8(path50.join(params.artifactDir, record.blob));
17867
18058
  } catch {
17868
18059
  throw new Error(params.corruptReason);
17869
18060
  }
@@ -17891,13 +18082,13 @@ var RECOVERY_STATES = /* @__PURE__ */ new Set([
17891
18082
  ]);
17892
18083
  var STAGING_STALE_MS = 5 * 6e4;
17893
18084
  function checkpointsRoot(stateDir) {
17894
- return path44.join(stateDir, "recovery", "checkpoints");
18085
+ return path51.join(stateDir, "recovery", "checkpoints");
17895
18086
  }
17896
18087
  function checkpointDir(stateDir, checkpointId) {
17897
18088
  if (!/^cp_[a-f0-9]{24}$/.test(checkpointId)) {
17898
18089
  throw new Error("invalid_recovery_checkpoint_id");
17899
18090
  }
17900
- return path44.join(checkpointsRoot(stateDir), checkpointId);
18091
+ return path51.join(checkpointsRoot(stateDir), checkpointId);
17901
18092
  }
17902
18093
  async function fsyncPath2(filePath) {
17903
18094
  const handle = await open5(filePath, "r");
@@ -17908,13 +18099,13 @@ async function fsyncPath2(filePath) {
17908
18099
  }
17909
18100
  }
17910
18101
  async function atomicWriteJson(filePath, value) {
17911
- await mkdir10(path44.dirname(filePath), { recursive: true, mode: 448 });
18102
+ await mkdir10(path51.dirname(filePath), { recursive: true, mode: 448 });
17912
18103
  const temporary = `${filePath}.tmp-${randomUUID4()}`;
17913
18104
  await writeFile7(temporary, `${JSON.stringify(value, null, 2)}
17914
18105
  `, { mode: 384 });
17915
18106
  await fsyncPath2(temporary);
17916
18107
  await rename2(temporary, filePath);
17917
- await fsyncPath2(path44.dirname(filePath));
18108
+ await fsyncPath2(path51.dirname(filePath));
17918
18109
  }
17919
18110
  async function writeRecoveryState(artifactDir, state, manifestHash, detail) {
17920
18111
  const value = {
@@ -17924,13 +18115,13 @@ async function writeRecoveryState(artifactDir, state, manifestHash, detail) {
17924
18115
  manifestHash,
17925
18116
  ...detail ? { detail } : {}
17926
18117
  };
17927
- await atomicWriteJson(path44.join(artifactDir, "state.json"), value);
18118
+ await atomicWriteJson(path51.join(artifactDir, "state.json"), value);
17928
18119
  }
17929
18120
  async function directorySize(root) {
17930
18121
  if (!existsSync13(root)) return 0;
17931
18122
  let total = 0;
17932
18123
  for (const entry of await readdir2(root, { withFileTypes: true })) {
17933
- const entryPath = path44.join(root, entry.name);
18124
+ const entryPath = path51.join(root, entry.name);
17934
18125
  if (entry.isDirectory()) total += await directorySize(entryPath);
17935
18126
  else total += (await lstat7(entryPath)).size;
17936
18127
  }
@@ -17975,9 +18166,9 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
17975
18166
  let rawManifest;
17976
18167
  let state;
17977
18168
  try {
17978
- rawManifest = JSON.parse(await readFile9(path44.join(artifactDir, "manifest.json"), "utf8"));
18169
+ rawManifest = JSON.parse(await readFile9(path51.join(artifactDir, "manifest.json"), "utf8"));
17979
18170
  state = JSON.parse(
17980
- await readFile9(path44.join(artifactDir, "state.json"), "utf8")
18171
+ await readFile9(path51.join(artifactDir, "state.json"), "utf8")
17981
18172
  );
17982
18173
  } catch {
17983
18174
  throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
@@ -17993,10 +18184,10 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
17993
18184
  }
17994
18185
  const entryPaths = /* @__PURE__ */ new Set();
17995
18186
  for (const entry of manifest.entries) {
17996
- if (!entry || typeof entry !== "object" || Array.isArray(entry) || Object.keys(entry).length !== 3 || !Object.keys(entry).every((key) => ["path", "before", "after"].includes(key)) || typeof entry.path !== "string" || !("before" in entry) || !("after" in entry) || !validRecoveryRelativePath(entry.path) || entryPaths.has(path44.normalize(entry.path))) {
18187
+ if (!entry || typeof entry !== "object" || Array.isArray(entry) || Object.keys(entry).length !== 3 || !Object.keys(entry).every((key) => ["path", "before", "after"].includes(key)) || typeof entry.path !== "string" || !("before" in entry) || !("after" in entry) || !validRecoveryRelativePath(entry.path) || entryPaths.has(path51.normalize(entry.path))) {
17997
18188
  throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
17998
18189
  }
17999
- entryPaths.add(path44.normalize(entry.path));
18190
+ entryPaths.add(path51.normalize(entry.path));
18000
18191
  for (const [side, snapshot] of [
18001
18192
  ["before", entry.before],
18002
18193
  ["after", entry.after]
@@ -18010,7 +18201,7 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
18010
18201
  });
18011
18202
  }
18012
18203
  }
18013
- const receiptPath = path44.join(artifactDir, "receipt.json");
18204
+ const receiptPath = path51.join(artifactDir, "receipt.json");
18014
18205
  let receipt;
18015
18206
  if (["applied", "restoring", "restored", "conflict"].includes(state.state) || existsSync13(receiptPath)) {
18016
18207
  receipt = await readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash);
@@ -18020,7 +18211,7 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
18020
18211
  async function readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash) {
18021
18212
  let rawReceipt;
18022
18213
  try {
18023
- rawReceipt = JSON.parse(await readFile9(path44.join(artifactDir, "receipt.json"), "utf8"));
18214
+ rawReceipt = JSON.parse(await readFile9(path51.join(artifactDir, "receipt.json"), "utf8"));
18024
18215
  } catch {
18025
18216
  throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
18026
18217
  }
@@ -18043,7 +18234,7 @@ async function readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHas
18043
18234
  return receipt;
18044
18235
  }
18045
18236
  async function ensureRecoveryReceipt(artifactDir, manifest, manifestHash) {
18046
- const receiptPath = path44.join(artifactDir, "receipt.json");
18237
+ const receiptPath = path51.join(artifactDir, "receipt.json");
18047
18238
  if (existsSync13(receiptPath)) {
18048
18239
  return readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash);
18049
18240
  }
@@ -18068,7 +18259,7 @@ async function artifactRepoRoot(stateDir, checkpointId) {
18068
18259
  const artifactDir = checkpointDir(stateDir, checkpointId);
18069
18260
  try {
18070
18261
  const manifest = JSON.parse(
18071
- await readFile9(path44.join(artifactDir, "manifest.json"), "utf8")
18262
+ await readFile9(path51.join(artifactDir, "manifest.json"), "utf8")
18072
18263
  );
18073
18264
  if (typeof manifest.repoRoot === "string" && manifest.repoRoot) {
18074
18265
  return canonicalPath(manifest.repoRoot);
@@ -18076,7 +18267,7 @@ async function artifactRepoRoot(stateDir, checkpointId) {
18076
18267
  } catch {
18077
18268
  }
18078
18269
  try {
18079
- const owner = JSON.parse(await readFile9(path44.join(artifactDir, "owner.json"), "utf8"));
18270
+ const owner = JSON.parse(await readFile9(path51.join(artifactDir, "owner.json"), "utf8"));
18080
18271
  return typeof owner.repoRoot === "string" && owner.repoRoot ? canonicalPath(owner.repoRoot) : null;
18081
18272
  } catch {
18082
18273
  return null;
@@ -18096,10 +18287,10 @@ async function cleanupOrphanedStaging(stateDir) {
18096
18287
  const now = Date.now();
18097
18288
  for (const entry of await readdir2(root, { withFileTypes: true })) {
18098
18289
  if (!entry.isDirectory() || !/^\.tmp-cp_[a-f0-9]{24}$/.test(entry.name)) continue;
18099
- const stagingPath = path44.join(root, entry.name);
18290
+ const stagingPath = path51.join(root, entry.name);
18100
18291
  let stale = false;
18101
18292
  try {
18102
- const owner = JSON.parse(await readFile9(path44.join(stagingPath, "owner.json"), "utf8"));
18293
+ const owner = JSON.parse(await readFile9(path51.join(stagingPath, "owner.json"), "utf8"));
18103
18294
  const pid = typeof owner.pid === "number" ? owner.pid : Number.NaN;
18104
18295
  const createdAt = typeof owner.createdAt === "string" ? Date.parse(owner.createdAt) : NaN;
18105
18296
  let alive = false;
@@ -18141,7 +18332,7 @@ async function markRecoveryCheckpointApplied(stateDir, checkpoint) {
18141
18332
  init_fingerprint2();
18142
18333
  import { existsSync as existsSync14 } from "node:fs";
18143
18334
  import { readFile as readFile10 } from "node:fs/promises";
18144
- import path45 from "node:path";
18335
+ import path52 from "node:path";
18145
18336
  async function matchRecoverySide(resourceRoot, entries, side) {
18146
18337
  for (const entry of entries) {
18147
18338
  const target = await assertRecoverySafeTarget(resourceRoot, entry.path);
@@ -18156,7 +18347,7 @@ async function reconcileRecoveryCheckpoint(stateDir, checkpointId) {
18156
18347
  } catch {
18157
18348
  const artifactDir = checkpointDir(stateDir, checkpointId);
18158
18349
  if (existsSync14(artifactDir)) {
18159
- const manifestPath = path45.join(artifactDir, "manifest.json");
18350
+ const manifestPath = path52.join(artifactDir, "manifest.json");
18160
18351
  const hash = existsSync14(manifestPath) ? hashValue(await readFile10(manifestPath, "utf8")) : "unavailable";
18161
18352
  await writeRecoveryState(artifactDir, "corrupt", hash, RECOVERY_CHECKPOINT_CORRUPT);
18162
18353
  }
@@ -18193,7 +18384,7 @@ async function reconcileRecoveryCheckpoint(stateDir, checkpointId) {
18193
18384
  // src/core/recovery/resource-identity.ts
18194
18385
  init_fingerprint2();
18195
18386
  import { lstat as lstat8, readFile as readFile11, realpath as realpath3 } from "node:fs/promises";
18196
- import path46 from "node:path";
18387
+ import path53 from "node:path";
18197
18388
  async function currentRecoveryResourceIdentity(resourceRoot, resourceKind) {
18198
18389
  const resolvedRoot = await realpath3(resourceRoot);
18199
18390
  if (resourceKind === "directory") {
@@ -18201,13 +18392,13 @@ async function currentRecoveryResourceIdentity(resourceRoot, resourceKind) {
18201
18392
  if (!rootInfo.isDirectory()) throw new Error("recovery_repo_identity_unavailable");
18202
18393
  return hashValue(`${resolvedRoot}\0${rootInfo.dev}:${rootInfo.ino}:${rootInfo.birthtimeMs}`);
18203
18394
  }
18204
- const dotGit = path46.join(resolvedRoot, ".git");
18395
+ const dotGit = path53.join(resolvedRoot, ".git");
18205
18396
  const gitInfo = await lstat8(dotGit);
18206
18397
  let gitMetadataPath = dotGit;
18207
18398
  if (gitInfo.isFile()) {
18208
18399
  const marker = (await readFile11(dotGit, "utf8")).trim();
18209
18400
  if (!marker.startsWith("gitdir:")) throw new Error("recovery_repo_identity_unavailable");
18210
- gitMetadataPath = path46.resolve(resolvedRoot, marker.slice("gitdir:".length).trim());
18401
+ gitMetadataPath = path53.resolve(resolvedRoot, marker.slice("gitdir:".length).trim());
18211
18402
  } else if (!gitInfo.isDirectory()) {
18212
18403
  throw new Error("recovery_repo_identity_unavailable");
18213
18404
  }
@@ -18280,16 +18471,16 @@ async function prepareRecoveryCheckpoint(params) {
18280
18471
  throw new Error(RECOVERY_CHECKPOINT_QUOTA);
18281
18472
  }
18282
18473
  const checkpointId = `cp_${randomUUID5().replaceAll("-", "").slice(0, 24)}`;
18283
- const temporary = path47.join(checkpointsRoot(params.stateDir), `.tmp-${checkpointId}`);
18474
+ const temporary = path54.join(checkpointsRoot(params.stateDir), `.tmp-${checkpointId}`);
18284
18475
  const finalDir = checkpointDir(params.stateDir, checkpointId);
18285
18476
  await mkdir11(temporary, { recursive: true, mode: 448 });
18286
- await atomicWriteJson(path47.join(temporary, "owner.json"), {
18477
+ await atomicWriteJson(path54.join(temporary, "owner.json"), {
18287
18478
  version: 1,
18288
18479
  pid: process.pid,
18289
18480
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
18290
18481
  repoRoot: canonicalPath(params.repoRoot)
18291
18482
  });
18292
- await mkdir11(path47.join(temporary, "blobs"), { recursive: true, mode: 448 });
18483
+ await mkdir11(path54.join(temporary, "blobs"), { recursive: true, mode: 448 });
18293
18484
  try {
18294
18485
  const entries = [];
18295
18486
  const protectedRoots = (params.protectedRoots ?? []).map(canonicalPath);
@@ -18298,8 +18489,8 @@ async function prepareRecoveryCheckpoint(params) {
18298
18489
  )) {
18299
18490
  const target = await assertRecoverySafeTarget(params.repoRoot, change.relativePath);
18300
18491
  if (protectedRoots.some((root) => {
18301
- const relative = path47.relative(root, target);
18302
- return relative === "" || relative !== ".." && !relative.startsWith(`..${path47.sep}`) && !path47.isAbsolute(relative);
18492
+ const relative = path54.relative(root, target);
18493
+ return relative === "" || relative !== ".." && !relative.startsWith(`..${path54.sep}`) && !path54.isAbsolute(relative);
18303
18494
  })) {
18304
18495
  throw new Error("recovery_protected_path");
18305
18496
  }
@@ -18311,7 +18502,7 @@ async function prepareRecoveryCheckpoint(params) {
18311
18502
  entries.push({
18312
18503
  path: change.relativePath,
18313
18504
  before: await captureRecoverySnapshot(baseline, {
18314
- blobDir: path47.join(temporary, "blobs")
18505
+ blobDir: path54.join(temporary, "blobs")
18315
18506
  }),
18316
18507
  after: withoutRecoveryBlob(await captureRecoverySnapshot(source))
18317
18508
  });
@@ -18348,7 +18539,7 @@ async function prepareRecoveryCheckpoint(params) {
18348
18539
  entries
18349
18540
  };
18350
18541
  const manifestHash = hashValue(canonicalStringify(manifest));
18351
- await atomicWriteJson(path47.join(temporary, "manifest.json"), manifest);
18542
+ await atomicWriteJson(path54.join(temporary, "manifest.json"), manifest);
18352
18543
  await writeRecoveryState(temporary, "prepared", manifestHash);
18353
18544
  await fsyncPath2(temporary);
18354
18545
  const projectedBytes = await recoveryCheckpointStorageBytes(params.stateDir, params.repoRoot);
@@ -18391,7 +18582,7 @@ async function listRecoveryCheckpoints(stateDir, repoRoot) {
18391
18582
  } catch {
18392
18583
  try {
18393
18584
  const raw = JSON.parse(
18394
- await readFile12(path47.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
18585
+ await readFile12(path54.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
18395
18586
  );
18396
18587
  rootFromArtifact = typeof raw.repoRoot === "string" && raw.repoRoot ? raw.repoRoot : void 0;
18397
18588
  } catch {
@@ -18425,7 +18616,7 @@ async function listRecoveryCheckpoints(stateDir, repoRoot) {
18425
18616
  } catch {
18426
18617
  try {
18427
18618
  const manifest = JSON.parse(
18428
- await readFile12(path47.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
18619
+ await readFile12(path54.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
18429
18620
  );
18430
18621
  if (manifest.checkpointId !== id || ![1, 2].includes(manifest.version)) continue;
18431
18622
  if (repoRoot && canonicalPath(manifest.repoRoot) !== canonicalPath(repoRoot)) continue;
@@ -18455,7 +18646,7 @@ async function recoveryCheckpointStorageBytes(stateDir, repoRoot) {
18455
18646
  let total = 0;
18456
18647
  for (const entry of await readdir3(root, { withFileTypes: true })) {
18457
18648
  if (!entry.isDirectory()) continue;
18458
- const entryPath = path47.join(root, entry.name);
18649
+ const entryPath = path54.join(root, entry.name);
18459
18650
  if (/^cp_[a-f0-9]{24}$/.test(entry.name)) {
18460
18651
  if (await artifactRepoRoot(stateDir, entry.name) === expected) {
18461
18652
  total += await directorySize(entryPath);
@@ -18464,7 +18655,7 @@ async function recoveryCheckpointStorageBytes(stateDir, repoRoot) {
18464
18655
  }
18465
18656
  if (/^\.tmp-cp_[a-f0-9]{24}$/.test(entry.name)) {
18466
18657
  try {
18467
- const owner = JSON.parse(await readFile12(path47.join(entryPath, "owner.json"), "utf8"));
18658
+ const owner = JSON.parse(await readFile12(path54.join(entryPath, "owner.json"), "utf8"));
18468
18659
  if (typeof owner.repoRoot === "string" && canonicalPath(owner.repoRoot) === expected) {
18469
18660
  total += await directorySize(entryPath);
18470
18661
  }
@@ -18508,14 +18699,14 @@ init_scrub();
18508
18699
  // src/core/transactional/file-checkpoint-backend.ts
18509
18700
  import { cp, lstat as lstat11, mkdir as mkdir13, mkdtemp as mkdtemp5, readdir as readdir6, rm as rm9, writeFile as writeFile10 } from "node:fs/promises";
18510
18701
  import os5 from "node:os";
18511
- import path51 from "node:path";
18702
+ import path58 from "node:path";
18512
18703
 
18513
18704
  // src/core/transactional/file-checkpoint-git.ts
18514
18705
  init_path_utils();
18515
18706
  import { spawn as spawn8 } from "node:child_process";
18516
18707
  import { createHash as createHash13 } from "node:crypto";
18517
18708
  import { copyFile as copyFile3, lstat as lstat9, readdir as readdir4, readFile as readFile13 } from "node:fs/promises";
18518
- import path48 from "node:path";
18709
+ import path55 from "node:path";
18519
18710
  var FILE_CHECKPOINT_GIT_METADATA_CHANGED = "file_checkpoint_git_metadata_changed";
18520
18711
  var FILE_CHECKPOINT_SOURCE_CHANGED = "file_checkpoint_source_changed";
18521
18712
  var FILE_CHECKPOINT_CWD_OUTSIDE_ROOT = "file_checkpoint_cwd_outside_root";
@@ -18549,7 +18740,7 @@ function rethrowStableFileCheckpointError(error) {
18549
18740
  }
18550
18741
  async function rootGitMetadataPresent(repoRoot) {
18551
18742
  try {
18552
- await lstat9(path48.join(repoRoot, ".git"));
18743
+ await lstat9(path55.join(repoRoot, ".git"));
18553
18744
  return true;
18554
18745
  } catch {
18555
18746
  return false;
@@ -18598,10 +18789,10 @@ function execGit2(repoRoot, args) {
18598
18789
  }
18599
18790
  async function resolveGitPath(repoRoot, gitPath) {
18600
18791
  const trimmed = gitPath.trim();
18601
- if (path48.isAbsolute(trimmed)) {
18792
+ if (path55.isAbsolute(trimmed)) {
18602
18793
  return trimmed;
18603
18794
  }
18604
- return path48.join(repoRoot, trimmed);
18795
+ return path55.join(repoRoot, trimmed);
18605
18796
  }
18606
18797
  async function cloneBareWorktreeCopy(sourceRoot, destinationRoot) {
18607
18798
  await execGit2(sourceRoot, [
@@ -18643,7 +18834,7 @@ async function copyGitIndexState(sourceRoot, destinationRoot) {
18643
18834
  destinationRoot,
18644
18835
  await execGit2(destinationRoot, ["rev-parse", "--git-dir"])
18645
18836
  );
18646
- const destinationShared = path48.join(destinationGitDir, path48.basename(sourceShared));
18837
+ const destinationShared = path55.join(destinationGitDir, path55.basename(sourceShared));
18647
18838
  try {
18648
18839
  await copyFile3(sourceShared, destinationShared);
18649
18840
  } catch (error) {
@@ -18652,7 +18843,7 @@ async function copyGitIndexState(sourceRoot, destinationRoot) {
18652
18843
  }
18653
18844
  async function readGitFile(gitDir, relativePath) {
18654
18845
  try {
18655
- return await readFile13(path48.join(gitDir, relativePath));
18846
+ return await readFile13(path55.join(gitDir, relativePath));
18656
18847
  } catch {
18657
18848
  return null;
18658
18849
  }
@@ -18676,8 +18867,8 @@ async function hashResolvedGitPath(repoRoot, gitDir, gitPath, hash) {
18676
18867
  repoRoot,
18677
18868
  await execGit2(repoRoot, ["rev-parse", "--git-path", gitPath])
18678
18869
  );
18679
- const relative = path48.resolve(resolved).startsWith(path48.resolve(gitDir)) ? path48.relative(gitDir, resolved) : resolved;
18680
- const content = typeof relative === "string" && !relative.startsWith("..") && !path48.isAbsolute(relative) ? await readGitFile(gitDir, relative) : await readAbsoluteGitFile(resolved);
18870
+ const relative = path55.resolve(resolved).startsWith(path55.resolve(gitDir)) ? path55.relative(gitDir, resolved) : resolved;
18871
+ const content = typeof relative === "string" && !relative.startsWith("..") && !path55.isAbsolute(relative) ? await readGitFile(gitDir, relative) : await readAbsoluteGitFile(resolved);
18681
18872
  if (content !== null) {
18682
18873
  hashGitFileContent(hash, gitPath, content);
18683
18874
  }
@@ -18687,13 +18878,13 @@ async function hashResolvedGitPath(repoRoot, gitDir, gitPath, hash) {
18687
18878
  async function hashGitTree(gitDir, relativeDir, hash) {
18688
18879
  let names;
18689
18880
  try {
18690
- names = await readdir4(path48.join(gitDir, relativeDir));
18881
+ names = await readdir4(path55.join(gitDir, relativeDir));
18691
18882
  } catch {
18692
18883
  return;
18693
18884
  }
18694
18885
  for (const name of names.sort()) {
18695
- const relativePath = relativeDir ? path48.join(relativeDir, name) : name;
18696
- const absolutePath = path48.join(gitDir, relativePath);
18886
+ const relativePath = relativeDir ? path55.join(relativeDir, name) : name;
18887
+ const absolutePath = path55.join(gitDir, relativePath);
18697
18888
  let childNames = null;
18698
18889
  try {
18699
18890
  childNames = await readdir4(absolutePath);
@@ -18715,7 +18906,7 @@ async function hashGitTree(gitDir, relativeDir, hash) {
18715
18906
  }
18716
18907
  async function computeGitMetadataFingerprint(repoRoot) {
18717
18908
  const gitDirRel = (await execGit2(repoRoot, ["rev-parse", "--git-dir"])).trim();
18718
- const gitDir = path48.isAbsolute(gitDirRel) ? gitDirRel : path48.join(repoRoot, gitDirRel);
18909
+ const gitDir = path55.isAbsolute(gitDirRel) ? gitDirRel : path55.join(repoRoot, gitDirRel);
18719
18910
  const hash = createHash13("sha256");
18720
18911
  for (const file of [
18721
18912
  "HEAD",
@@ -18738,8 +18929,8 @@ async function computeGitMetadataFingerprint(repoRoot) {
18738
18929
  const sharedIndex = (await execGit2(repoRoot, ["rev-parse", "--shared-index-path"])).trim();
18739
18930
  if (sharedIndex) {
18740
18931
  const resolved = await resolveGitPath(repoRoot, sharedIndex);
18741
- const relative = path48.relative(gitDir, resolved);
18742
- const content = relative && !relative.startsWith("..") && !path48.isAbsolute(relative) ? await readGitFile(gitDir, relative) : await readAbsoluteGitFile(resolved);
18932
+ const relative = path55.relative(gitDir, resolved);
18933
+ const content = relative && !relative.startsWith("..") && !path55.isAbsolute(relative) ? await readGitFile(gitDir, relative) : await readAbsoluteGitFile(resolved);
18743
18934
  if (content !== null) {
18744
18935
  hashGitFileContent(hash, "shared-index", content);
18745
18936
  }
@@ -18750,7 +18941,7 @@ async function computeGitMetadataFingerprint(repoRoot) {
18750
18941
  await hashResolvedGitPath(repoRoot, gitDir, gitPath, hash);
18751
18942
  }
18752
18943
  try {
18753
- const rootGitPath = path48.join(repoRoot, ".git");
18944
+ const rootGitPath = path55.join(repoRoot, ".git");
18754
18945
  const rootGitInfo = await lstat9(rootGitPath);
18755
18946
  if (rootGitInfo.isFile()) {
18756
18947
  const content = await readAbsoluteGitFile(rootGitPath);
@@ -18766,14 +18957,14 @@ async function computeGitMetadataFingerprint(repoRoot) {
18766
18957
  function resolveExecutionCwdRelative(resourceRoot, cwd) {
18767
18958
  const resolvedCwd = canonicalPath(cwd);
18768
18959
  const resourceCanonical = canonicalPath(resourceRoot);
18769
- const relative = path48.relative(resourceCanonical, resolvedCwd);
18960
+ const relative = path55.relative(resourceCanonical, resolvedCwd);
18770
18961
  if (relative === "" || relative === ".") {
18771
18962
  return "";
18772
18963
  }
18773
- if (relative.startsWith("..") || path48.isAbsolute(relative)) {
18964
+ if (relative.startsWith("..") || path55.isAbsolute(relative)) {
18774
18965
  throw new Error(FILE_CHECKPOINT_CWD_OUTSIDE_ROOT);
18775
18966
  }
18776
- return relative.split(path48.sep).join("/");
18967
+ return relative.split(path55.sep).join("/");
18777
18968
  }
18778
18969
 
18779
18970
  // src/core/transactional/file-checkpoint-isolation.ts
@@ -18795,7 +18986,7 @@ function fileCheckpointIsolationReason(context) {
18795
18986
 
18796
18987
  // src/core/transactional/file-checkpoint-staging.ts
18797
18988
  import { readdir as readdir5, readFile as readFile14, rm as rm7, writeFile as writeFile8 } from "node:fs/promises";
18798
- import path49 from "node:path";
18989
+ import path56 from "node:path";
18799
18990
  function isOwnerProcessAlive(pid) {
18800
18991
  try {
18801
18992
  process.kill(pid, 0);
@@ -18805,12 +18996,12 @@ function isOwnerProcessAlive(pid) {
18805
18996
  }
18806
18997
  }
18807
18998
  async function writeOwnerMarker(stagingRoot, marker) {
18808
- await writeFile8(path49.join(stagingRoot, "owner.json"), `${JSON.stringify(marker)}
18999
+ await writeFile8(path56.join(stagingRoot, "owner.json"), `${JSON.stringify(marker)}
18809
19000
  `, "utf8");
18810
19001
  }
18811
19002
  async function readOwnerMarker(stagingRoot) {
18812
19003
  try {
18813
- const raw = await readFile14(path49.join(stagingRoot, "owner.json"), "utf8");
19004
+ const raw = await readFile14(path56.join(stagingRoot, "owner.json"), "utf8");
18814
19005
  return JSON.parse(raw.trim());
18815
19006
  } catch {
18816
19007
  return null;
@@ -18828,7 +19019,7 @@ async function collectDeadOwnerStaging(parentDir) {
18828
19019
  if (!name.startsWith("belay-file-checkpoint-")) {
18829
19020
  continue;
18830
19021
  }
18831
- const stagingRoot = path49.join(parentDir, name);
19022
+ const stagingRoot = path56.join(parentDir, name);
18832
19023
  const marker = await readOwnerMarker(stagingRoot);
18833
19024
  if (!marker) {
18834
19025
  dead.push(stagingRoot);
@@ -18860,7 +19051,7 @@ import {
18860
19051
  writeFile as writeFile9
18861
19052
  } from "node:fs/promises";
18862
19053
  import os4 from "node:os";
18863
- import path50 from "node:path";
19054
+ import path57 from "node:path";
18864
19055
  var FILE_CHECKPOINT_COPY_FAILED = "file_checkpoint_copy_failed";
18865
19056
  async function chmodSafe2(target, mode) {
18866
19057
  try {
@@ -18870,7 +19061,7 @@ async function chmodSafe2(target, mode) {
18870
19061
  }
18871
19062
  }
18872
19063
  async function copyRegularFile(sourcePath, destinationPath, mode, strategy) {
18873
- await mkdir12(path50.dirname(destinationPath), { recursive: true });
19064
+ await mkdir12(path57.dirname(destinationPath), { recursive: true });
18874
19065
  if (strategy === "clonefile" && fsConstants2.COPYFILE_FICLONE !== void 0) {
18875
19066
  try {
18876
19067
  await copyFile4(sourcePath, destinationPath, fsConstants2.COPYFILE_FICLONE);
@@ -18896,7 +19087,7 @@ async function copyNode(sourceRoot, destinationRoot, relativePath, strategy) {
18896
19087
  return strategy;
18897
19088
  }
18898
19089
  if (info.isSymbolicLink()) {
18899
- await mkdir12(path50.dirname(destinationPath), { recursive: true });
19090
+ await mkdir12(path57.dirname(destinationPath), { recursive: true });
18900
19091
  await symlink4(await readlink5(sourcePath), destinationPath);
18901
19092
  return strategy;
18902
19093
  }
@@ -18952,9 +19143,9 @@ async function mapWithConcurrency(items, concurrency, worker) {
18952
19143
  async function probeFileCloneStrategy() {
18953
19144
  let tempDir = null;
18954
19145
  try {
18955
- tempDir = await mkdtemp4(path50.join(os4.tmpdir(), "belay-clone-probe-"));
18956
- const source = path50.join(tempDir, "source.txt");
18957
- const destination = path50.join(tempDir, "dest.txt");
19146
+ tempDir = await mkdtemp4(path57.join(os4.tmpdir(), "belay-clone-probe-"));
19147
+ const source = path57.join(tempDir, "source.txt");
19148
+ const destination = path57.join(tempDir, "dest.txt");
18958
19149
  await writeFile9(source, "probe\n");
18959
19150
  if (fsConstants2.COPYFILE_FICLONE_FORCE !== void 0) {
18960
19151
  try {
@@ -19107,11 +19298,11 @@ async function protectedRootState(root) {
19107
19298
  return `directory:${node.hash}:${index.treeHash}`;
19108
19299
  }
19109
19300
  function executionProtectedRoot(resourceRoot, executionRoot, protectedRoot) {
19110
- const relative = path51.relative(path51.resolve(resourceRoot), path51.resolve(protectedRoot));
19111
- if (relative === "" || relative.startsWith("..") || path51.isAbsolute(relative)) {
19301
+ const relative = path58.relative(path58.resolve(resourceRoot), path58.resolve(protectedRoot));
19302
+ if (relative === "" || relative.startsWith("..") || path58.isAbsolute(relative)) {
19112
19303
  return null;
19113
19304
  }
19114
- return path51.join(executionRoot, relative);
19305
+ return path58.join(executionRoot, relative);
19115
19306
  }
19116
19307
  async function captureProtectedRootStates(resourceRoot, executionRoot, protectedRoots) {
19117
19308
  const states = /* @__PURE__ */ new Map();
@@ -19136,15 +19327,15 @@ async function directoryByteSize(root, deadlineMs) {
19136
19327
  }
19137
19328
  let total = 0;
19138
19329
  for (const name of await readdir6(root)) {
19139
- total += await directoryByteSize(path51.join(root, name), deadlineMs);
19330
+ total += await directoryByteSize(path58.join(root, name), deadlineMs);
19140
19331
  }
19141
19332
  return total;
19142
19333
  }
19143
19334
  async function copyGitMetadataDirectory(sourceRoot, destinationRoot) {
19144
19335
  const gitDirRel = (await execGit2(sourceRoot, ["rev-parse", "--git-dir"])).trim();
19145
- const sourceGitDir = path51.isAbsolute(gitDirRel) ? gitDirRel : path51.join(sourceRoot, gitDirRel);
19146
- const relativeGitDir = path51.relative(path51.resolve(sourceRoot), path51.resolve(sourceGitDir));
19147
- const destinationGitDir = relativeGitDir && !relativeGitDir.startsWith("..") ? path51.join(destinationRoot, relativeGitDir) : path51.join(destinationRoot, ".git");
19336
+ const sourceGitDir = path58.isAbsolute(gitDirRel) ? gitDirRel : path58.join(sourceRoot, gitDirRel);
19337
+ const relativeGitDir = path58.relative(path58.resolve(sourceRoot), path58.resolve(sourceGitDir));
19338
+ const destinationGitDir = relativeGitDir && !relativeGitDir.startsWith("..") ? path58.join(destinationRoot, relativeGitDir) : path58.join(destinationRoot, ".git");
19148
19339
  await cp(sourceGitDir, destinationGitDir, { recursive: true, force: true });
19149
19340
  }
19150
19341
  async function prepareDirtyGitSnapshot(context) {
@@ -19153,7 +19344,7 @@ async function prepareDirtyGitSnapshot(context) {
19153
19344
  const quotas = context.fileCheckpoint;
19154
19345
  const deadlineMs = Date.now() + quotas.prepareTimeoutMs;
19155
19346
  await removeDeadOwnerStaging(os5.tmpdir());
19156
- const stagingRoot = await mkdtemp5(path51.join(os5.tmpdir(), "belay-file-checkpoint-"));
19347
+ const stagingRoot = await mkdtemp5(path58.join(os5.tmpdir(), "belay-file-checkpoint-"));
19157
19348
  await writeOwnerMarker(stagingRoot, {
19158
19349
  version: 1,
19159
19350
  pid: process.pid,
@@ -19161,8 +19352,8 @@ async function prepareDirtyGitSnapshot(context) {
19161
19352
  resourceRoot: context.repoRoot,
19162
19353
  backend: "file_checkpoint"
19163
19354
  });
19164
- const baselineRoot = path51.join(stagingRoot, "baseline");
19165
- const executionRoot = path51.join(stagingRoot, "execution");
19355
+ const baselineRoot = path58.join(stagingRoot, "baseline");
19356
+ const executionRoot = path58.join(stagingRoot, "execution");
19166
19357
  try {
19167
19358
  resolveExecutionCwdRelative(context.repoRoot, context.cwd);
19168
19359
  const sourceGitMetadataFingerprint = await computeGitMetadataFingerprint(context.repoRoot);
@@ -19196,7 +19387,7 @@ async function prepareDirtyGitSnapshot(context) {
19196
19387
  throw new Error(FILE_CHECKPOINT_SOURCE_CHANGED);
19197
19388
  }
19198
19389
  await writeFile10(
19199
- path51.join(stagingRoot, "baseline-index.json"),
19390
+ path58.join(stagingRoot, "baseline-index.json"),
19200
19391
  `${JSON.stringify(baselineIndex)}
19201
19392
  `,
19202
19393
  "utf8"
@@ -19246,7 +19437,7 @@ async function prepareNonGitSnapshot(context) {
19246
19437
  const quotas = context.fileCheckpoint;
19247
19438
  const deadlineMs = Date.now() + quotas.prepareTimeoutMs;
19248
19439
  await removeDeadOwnerStaging(os5.tmpdir());
19249
- const stagingRoot = await mkdtemp5(path51.join(os5.tmpdir(), "belay-file-checkpoint-"));
19440
+ const stagingRoot = await mkdtemp5(path58.join(os5.tmpdir(), "belay-file-checkpoint-"));
19250
19441
  await writeOwnerMarker(stagingRoot, {
19251
19442
  version: 1,
19252
19443
  pid: process.pid,
@@ -19254,8 +19445,8 @@ async function prepareNonGitSnapshot(context) {
19254
19445
  resourceRoot: context.repoRoot,
19255
19446
  backend: "file_checkpoint"
19256
19447
  });
19257
- const baselineRoot = path51.join(stagingRoot, "baseline");
19258
- const executionRoot = path51.join(stagingRoot, "execution");
19448
+ const baselineRoot = path58.join(stagingRoot, "baseline");
19449
+ const executionRoot = path58.join(stagingRoot, "execution");
19259
19450
  try {
19260
19451
  resolveExecutionCwdRelative(context.repoRoot, context.cwd);
19261
19452
  const resourceIdentity = await currentRecoveryResourceIdentity(context.repoRoot, "directory");
@@ -19284,7 +19475,7 @@ async function prepareNonGitSnapshot(context) {
19284
19475
  throw new Error(FILE_CHECKPOINT_SOURCE_CHANGED);
19285
19476
  }
19286
19477
  await writeFile10(
19287
- path51.join(stagingRoot, "baseline-index.json"),
19478
+ path58.join(stagingRoot, "baseline-index.json"),
19288
19479
  `${JSON.stringify(baselineIndex)}
19289
19480
  `,
19290
19481
  "utf8"
@@ -19637,10 +19828,10 @@ async function selectTransactionalBackend(context) {
19637
19828
  }
19638
19829
 
19639
19830
  // src/core/transactional/diff-evaluator.ts
19640
- import path52 from "node:path";
19831
+ import path59 from "node:path";
19641
19832
  init_path_utils();
19642
19833
  function categorizeChange(change, ctx) {
19643
- const absolutePath = canonicalPath(path52.join(ctx.repoRoot, change.relativePath));
19834
+ const absolutePath = canonicalPath(path59.join(ctx.repoRoot, change.relativePath));
19644
19835
  if (!pathWithinRoot(ctx.repoRoot, absolutePath)) {
19645
19836
  return "repo_outside";
19646
19837
  }
@@ -20234,7 +20425,7 @@ async function notifyDeny(config, event) {
20234
20425
  init_path_utils();
20235
20426
 
20236
20427
  // src/adapters/layouts/protected-paths.ts
20237
- import path53 from "node:path";
20428
+ import path60 from "node:path";
20238
20429
  function protectedArtifactRoots(layout, repoRoot, controlPlaneDir) {
20239
20430
  const roots = [
20240
20431
  layout.configPath(repoRoot),
@@ -20246,7 +20437,7 @@ function protectedArtifactRoots(layout, repoRoot, controlPlaneDir) {
20246
20437
  if (controlPlaneDir) {
20247
20438
  roots.push(controlPlaneDir);
20248
20439
  }
20249
- return roots.map((entry) => path53.resolve(entry));
20440
+ return roots.map((entry) => path60.resolve(entry));
20250
20441
  }
20251
20442
 
20252
20443
  // src/adapters/shared/gate-runtime.ts
@@ -20299,8 +20490,8 @@ function createDefaultGateRuntimeDeps() {
20299
20490
  return loadJsonFile(configPath, {});
20300
20491
  },
20301
20492
  async appendAudit(ctx, event) {
20302
- const auditPath = path54.join(ctx.repoRoot, ctx.config.audit.logPath);
20303
- await mkdir14(path54.dirname(auditPath), { recursive: true });
20493
+ const auditPath = path61.join(ctx.repoRoot, ctx.config.audit.logPath);
20494
+ await mkdir14(path61.dirname(auditPath), { recursive: true });
20304
20495
  const provenance = auditProvenance(ctx.config);
20305
20496
  const record = {
20306
20497
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -20333,7 +20524,7 @@ function createDefaultGateRuntimeDeps() {
20333
20524
  };
20334
20525
  },
20335
20526
  async writeApprovals(filePath, state) {
20336
- await mkdir14(path54.dirname(filePath), { recursive: true });
20527
+ await mkdir14(path61.dirname(filePath), { recursive: true });
20337
20528
  await writeFile11(filePath, `${JSON.stringify(compactApprovals(state), null, 2)}
20338
20529
  `, "utf8");
20339
20530
  },
@@ -20495,7 +20686,7 @@ function deriveWorkspaceRootScopeHint(params) {
20495
20686
  if (!targetPath) {
20496
20687
  return void 0;
20497
20688
  }
20498
- const candidateRoot = canonicalPath(path54.dirname(targetPath));
20689
+ const candidateRoot = canonicalPath(path61.dirname(targetPath));
20499
20690
  const validation = validateTrustedWorkspaceRootCandidate({
20500
20691
  candidatePath: candidateRoot,
20501
20692
  repoRoot: action.repoRoot,
@@ -21071,6 +21262,7 @@ async function gateDecisionToVerdict(ctx, deps, kind, result, auditExtras = {})
21071
21262
  event: auditEvent,
21072
21263
  sourceEvent,
21073
21264
  kind,
21265
+ repoLabel: path61.basename(ctx.repoRoot),
21074
21266
  ...auditExtras.toolInvocationCorrelationId ? { toolInvocationCorrelationId: auditExtras.toolInvocationCorrelationId } : {},
21075
21267
  fingerprint: result.fingerprint,
21076
21268
  summary: result.normalizedCommand ?? result.summary ?? "",
@@ -21537,18 +21729,18 @@ async function appendObservedAudit(ctx, deps, eventName, payload) {
21537
21729
 
21538
21730
  // src/adapters/shared/repo-root.ts
21539
21731
  import { existsSync as existsSync17 } from "node:fs";
21540
- import path55 from "node:path";
21732
+ import path62 from "node:path";
21541
21733
  function belayConfigPath(current, adapterName) {
21542
21734
  if (adapterName === "cursor") {
21543
- return path55.join(current, ".cursor", "belay.config.json");
21735
+ return path62.join(current, ".cursor", "belay.config.json");
21544
21736
  }
21545
21737
  if (adapterName === "claude") {
21546
- return path55.join(current, ".claude", "belay.config.json");
21738
+ return path62.join(current, ".claude", "belay.config.json");
21547
21739
  }
21548
- return path55.join(current, ".codex", "belay.config.json");
21740
+ return path62.join(current, ".codex", "belay.config.json");
21549
21741
  }
21550
21742
  function markerMatches(current, marker, layout) {
21551
- const markerPath = path55.join(current, marker);
21743
+ const markerPath = path62.join(current, marker);
21552
21744
  if (!existsSync17(markerPath)) {
21553
21745
  return false;
21554
21746
  }
@@ -21558,16 +21750,16 @@ function markerMatches(current, marker, layout) {
21558
21750
  return true;
21559
21751
  }
21560
21752
  function findRepoRoot(startPath, layout) {
21561
- let current = path55.resolve(startPath);
21753
+ let current = path62.resolve(startPath);
21562
21754
  while (true) {
21563
21755
  for (const marker of layout.repoRootMarkers) {
21564
21756
  if (markerMatches(current, marker, layout)) {
21565
21757
  return current;
21566
21758
  }
21567
21759
  }
21568
- const parent = path55.dirname(current);
21760
+ const parent = path62.dirname(current);
21569
21761
  if (parent === current) {
21570
- return path55.resolve(startPath);
21762
+ return path62.resolve(startPath);
21571
21763
  }
21572
21764
  current = parent;
21573
21765
  }