@guilz-dev/belay 0.9.1 → 0.9.2

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.
@@ -1,6 +1,7 @@
1
1
  import type { HookEntry, HooksFile } from '../../types.js';
2
- /** Legacy Belay-managed Shell preToolUse gate duplicated beforeShellExecution. */
3
- export declare function legacyManagedShellPreToolUseEntry(platform: NodeJS.Platform, hooksDir: string, repoRoot: string): HookEntry;
4
- export declare function removeLegacyManagedShellPreToolUse(hooks: HooksFile, platform: NodeJS.Platform, hooksDir: string, repoRoot: string): HooksFile;
2
+ /** Managed Shell preToolUse gate for Cursor Agent Shell tool invocations. */
3
+ export declare function managedShellPreToolUseEntry(platform: NodeJS.Platform, hooksDir: string, repoRoot: string): HookEntry;
4
+ /** @deprecated Use {@link managedShellPreToolUseEntry}. */
5
+ export declare const legacyManagedShellPreToolUseEntry: typeof managedShellPreToolUseEntry;
5
6
  export declare function mergeCursorHooksFile(current: HooksFile, platform: NodeJS.Platform, hooksDir: string, repoRoot: string): HooksFile;
6
7
  export declare function hasDuplicateCursorShellGates(hooks: HooksFile, platform: NodeJS.Platform, hooksDir: string, repoRoot: string): boolean;
@@ -10,37 +10,24 @@ function mergeHookEntry(current, expected, placement) {
10
10
  }
11
11
  return [...filtered, expected];
12
12
  }
13
- /** Legacy Belay-managed Shell preToolUse gate duplicated beforeShellExecution. */
14
- export function legacyManagedShellPreToolUseEntry(platform, hooksDir, repoRoot) {
13
+ /** Managed Shell preToolUse gate for Cursor Agent Shell tool invocations. */
14
+ export function managedShellPreToolUseEntry(platform, hooksDir, repoRoot) {
15
15
  const managed = getManagedHookEntries(platform, hooksDir, repoRoot);
16
16
  const referencePreToolUse = managed.find((entry) => entry.event === 'preToolUse' && entry.definition.matcher === 'Write')?.definition;
17
17
  if (!referencePreToolUse) {
18
- throw new Error('managed hook definitions missing for legacy Shell migration');
18
+ throw new Error('managed hook definitions missing for Shell preToolUse gate');
19
19
  }
20
20
  return {
21
21
  command: referencePreToolUse.command,
22
22
  matcher: 'Shell',
23
23
  };
24
24
  }
25
- export function removeLegacyManagedShellPreToolUse(hooks, platform, hooksDir, repoRoot) {
26
- const legacy = legacyManagedShellPreToolUseEntry(platform, hooksDir, repoRoot);
27
- const preToolUse = hooks.hooks.preToolUse;
28
- if (!Array.isArray(preToolUse)) {
29
- return hooks;
30
- }
31
- return {
32
- ...hooks,
33
- hooks: {
34
- ...hooks.hooks,
35
- preToolUse: preToolUse.filter((entry) => !entryMatches(entry, legacy)),
36
- },
37
- };
38
- }
25
+ /** @deprecated Use {@link managedShellPreToolUseEntry}. */
26
+ export const legacyManagedShellPreToolUseEntry = managedShellPreToolUseEntry;
39
27
  export function mergeCursorHooksFile(current, platform, hooksDir, repoRoot) {
40
- const withoutLegacyShell = removeLegacyManagedShellPreToolUse(current, platform, hooksDir, repoRoot);
41
28
  const next = {
42
- version: withoutLegacyShell.version || 1,
43
- hooks: { ...withoutLegacyShell.hooks },
29
+ version: current.version || 1,
30
+ hooks: { ...current.hooks },
44
31
  };
45
32
  const managedEntries = getManagedHookEntries(platform, hooksDir, repoRoot);
46
33
  for (const { event, definition } of managedEntries) {
@@ -52,10 +39,11 @@ export function mergeCursorHooksFile(current, platform, hooksDir, repoRoot) {
52
39
  return next;
53
40
  }
54
41
  export function hasDuplicateCursorShellGates(hooks, platform, hooksDir, repoRoot) {
55
- const legacy = legacyManagedShellPreToolUseEntry(platform, hooksDir, repoRoot);
42
+ const shellEntry = managedShellPreToolUseEntry(platform, hooksDir, repoRoot);
56
43
  const preToolUse = hooks.hooks.preToolUse;
57
44
  if (!Array.isArray(preToolUse)) {
58
45
  return false;
59
46
  }
60
- return preToolUse.some((entry) => entryMatches(entry, legacy));
47
+ const shellMatches = preToolUse.filter((entry) => entryMatches(entry, shellEntry));
48
+ return shellMatches.length > 1;
61
49
  }
@@ -11125,6 +11125,7 @@ function worstEffectDecision(decisions) {
11125
11125
 
11126
11126
  // src/core/effect-ir/shell-lower.ts
11127
11127
  init_git_resource_identity();
11128
+ init_path_utils();
11128
11129
  init_shell_tokenizer();
11129
11130
  import { lstatSync as lstatSync2, realpathSync as realpathSync4 } from "node:fs";
11130
11131
  import path39 from "node:path";
@@ -12855,6 +12856,159 @@ function gitRequirement(tag, action, resource, segment, signals) {
12855
12856
  // src/core/verdict/launcher-resolve.ts
12856
12857
  import { existsSync as existsSync11, readFileSync as readFileSync5 } from "node:fs";
12857
12858
  import path37 from "node:path";
12859
+
12860
+ // src/core/verdict/makefile-expand.ts
12861
+ var MAX_EXPAND_DEPTH = 16;
12862
+ function parseMakefileVariables(content) {
12863
+ const variables = /* @__PURE__ */ new Map();
12864
+ for (const line of content.split("\n")) {
12865
+ const trimmed = line.trim();
12866
+ if (!trimmed || trimmed.startsWith("#")) {
12867
+ continue;
12868
+ }
12869
+ const match = /^([A-Za-z_][A-Za-z0-9_]*)\s*[:?]?=\s*(.+)$/.exec(trimmed);
12870
+ if (!match) {
12871
+ continue;
12872
+ }
12873
+ variables.set(match[1] ?? "", (match[2] ?? "").trim());
12874
+ }
12875
+ return variables;
12876
+ }
12877
+ function parsePhonyTargets(content) {
12878
+ const phony = /* @__PURE__ */ new Set();
12879
+ for (const line of content.split("\n")) {
12880
+ const trimmed = line.trim();
12881
+ const match = /^\.PHONY:\s*(.+)$/.exec(trimmed);
12882
+ if (!match) {
12883
+ continue;
12884
+ }
12885
+ for (const token of (match[1] ?? "").split(/\s+/)) {
12886
+ if (token) {
12887
+ phony.add(token);
12888
+ }
12889
+ }
12890
+ }
12891
+ return phony;
12892
+ }
12893
+ function normalizeMakeRecipeLine(line) {
12894
+ let normalized = line.trim();
12895
+ while (normalized.startsWith("@") || normalized.startsWith("-") || normalized.startsWith("+")) {
12896
+ normalized = normalized.slice(1).trimStart();
12897
+ }
12898
+ return normalized;
12899
+ }
12900
+ function expandMakeExpression(expression, cliVars, makefileVars) {
12901
+ if (/\$\(\s*shell\b/i.test(expression) || expression.includes("$$")) {
12902
+ return null;
12903
+ }
12904
+ try {
12905
+ const expanded = expandMakeValue(expression, cliVars, makefileVars, 0);
12906
+ if (expanded === null || /\$\(/.test(expanded) || /\$\{/.test(expanded)) {
12907
+ return null;
12908
+ }
12909
+ return expanded;
12910
+ } catch {
12911
+ return null;
12912
+ }
12913
+ }
12914
+ function expandMakeValue(expression, cliVars, makefileVars, depth) {
12915
+ if (depth > MAX_EXPAND_DEPTH) {
12916
+ return null;
12917
+ }
12918
+ let value = expression.trim();
12919
+ let changed = true;
12920
+ let iterations = 0;
12921
+ while (changed && iterations < MAX_EXPAND_DEPTH) {
12922
+ changed = false;
12923
+ iterations += 1;
12924
+ const orMatch = value.match(/\$\(\s*or\s+([^()]*(?:\([^)]*\)[^()]*)*)\)/);
12925
+ if (orMatch) {
12926
+ const [fullMatch, inner] = orMatch;
12927
+ const parts = splitMakeFunctionArgs(inner ?? "");
12928
+ let selected = null;
12929
+ for (const part of parts) {
12930
+ const expanded = expandMakeValue(part.trim(), cliVars, makefileVars, depth + 1);
12931
+ if (expanded !== null && expanded.trim() !== "") {
12932
+ selected = expanded;
12933
+ break;
12934
+ }
12935
+ }
12936
+ if (selected === null) {
12937
+ const fallback = parts.at(-1)?.trim();
12938
+ selected = fallback === void 0 ? "" : expandMakeValue(fallback, cliVars, makefileVars, depth + 1);
12939
+ }
12940
+ if (selected === null) {
12941
+ return null;
12942
+ }
12943
+ value = value.replace(fullMatch, selected);
12944
+ changed = true;
12945
+ continue;
12946
+ }
12947
+ const varMatch = value.match(/\$\(([A-Za-z_][A-Za-z0-9_]*)\)/);
12948
+ if (varMatch) {
12949
+ const [fullMatch, name] = varMatch;
12950
+ const resolved = resolveMakeVariable(name ?? "", cliVars, makefileVars, depth + 1);
12951
+ if (resolved === null) {
12952
+ return null;
12953
+ }
12954
+ value = value.replace(fullMatch, resolved);
12955
+ changed = true;
12956
+ continue;
12957
+ }
12958
+ const bracedMatch = value.match(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/);
12959
+ if (bracedMatch) {
12960
+ const [fullMatch, name] = bracedMatch;
12961
+ const resolved = name === "PWD" ? "." : resolveMakeVariable(name ?? "", cliVars, makefileVars, depth + 1);
12962
+ if (resolved === null) {
12963
+ return null;
12964
+ }
12965
+ value = value.replace(fullMatch, resolved);
12966
+ changed = true;
12967
+ continue;
12968
+ }
12969
+ break;
12970
+ }
12971
+ return value;
12972
+ }
12973
+ function resolveMakeVariable(name, cliVars, makefileVars, depth) {
12974
+ if (Object.hasOwn(cliVars, name)) {
12975
+ return cliVars[name] ?? "";
12976
+ }
12977
+ const definition = makefileVars.get(name);
12978
+ if (definition === void 0) {
12979
+ return null;
12980
+ }
12981
+ return expandMakeValue(definition, cliVars, makefileVars, depth);
12982
+ }
12983
+ function splitMakeFunctionArgs(input) {
12984
+ const parts = [];
12985
+ let current = "";
12986
+ let depth = 0;
12987
+ for (const char of input) {
12988
+ if (char === "(") {
12989
+ depth += 1;
12990
+ current += char;
12991
+ continue;
12992
+ }
12993
+ if (char === ")") {
12994
+ depth -= 1;
12995
+ current += char;
12996
+ continue;
12997
+ }
12998
+ if (char === "," && depth === 0) {
12999
+ parts.push(current.trim());
13000
+ current = "";
13001
+ continue;
13002
+ }
13003
+ current += char;
13004
+ }
13005
+ if (current.trim()) {
13006
+ parts.push(current.trim());
13007
+ }
13008
+ return parts;
13009
+ }
13010
+
13011
+ // src/core/verdict/launcher-resolve.ts
12858
13012
  var MAX_RESOLVE_DEPTH = 8;
12859
13013
  var PNPM_BUILTIN_COMMANDS = /* @__PURE__ */ new Set([
12860
13014
  "add",
@@ -12995,10 +13149,9 @@ function resolveNpmRecipe(cwd, repoRoot, scriptName, extraArgs) {
12995
13149
  reason: "npm_script_resolved"
12996
13150
  };
12997
13151
  }
12998
- function parseMakefileRecipes(makefilePath) {
13152
+ function parseMakefileRecipeContent(content) {
12999
13153
  const targets = /* @__PURE__ */ new Map();
13000
13154
  try {
13001
- const content = readFileSync5(makefilePath, "utf8");
13002
13155
  const lines = content.split("\n");
13003
13156
  let currentTarget = null;
13004
13157
  let recipeLines = [];
@@ -13057,7 +13210,7 @@ function parseMakefileRecipes(makefilePath) {
13057
13210
  }
13058
13211
  return targets;
13059
13212
  }
13060
- function resolveMakeRecipe(cwd, repoRoot, target) {
13213
+ function resolveMakeRecipe(cwd, repoRoot, target, cliVars = {}) {
13061
13214
  const candidates = ["Makefile", "makefile", "GNUmakefile"];
13062
13215
  let makefilePath = null;
13063
13216
  let searchDir = path37.resolve(cwd);
@@ -13078,7 +13231,10 @@ function resolveMakeRecipe(cwd, repoRoot, target) {
13078
13231
  if (!makefilePath) {
13079
13232
  return { recipes: [], opaque: true, reason: "unknown_local_effect" };
13080
13233
  }
13081
- const targets = parseMakefileRecipes(makefilePath);
13234
+ const makefileContent = readFileSync5(makefilePath, "utf8");
13235
+ const makefileVars = parseMakefileVariables(makefileContent);
13236
+ const phonyTargets = parsePhonyTargets(makefileContent);
13237
+ const targets = parseMakefileRecipeContent(makefileContent);
13082
13238
  if (!targets.has(target)) {
13083
13239
  return { recipes: [], opaque: true, reason: "make_target_undefined" };
13084
13240
  }
@@ -13104,7 +13260,10 @@ function resolveMakeRecipe(cwd, repoRoot, target) {
13104
13260
  return false;
13105
13261
  }
13106
13262
  }
13107
- recipeLines.push(...entry.recipes);
13263
+ const skipPhonyPrerequisiteRecipes = name !== target && (phonyTargets.has(name) || name.startsWith("_")) && (targets.get(target)?.recipes.length ?? 0) > 0;
13264
+ if (!skipPhonyPrerequisiteRecipes) {
13265
+ recipeLines.push(...entry.recipes);
13266
+ }
13108
13267
  visiting.delete(name);
13109
13268
  visited.add(name);
13110
13269
  return true;
@@ -13112,15 +13271,24 @@ function resolveMakeRecipe(cwd, repoRoot, target) {
13112
13271
  if (!collect(target)) {
13113
13272
  return { recipes: recipeLines, opaque: true, reason: "make_dependency_cycle" };
13114
13273
  }
13274
+ const expandedRecipes = [];
13115
13275
  for (const line of recipeLines) {
13116
- if (/\$\(/.test(line) || /\$\{/.test(line)) {
13276
+ const normalized = normalizeMakeRecipeLine(line);
13277
+ const expanded = expandMakeExpression(normalized, cliVars, makefileVars);
13278
+ if (expanded === null) {
13117
13279
  return { recipes: recipeLines, opaque: true, reason: "make_recipe_dynamic" };
13118
13280
  }
13281
+ expandedRecipes.push(expanded);
13282
+ }
13283
+ for (const line of expandedRecipes) {
13284
+ if (/\$\(/.test(line) || /\$\{/.test(line)) {
13285
+ return { recipes: expandedRecipes, opaque: true, reason: "make_recipe_dynamic" };
13286
+ }
13119
13287
  }
13120
13288
  if (opaquePrerequisites) {
13121
- return { recipes: recipeLines, opaque: true, reason: "make_prerequisite_dynamic" };
13289
+ return { recipes: expandedRecipes, opaque: true, reason: "make_prerequisite_dynamic" };
13122
13290
  }
13123
- return { recipes: recipeLines, opaque: false, reason: "make_recipe_resolved" };
13291
+ return { recipes: expandedRecipes, opaque: false, reason: "make_recipe_resolved" };
13124
13292
  }
13125
13293
  function resolveLauncherRecipe(params) {
13126
13294
  if (params.depth >= MAX_RESOLVE_DEPTH) {
@@ -13144,8 +13312,28 @@ function resolveLauncherRecipe(params) {
13144
13312
  }
13145
13313
  return resolution;
13146
13314
  }
13147
- if (tokens[0] === "make" && tokens[1] && !tokens[1].startsWith("-")) {
13148
- return resolveMakeRecipe(params.cwd, params.repoRoot, tokens[1]);
13315
+ if (tokens[0] === "make") {
13316
+ if (tokens.includes("-n") || tokens.includes("--dry-run")) {
13317
+ return null;
13318
+ }
13319
+ let target = null;
13320
+ const cliVars = {};
13321
+ for (const token of tokens.slice(1)) {
13322
+ if (token.startsWith("-")) {
13323
+ continue;
13324
+ }
13325
+ const assignment = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/.exec(token);
13326
+ if (assignment) {
13327
+ cliVars[assignment[1] ?? ""] = assignment[2] ?? "";
13328
+ continue;
13329
+ }
13330
+ if (!target) {
13331
+ target = token;
13332
+ }
13333
+ }
13334
+ if (target) {
13335
+ return resolveMakeRecipe(params.cwd, params.repoRoot, target, cliVars);
13336
+ }
13149
13337
  }
13150
13338
  if (tokens[0] === "pnpm" && tokens[1] === "exec" && tokens[2]) {
13151
13339
  return {
@@ -13662,7 +13850,6 @@ function extractRecursiveScript(tokens) {
13662
13850
  return null;
13663
13851
  }
13664
13852
  const head = normalizeHead(filtered[0] ?? "");
13665
- const second = filtered[1] ?? "";
13666
13853
  if (head === "eval") {
13667
13854
  const body = filtered.slice(1).join(" ").trim();
13668
13855
  return body || null;
@@ -13670,12 +13857,33 @@ function extractRecursiveScript(tokens) {
13670
13857
  if (SHELL_INTERPRETERS.has(head) || CODE_INTERPRETERS.has(head)) {
13671
13858
  const flagIndex = filtered.findIndex((token) => SCRIPT_FLAGS.has(token));
13672
13859
  if (flagIndex !== -1) {
13673
- const body = filtered.slice(flagIndex + 1).join(" ").replace(/^['"]|['"]$/g, "").trim();
13860
+ const body = filtered[flagIndex + 1] ?? "";
13674
13861
  return body || null;
13675
13862
  }
13676
13863
  }
13677
- if (head === "bash" && (second === "-lc" || second === "-c")) {
13678
- const body = filtered.slice(2).join(" ").replace(/^['"]|['"]$/g, "").trim();
13864
+ return null;
13865
+ }
13866
+ function extractDockerComposeRunScript(tokens) {
13867
+ const head = normalizeHead(tokens[0] ?? "");
13868
+ const usesCompose = head === "docker-compose" || head === "docker" && (tokens[1] ?? "") === "compose";
13869
+ if (!usesCompose) {
13870
+ return null;
13871
+ }
13872
+ if (!tokens.includes("run")) {
13873
+ return null;
13874
+ }
13875
+ const runIndex = tokens.indexOf("run");
13876
+ const tail = tokens.slice(runIndex + 1);
13877
+ for (let index = 0; index < tail.length; index += 1) {
13878
+ const shellHead = normalizeHead(tail[index] ?? "");
13879
+ if (!SHELL_INTERPRETERS.has(shellHead)) {
13880
+ continue;
13881
+ }
13882
+ const flag = tail[index + 1] ?? "";
13883
+ if (flag !== "-lc" && flag !== "-c") {
13884
+ continue;
13885
+ }
13886
+ const body = tail[index + 2] ?? "";
13679
13887
  return body || null;
13680
13888
  }
13681
13889
  return null;
@@ -14039,9 +14247,10 @@ function lowerSegment(command, context) {
14039
14247
  const environment = extractEnvironment(rawTokens, context.env);
14040
14248
  const env = environment.env;
14041
14249
  const parsed = parseSegment(command);
14042
- const tokens = stripRedirects(environment.commandTokens ?? parsed.tokens).map(
14043
- (token) => expandKnownVariables(token, env)
14044
- );
14250
+ const parsedTokens = environment.commandTokens ?? parsed.tokens;
14251
+ const tokens = stripRedirects(
14252
+ parsedTokens.length === 0 && rawTokens.length > 0 && rawTokens.every((token) => ENV_PREFIX_PATTERN2.test(token)) ? rawTokens : parsedTokens
14253
+ ).map((token) => expandKnownVariables(token, env));
14045
14254
  const head = path39.basename(tokens[0] ?? parsed.head);
14046
14255
  let opacity = segmentOpacity(command);
14047
14256
  const signals = /* @__PURE__ */ new Set();
@@ -14143,6 +14352,31 @@ function lowerSegment(command, context) {
14143
14352
  }
14144
14353
  return shellSegment(commandRedacted, head, requirements, "recursive", signals);
14145
14354
  }
14355
+ const dockerComposeScript = extractDockerComposeRunScript(tokens);
14356
+ if (dockerComposeScript && opacity !== "opaque" && opacity !== "unparseable") {
14357
+ requirements.push(
14358
+ processRequirement(head, "spawn", commandRedacted, ["process.docker_compose_run"])
14359
+ );
14360
+ const nested = lowerTopLevelSegments(dockerComposeScript, {
14361
+ ...context,
14362
+ command: dockerComposeScript,
14363
+ env,
14364
+ depth: context.depth + 1
14365
+ });
14366
+ for (const nestedSegment of nested) {
14367
+ requirements.push(
14368
+ ...nestedSegment.requirements.map(
14369
+ (entry) => withInnerProvenance(entry, dockerComposeScript, head, commandRedacted)
14370
+ )
14371
+ );
14372
+ for (const signal of nestedSegment.signals) {
14373
+ signals.add(signal);
14374
+ }
14375
+ opacity = joinNestedOpacity(opacity, nestedSegment);
14376
+ }
14377
+ signals.add("process.docker_compose_run");
14378
+ return shellSegment(commandRedacted, head, requirements, "recursive", signals);
14379
+ }
14146
14380
  const launcher = resolveLauncherRecipe({
14147
14381
  tokens,
14148
14382
  cwd: context.cwd,
@@ -14364,6 +14598,111 @@ function railsReadOnlySubcommand(args) {
14364
14598
  }
14365
14599
  return RAILS_READ_ONLY_SUBCOMMANDS.has(subcommand);
14366
14600
  }
14601
+ function isRubyTestScript(scriptPath) {
14602
+ const base = path39.basename(scriptPath);
14603
+ return base.endsWith("_test.rb") || base.endsWith("_spec.rb");
14604
+ }
14605
+ function parseRubyTestInvocation(args) {
14606
+ const includePaths = [];
14607
+ for (let index = 0; index < args.length; index += 1) {
14608
+ const arg = args[index] ?? "";
14609
+ if (arg === "-e" || arg === "-r") {
14610
+ return null;
14611
+ }
14612
+ if (arg === "-I") {
14613
+ const includePath = args[index + 1];
14614
+ if (!includePath) {
14615
+ return null;
14616
+ }
14617
+ includePaths.push(includePath);
14618
+ index += 1;
14619
+ continue;
14620
+ }
14621
+ if (arg.startsWith("-I") && arg.length > 2) {
14622
+ includePaths.push(arg.slice(2));
14623
+ continue;
14624
+ }
14625
+ if (arg.startsWith("-")) {
14626
+ if (arg === "-n") {
14627
+ if (!args[index + 1]) {
14628
+ return null;
14629
+ }
14630
+ index += 1;
14631
+ continue;
14632
+ }
14633
+ if (arg.startsWith("-n")) {
14634
+ continue;
14635
+ }
14636
+ return null;
14637
+ }
14638
+ if (isRubyTestScript(arg)) {
14639
+ return { includePaths, scriptPath: arg };
14640
+ }
14641
+ return null;
14642
+ }
14643
+ return null;
14644
+ }
14645
+ function isRubocopMutating(args) {
14646
+ return args.some(
14647
+ (arg) => arg === "-A" || arg === "-a" || arg === "--auto-correct" || arg === "--autocorrect" || arg.startsWith("--auto-correct-all") || arg.startsWith("--autocorrect-all")
14648
+ );
14649
+ }
14650
+ function decodeBundleExecInner(innerHead, innerArgs, segment) {
14651
+ const innerBase = executableBaseName(innerHead);
14652
+ if (innerBase === "rubocop") {
14653
+ const mutating = isRubocopMutating(innerArgs);
14654
+ return [
14655
+ processRequirement(
14656
+ innerHead,
14657
+ mutating ? "spawn" : "inspect",
14658
+ segment,
14659
+ mutating ? ["process.linter.mutating"] : ["process.inspect.linter"]
14660
+ )
14661
+ ];
14662
+ }
14663
+ if (innerBase === "rspec") {
14664
+ const targetArgs = innerArgs.filter((arg) => !arg.startsWith("-"));
14665
+ if (targetArgs.length === 0) {
14666
+ return null;
14667
+ }
14668
+ return [processRequirement(innerHead, "spawn", segment, ["process.test_runner.rspec"])];
14669
+ }
14670
+ return null;
14671
+ }
14672
+ function decodeRuby(args, cwd, repoRoot, segment) {
14673
+ const parsed = parseRubyTestInvocation(args);
14674
+ if (!parsed) {
14675
+ return unsupportedProcess("ruby", segment, "process.ruby_grammar_incomplete");
14676
+ }
14677
+ const scriptPath = resolvePathOperand(parsed.scriptPath, cwd);
14678
+ if (!pathWithinRoot(canonicalPath(repoRoot), canonicalPath(scriptPath))) {
14679
+ return unsupportedProcess("ruby", segment, "process.ruby_outside_repo");
14680
+ }
14681
+ for (const includePath of parsed.includePaths) {
14682
+ const resolvedInclude = resolvePathOperand(includePath, cwd);
14683
+ if (!pathWithinRoot(canonicalPath(repoRoot), canonicalPath(resolvedInclude))) {
14684
+ return unsupportedProcess("ruby", segment, "process.ruby_outside_repo");
14685
+ }
14686
+ }
14687
+ const lowered = [
14688
+ processRequirement("ruby", "spawn", segment, ["process.test_runner.minitest"]),
14689
+ requirement2("fs.read", "fs.read", { kind: "path", path: scriptPath }, segment, [
14690
+ "ruby.minitest_script_read"
14691
+ ])
14692
+ ];
14693
+ for (const includePath of parsed.includePaths) {
14694
+ lowered.push(
14695
+ requirement2(
14696
+ "fs.read",
14697
+ "fs.read",
14698
+ { kind: "path", path: resolvePathOperand(includePath, cwd) },
14699
+ segment,
14700
+ ["ruby.minitest_load_path_read"]
14701
+ )
14702
+ );
14703
+ }
14704
+ return lowered;
14705
+ }
14367
14706
  function decodeRuntimeMetadataProcess(head, args, segment) {
14368
14707
  if (head === "bundle") {
14369
14708
  if (args.length === 1 && isMetadataOnlyArgv(args)) {
@@ -14385,6 +14724,10 @@ function decodeRuntimeMetadataProcess(head, args, segment) {
14385
14724
  ])
14386
14725
  ];
14387
14726
  }
14727
+ const bundleExecInner = decodeBundleExecInner(innerHead, innerArgs, segment);
14728
+ if (bundleExecInner) {
14729
+ return bundleExecInner;
14730
+ }
14388
14731
  }
14389
14732
  return null;
14390
14733
  }
@@ -14400,9 +14743,74 @@ function decodeRuntimeMetadataProcess(head, args, segment) {
14400
14743
  }
14401
14744
  return null;
14402
14745
  }
14746
+ function decodeSetBuiltin(args) {
14747
+ let index = 0;
14748
+ while (index < args.length) {
14749
+ const arg = args[index] ?? "";
14750
+ if (arg === "--") {
14751
+ index += 1;
14752
+ continue;
14753
+ }
14754
+ if (arg === "-o" || arg === "+o") {
14755
+ if (!args[index + 1]) {
14756
+ return false;
14757
+ }
14758
+ index += 2;
14759
+ continue;
14760
+ }
14761
+ if (/^[-+][A-Za-z0-9]+$/.test(arg)) {
14762
+ index += 1;
14763
+ continue;
14764
+ }
14765
+ return false;
14766
+ }
14767
+ return true;
14768
+ }
14769
+ function decodeShellControlBuiltin(head, args) {
14770
+ if (head === "set") {
14771
+ return decodeSetBuiltin(args) ? [] : null;
14772
+ }
14773
+ if (head === "wait") {
14774
+ if (args.length === 0 || args.every((arg) => /^\d+$/.test(arg))) {
14775
+ return [];
14776
+ }
14777
+ return null;
14778
+ }
14779
+ if (head === "exit") {
14780
+ if (args.length === 0 || args.length === 1 && /^-?\d+$/.test(args[0] ?? "")) {
14781
+ return [];
14782
+ }
14783
+ return null;
14784
+ }
14785
+ return null;
14786
+ }
14787
+ function decodeDockerComposeRun(head, args, segment) {
14788
+ let composeArgs = null;
14789
+ let command = head;
14790
+ if (head === "docker-compose") {
14791
+ composeArgs = args;
14792
+ } else if (head === "docker" && args[0] === "compose") {
14793
+ composeArgs = args.slice(1);
14794
+ command = "docker";
14795
+ }
14796
+ if (!composeArgs) {
14797
+ return null;
14798
+ }
14799
+ if (composeArgs.includes("run")) {
14800
+ return [processRequirement(command, "spawn", segment, ["process.docker_compose_run"])];
14801
+ }
14802
+ return unsupportedProcess(command, segment, "process.docker_compose_grammar_incomplete");
14803
+ }
14403
14804
  function decodeProcessOrFilesystem(params) {
14404
14805
  const { tokens, head, env, cwd, repoRoot, segment } = params;
14405
14806
  const args = tokens.slice(1);
14807
+ if (tokens.length > 0 && tokens.every((token) => ENV_PREFIX_PATTERN2.test(token))) {
14808
+ return [];
14809
+ }
14810
+ const shellControl = decodeShellControlBuiltin(head, args);
14811
+ if (shellControl) {
14812
+ return shellControl;
14813
+ }
14406
14814
  if (isCommandInspection(tokens)) {
14407
14815
  return [processRequirement(head, "inspect", segment, ["process.inspect.command_lookup"])];
14408
14816
  }
@@ -14485,6 +14893,19 @@ function decodeProcessOrFilesystem(params) {
14485
14893
  if (runtimeMetadata) {
14486
14894
  return runtimeMetadata;
14487
14895
  }
14896
+ if (head === "ruby") {
14897
+ return decodeRuby(args, cwd, repoRoot, segment);
14898
+ }
14899
+ if (head === "rubocop" || head === "rspec") {
14900
+ const decoded = decodeBundleExecInner(head, args, segment);
14901
+ if (decoded) {
14902
+ return decoded;
14903
+ }
14904
+ }
14905
+ const dockerCompose = decodeDockerComposeRun(head, args, segment);
14906
+ if (dockerCompose) {
14907
+ return dockerCompose;
14908
+ }
14488
14909
  if ((head === "npm" || head === "pnpm") && args.length === 1 && isMetadataOnlyArgv(args)) {
14489
14910
  return [processRequirement(head, "inspect", segment, ["process.inspect.package_manager"])];
14490
14911
  }
@@ -16723,7 +17144,7 @@ function hashDecisionConfig(config) {
16723
17144
  init_fingerprint2();
16724
17145
 
16725
17146
  // src/version.ts
16726
- var PACKAGE_VERSION = "0.9.1";
17147
+ var PACKAGE_VERSION = "0.9.2";
16727
17148
 
16728
17149
  // src/runtime-provenance.ts
16729
17150
  function resolveRuntimeArtifactHash(artifactHash) {