@guilz-dev/belay 0.9.2 → 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 (118) hide show
  1. package/README.md +36 -2
  2. package/dist/adapters/codex/hooks.d.ts +1 -0
  3. package/dist/adapters/codex/hooks.js +3 -0
  4. package/dist/adapters/codex/runtime-entry.d.ts +3 -0
  5. package/dist/adapters/codex/runtime-entry.js +27 -4
  6. package/dist/adapters/cursor/cwd-resolution.d.ts +10 -0
  7. package/dist/adapters/cursor/cwd-resolution.js +58 -0
  8. package/dist/adapters/cursor/dispatcher-generation.d.ts +3 -0
  9. package/dist/adapters/cursor/dispatcher-generation.js +5 -0
  10. package/dist/adapters/cursor/hook-dispatch-entry.d.ts +7 -0
  11. package/dist/adapters/cursor/hook-dispatch-entry.js +126 -0
  12. package/dist/adapters/cursor/hook-router.d.ts +23 -0
  13. package/dist/adapters/cursor/hook-router.js +266 -0
  14. package/dist/adapters/cursor/hooks.d.ts +5 -0
  15. package/dist/adapters/cursor/hooks.js +115 -3
  16. package/dist/adapters/cursor/routing-layout.d.ts +5 -0
  17. package/dist/adapters/cursor/routing-layout.js +29 -0
  18. package/dist/adapters/cursor/runtime-entry.d.ts +18 -0
  19. package/dist/adapters/cursor/runtime-entry.js +135 -34
  20. package/dist/adapters/layouts/scope.d.ts +5 -0
  21. package/dist/adapters/layouts/scope.js +71 -0
  22. package/dist/adapters/shared/gate-runtime.js +27 -3
  23. package/dist/adapters/shared/repo-root.js +20 -1
  24. package/dist/bundle/claude-runtime.mjs +2660 -1951
  25. package/dist/bundle/codex-runtime.mjs +2680 -1953
  26. package/dist/bundle/cursor-dispatcher.mjs +443 -0
  27. package/dist/bundle/cursor-runtime.mjs +5638 -4697
  28. package/dist/cli.js +33 -3
  29. package/dist/commands/doctor.js +215 -16
  30. package/dist/commands/health-snapshot.d.ts +3 -0
  31. package/dist/commands/health-snapshot.js +61 -0
  32. package/dist/commands/recovery-checkpoints.d.ts +3 -3
  33. package/dist/commands/report.js +14 -0
  34. package/dist/commands/status.js +15 -0
  35. package/dist/commands/where.d.ts +4 -0
  36. package/dist/commands/where.js +52 -0
  37. package/dist/config-io.js +11 -2
  38. package/dist/core/approval-repo-lookup.d.ts +16 -0
  39. package/dist/core/approval-repo-lookup.js +48 -0
  40. package/dist/core/audit-io.d.ts +1 -1
  41. package/dist/core/audit-io.js +1 -1
  42. package/dist/core/audit-legacy-archive.js +43 -25
  43. package/dist/core/audit-query.d.ts +1 -0
  44. package/dist/core/audit-query.js +7 -0
  45. package/dist/core/audit-serialize.d.ts +3 -0
  46. package/dist/core/audit-serialize.js +39 -3
  47. package/dist/core/audit-summary.d.ts +9 -0
  48. package/dist/core/audit-summary.js +58 -1
  49. package/dist/core/audit-types.d.ts +4 -0
  50. package/dist/core/classify-tool.js +16 -8
  51. package/dist/core/effect-ir/argv-delegate.d.ts +9 -0
  52. package/dist/core/effect-ir/argv-delegate.js +42 -0
  53. package/dist/core/effect-ir/shell-lower/argv-delegate-gate.d.ts +3 -0
  54. package/dist/core/effect-ir/shell-lower/argv-delegate-gate.js +40 -0
  55. package/dist/core/effect-ir/shell-lower/augment.d.ts +4 -0
  56. package/dist/core/effect-ir/shell-lower/augment.js +97 -0
  57. package/dist/core/effect-ir/shell-lower/context.d.ts +10 -0
  58. package/dist/core/effect-ir/shell-lower/context.js +1 -0
  59. package/dist/core/effect-ir/shell-lower/decode-process.d.ts +9 -0
  60. package/dist/core/effect-ir/shell-lower/decode-process.js +201 -0
  61. package/dist/core/effect-ir/shell-lower/decoders/belay.d.ts +2 -0
  62. package/dist/core/effect-ir/shell-lower/decoders/belay.js +29 -0
  63. package/dist/core/effect-ir/shell-lower/decoders/builtins.d.ts +7 -0
  64. package/dist/core/effect-ir/shell-lower/decoders/builtins.js +194 -0
  65. package/dist/core/effect-ir/shell-lower/decoders/decode-package-exec.d.ts +4 -0
  66. package/dist/core/effect-ir/shell-lower/decoders/decode-package-exec.js +68 -0
  67. package/dist/core/effect-ir/shell-lower/decoders/docker.d.ts +3 -0
  68. package/dist/core/effect-ir/shell-lower/decoders/docker.js +43 -0
  69. package/dist/core/effect-ir/shell-lower/decoders/filesystem.d.ts +9 -0
  70. package/dist/core/effect-ir/shell-lower/decoders/filesystem.js +217 -0
  71. package/dist/core/effect-ir/shell-lower/decoders/prisma.d.ts +2 -0
  72. package/dist/core/effect-ir/shell-lower/decoders/prisma.js +54 -0
  73. package/dist/core/effect-ir/shell-lower/decoders/ruby.d.ts +11 -0
  74. package/dist/core/effect-ir/shell-lower/decoders/ruby.js +147 -0
  75. package/dist/core/effect-ir/shell-lower/decoders/toolchain.d.ts +7 -0
  76. package/dist/core/effect-ir/shell-lower/decoders/toolchain.js +155 -0
  77. package/dist/core/effect-ir/shell-lower/requirement.d.ts +12 -0
  78. package/dist/core/effect-ir/shell-lower/requirement.js +65 -0
  79. package/dist/core/effect-ir/shell-lower/segment.d.ts +11 -0
  80. package/dist/core/effect-ir/shell-lower/segment.js +82 -0
  81. package/dist/core/effect-ir/shell-lower/tokens.d.ts +26 -0
  82. package/dist/core/effect-ir/shell-lower/tokens.js +183 -0
  83. package/dist/core/effect-ir/shell-lower.d.ts +2 -7
  84. package/dist/core/effect-ir/shell-lower.js +122 -1511
  85. package/dist/core/glob.js +32 -16
  86. package/dist/core/integrity.d.ts +2 -2
  87. package/dist/core/integrity.js +52 -12
  88. package/dist/core/replay-scrub.d.ts +3 -0
  89. package/dist/core/replay-scrub.js +30 -3
  90. package/dist/core/shell-substitution.js +2 -2
  91. package/dist/core/shell-tokenizer.d.ts +28 -0
  92. package/dist/core/shell-tokenizer.js +111 -29
  93. package/dist/core/shell-unparseable.js +3 -1
  94. package/dist/core/verdict/docker-compose-run.d.ts +18 -0
  95. package/dist/core/verdict/docker-compose-run.js +136 -0
  96. package/dist/core/verdict/launcher-resolve.js +28 -28
  97. package/dist/core/verdict/parser.d.ts +3 -0
  98. package/dist/core/verdict/parser.js +23 -40
  99. package/dist/core/verdict/recursive-invocation.d.ts +20 -0
  100. package/dist/core/verdict/recursive-invocation.js +224 -0
  101. package/dist/defaults.d.ts +7 -3
  102. package/dist/defaults.js +35 -21
  103. package/dist/installer/bootstrap.d.ts +1 -0
  104. package/dist/installer/bootstrap.js +2 -2
  105. package/dist/installer/runtime-artifacts.js +43 -13
  106. package/dist/installer/scope-config.d.ts +2 -2
  107. package/dist/installer.d.ts +10 -1
  108. package/dist/installer.js +83 -7
  109. package/dist/node-resolution.d.ts +1 -0
  110. package/dist/node-resolution.js +61 -0
  111. package/dist/templates.d.ts +7 -4
  112. package/dist/templates.js +42 -4
  113. package/dist/types.d.ts +35 -1
  114. package/dist/version.d.ts +1 -1
  115. package/dist/version.js +1 -1
  116. package/package.json +5 -2
  117. package/skills/belay/SKILL.md +5 -0
  118. package/skills/belay/belay-report.md +4 -1
@@ -0,0 +1,43 @@
1
+ import { processRequirement, unsupportedProcess } from '../requirement.js';
2
+ import { isOptionToken } from '../tokens.js';
3
+ export function decodeDockerComposeRun(head, args, segment) {
4
+ let composeArgs = null;
5
+ let command = head;
6
+ if (head === 'docker-compose') {
7
+ composeArgs = args;
8
+ }
9
+ else if (head === 'docker' && args[0] === 'compose') {
10
+ composeArgs = args.slice(1);
11
+ command = 'docker';
12
+ }
13
+ if (!composeArgs) {
14
+ return null;
15
+ }
16
+ if (composeArgs.includes('run')) {
17
+ return [processRequirement(command, 'spawn', segment, ['process.docker_compose_run'])];
18
+ }
19
+ return unsupportedProcess(command, segment, 'process.docker_compose_grammar_incomplete');
20
+ }
21
+ export function validDockerInfo(args) {
22
+ for (let index = 0; index < args.length; index += 1) {
23
+ const arg = args[index] ?? '';
24
+ if (arg === '--help') {
25
+ continue;
26
+ }
27
+ if (arg === '--format' || arg === '-f') {
28
+ if (!args[index + 1] || isOptionToken(args[index + 1] ?? '')) {
29
+ return false;
30
+ }
31
+ index += 1;
32
+ continue;
33
+ }
34
+ if (arg.startsWith('--format=')) {
35
+ if (arg.slice('--format='.length).length === 0) {
36
+ return false;
37
+ }
38
+ continue;
39
+ }
40
+ return false;
41
+ }
42
+ return true;
43
+ }
@@ -0,0 +1,9 @@
1
+ import type { ShellEffectRequirement } from '../../shell-build.js';
2
+ export declare function decodeCopyMove(head: 'cp' | 'mv', args: string[], cwd: string, segment: string): ShellEffectRequirement[];
3
+ export declare function decodeRm(args: string[], cwd: string, repoRoot: string, segment: string): ShellEffectRequirement[];
4
+ export declare function canonicalRmOperand(targetPath: string, finalOperandIsSymlink: boolean): string;
5
+ export declare function isSymbolicLink(targetPath: string): boolean;
6
+ export declare function pathContains(ancestor: string, candidate: string): boolean;
7
+ export declare function filesystemReadOperands(head: string, args: string[]): string[] | null;
8
+ export declare function findReadOperands(args: string[]): string[] | null;
9
+ export declare function filesystemWriteOperands(head: string, args: string[]): string[] | null;
@@ -0,0 +1,217 @@
1
+ import { lstatSync, realpathSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { inspectGitResourceIdentity } from '../../../git-resource-identity.js';
4
+ import { addSecretRead, addWriteEffects, processRequirement, requirement } from '../requirement.js';
5
+ import { resolvePathOperand } from '../tokens.js';
6
+ export function decodeCopyMove(head, args, cwd, segment) {
7
+ const requirements = [processRequirement(head, 'spawn', segment, ['process.filesystem_mutation'])];
8
+ const operands = [];
9
+ let targetDirectory = null;
10
+ let recursive = false;
11
+ let incomplete = false;
12
+ let optionsEnded = false;
13
+ const neutralFlags = new Set([
14
+ '-f',
15
+ '-i',
16
+ '-n',
17
+ '-p',
18
+ '-v',
19
+ '--force',
20
+ '--interactive',
21
+ '--no-clobber',
22
+ ]);
23
+ for (let index = 0; index < args.length; index += 1) {
24
+ const arg = args[index] ?? '';
25
+ if (!optionsEnded && arg === '--') {
26
+ optionsEnded = true;
27
+ continue;
28
+ }
29
+ if (!optionsEnded && (arg === '-r' || arg === '-R' || arg === '--recursive')) {
30
+ recursive = true;
31
+ continue;
32
+ }
33
+ if (!optionsEnded && (arg === '-t' || arg === '--target-directory')) {
34
+ const value = args[index + 1];
35
+ if (!value || value.startsWith('-')) {
36
+ incomplete = true;
37
+ }
38
+ else {
39
+ targetDirectory = value;
40
+ index += 1;
41
+ }
42
+ continue;
43
+ }
44
+ if (!optionsEnded && arg.startsWith('--target-directory=')) {
45
+ targetDirectory = arg.slice('--target-directory='.length) || null;
46
+ incomplete ||= !targetDirectory;
47
+ continue;
48
+ }
49
+ if (!optionsEnded && arg.startsWith('-')) {
50
+ if (!neutralFlags.has(arg)) {
51
+ incomplete = true;
52
+ }
53
+ continue;
54
+ }
55
+ operands.push(arg);
56
+ }
57
+ const sources = targetDirectory ? operands : operands.slice(0, -1);
58
+ const destination = targetDirectory ?? (operands.length >= 2 ? (operands.at(-1) ?? null) : null);
59
+ if (sources.length === 0 || !destination) {
60
+ incomplete = true;
61
+ }
62
+ for (const source of sources) {
63
+ const resolved = resolvePathOperand(source, cwd);
64
+ requirements.push(requirement('fs.read', 'fs.read', { kind: 'path', path: resolved }, segment, [
65
+ `${head}.source_read`,
66
+ ]));
67
+ addSecretRead(requirements, resolved, segment);
68
+ if (head === 'mv') {
69
+ addWriteEffects(requirements, resolved, segment, ['mv.source_remove']);
70
+ }
71
+ }
72
+ if (destination) {
73
+ addWriteEffects(requirements, resolvePathOperand(destination, cwd), segment, [
74
+ `${head}.destination_write`,
75
+ ]);
76
+ }
77
+ if (recursive || incomplete) {
78
+ requirements.push(requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, segment, [
79
+ recursive ? `${head}.recursive_source` : `${head}.grammar_incomplete`,
80
+ ]));
81
+ }
82
+ return requirements;
83
+ }
84
+ export function decodeRm(args, cwd, repoRoot, segment) {
85
+ const requirements = [processRequirement('rm', 'spawn', segment, ['process.filesystem_mutation'])];
86
+ const recursive = args.some((arg) => arg === '-r' || arg === '-R' || arg === '--recursive' || /^-[^-]*[rR]/.test(arg));
87
+ const operands = args.filter((arg) => arg && !arg.startsWith('-'));
88
+ if (operands.length === 0) {
89
+ return [
90
+ ...requirements,
91
+ requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, segment, [
92
+ 'rm.operand_missing',
93
+ ]),
94
+ ];
95
+ }
96
+ const identity = inspectGitResourceIdentity(repoRoot);
97
+ for (const operand of operands) {
98
+ const resolved = resolvePathOperand(operand, cwd);
99
+ addWriteEffects(requirements, resolved, segment, ['filesystem.write', 'rm.remove']);
100
+ const finalOperandIsSymlink = isSymbolicLink(resolved);
101
+ const canonicalResolved = canonicalRmOperand(resolved, finalOperandIsSymlink);
102
+ const targetIdentity = recursive && !finalOperandIsSymlink
103
+ ? inspectGitResourceIdentity(resolved)
104
+ : { status: 'absent' };
105
+ const sameIdentityTargetRoot = identity.status === 'resolved' &&
106
+ targetIdentity.status === 'resolved' &&
107
+ identity.identity.commonDir === targetIdentity.identity.commonDir &&
108
+ pathContains(canonicalResolved, targetIdentity.identity.repositoryRoot);
109
+ if (recursive &&
110
+ identity.status === 'resolved' &&
111
+ (sameIdentityTargetRoot ||
112
+ [
113
+ repoRoot,
114
+ identity.identity.repositoryRoot,
115
+ identity.identity.gitDir,
116
+ identity.identity.commonDir,
117
+ identity.identity.gitEntryPath,
118
+ ].some((metadataPath) => pathContains(canonicalResolved, metadataPath)))) {
119
+ const gitEntryPath = targetIdentity.status === 'resolved'
120
+ ? targetIdentity.identity.gitEntryPath
121
+ : identity.identity.gitEntryPath;
122
+ requirements.push(requirement('control_plane.write', 'control_plane.write', { kind: 'path', path: gitEntryPath }, segment, ['rm.recursive_git_boundary', 'tier1_catastrophic']));
123
+ }
124
+ }
125
+ return requirements;
126
+ }
127
+ export function canonicalRmOperand(targetPath, finalOperandIsSymlink) {
128
+ try {
129
+ if (finalOperandIsSymlink) {
130
+ return path.join(realpathSync.native(path.dirname(targetPath)), path.basename(targetPath));
131
+ }
132
+ return realpathSync.native(targetPath);
133
+ }
134
+ catch {
135
+ return path.resolve(targetPath);
136
+ }
137
+ }
138
+ export function isSymbolicLink(targetPath) {
139
+ try {
140
+ return lstatSync(targetPath).isSymbolicLink();
141
+ }
142
+ catch {
143
+ return false;
144
+ }
145
+ }
146
+ export function pathContains(ancestor, candidate) {
147
+ const relative = path.relative(path.resolve(ancestor), path.resolve(candidate));
148
+ return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
149
+ }
150
+ export function filesystemReadOperands(head, args) {
151
+ switch (head) {
152
+ case 'ls': {
153
+ const operands = args.filter((arg) => arg && !arg.startsWith('-'));
154
+ return operands.length > 0 ? operands : ['.'];
155
+ }
156
+ case 'find':
157
+ return findReadOperands(args);
158
+ case 'cat':
159
+ case 'head':
160
+ case 'tail':
161
+ case 'less':
162
+ case 'stat':
163
+ case 'file':
164
+ case 'du':
165
+ case 'wc':
166
+ return args.filter((arg) => arg && !arg.startsWith('-'));
167
+ case 'grep':
168
+ case 'rg':
169
+ return args.slice(1).filter((arg) => arg && !arg.startsWith('-'));
170
+ case 'jq':
171
+ return args.slice(1).filter((arg) => arg && !arg.startsWith('-'));
172
+ default:
173
+ return null;
174
+ }
175
+ }
176
+ export function findReadOperands(args) {
177
+ const mutatingPrimaries = new Set([
178
+ '-delete',
179
+ '-exec',
180
+ '-execdir',
181
+ '-fls',
182
+ '-fprint',
183
+ '-fprintf',
184
+ '-ok',
185
+ '-okdir',
186
+ ]);
187
+ if (args.some((arg) => mutatingPrimaries.has(arg) ||
188
+ [...mutatingPrimaries].some((primary) => arg.startsWith(`${primary}=`)))) {
189
+ return null;
190
+ }
191
+ const paths = [];
192
+ for (const arg of args) {
193
+ if (arg.startsWith('-') || arg === '!' || arg === '(') {
194
+ break;
195
+ }
196
+ paths.push(arg);
197
+ }
198
+ return paths.length > 0 ? paths : ['.'];
199
+ }
200
+ export function filesystemWriteOperands(head, args) {
201
+ const positional = args.filter((arg) => arg && !arg.startsWith('-'));
202
+ switch (head) {
203
+ case 'touch':
204
+ case 'mkdir':
205
+ case 'rm':
206
+ case 'truncate':
207
+ case 'chmod':
208
+ return positional;
209
+ case 'cp':
210
+ case 'mv':
211
+ return positional.length > 0 ? [positional[positional.length - 1] ?? ''] : [];
212
+ case 'tee':
213
+ return positional;
214
+ default:
215
+ return null;
216
+ }
217
+ }
@@ -0,0 +1,2 @@
1
+ import type { ShellEffectRequirement } from '../../shell-build.js';
2
+ export declare function decodePrisma(args: string[], env: Readonly<Record<string, string | undefined>>, repoRoot: string, segment: string): ShellEffectRequirement[];
@@ -0,0 +1,54 @@
1
+ import { processRequirement, requirement } from '../requirement.js';
2
+ import { databaseEndpoint, LOOPBACK_HOSTS } from '../tokens.js';
3
+ export function decodePrisma(args, env, repoRoot, segment) {
4
+ const processEffect = processRequirement('prisma', 'spawn', segment, ['process.prisma']);
5
+ if (args[0] === 'generate') {
6
+ return [
7
+ processEffect,
8
+ requirement('fs.write', 'fs.write', { kind: 'path', path: repoRoot }, segment, [
9
+ 'prisma.generate.repo_local',
10
+ ]),
11
+ ];
12
+ }
13
+ const databaseMutation = args[0] === 'migrate' || (args[0] === 'db' && args[1] === 'seed') || args[0] === 'seed';
14
+ if (!databaseMutation) {
15
+ return [
16
+ processEffect,
17
+ requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, segment, [
18
+ 'prisma.grammar_incomplete',
19
+ ]),
20
+ ];
21
+ }
22
+ const endpoint = databaseEndpoint(env.DATABASE_URL);
23
+ if (!endpoint) {
24
+ return [
25
+ processEffect,
26
+ requirement('network.connect', 'network.connect', {
27
+ kind: 'network',
28
+ host: 'unknown',
29
+ protocol: 'database',
30
+ mode: 'ambiguous',
31
+ payload: 'present',
32
+ }, segment, ['prisma.database_endpoint_unknown']),
33
+ requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, segment, [
34
+ 'prisma.database_endpoint_unknown',
35
+ ]),
36
+ ];
37
+ }
38
+ const network = requirement('network.connect', 'network.connect', {
39
+ kind: 'network',
40
+ ...endpoint,
41
+ mode: 'mutate',
42
+ payload: 'present',
43
+ }, segment, [LOOPBACK_HOSTS.has(endpoint.host) ? 'prisma.database_local' : 'prisma.database_remote']);
44
+ if (LOOPBACK_HOSTS.has(endpoint.host)) {
45
+ return [processEffect, network];
46
+ }
47
+ return [
48
+ processEffect,
49
+ network,
50
+ requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, segment, [
51
+ 'prisma.remote_database_mutation',
52
+ ]),
53
+ ];
54
+ }
@@ -0,0 +1,11 @@
1
+ import type { ShellEffectRequirement } from '../../shell-build.js';
2
+ export declare function railsReadOnlySubcommand(args: string[]): boolean;
3
+ export declare function isRubyTestScript(scriptPath: string): boolean;
4
+ export declare function parseRubyTestInvocation(args: string[]): {
5
+ includePaths: string[];
6
+ scriptPath: string;
7
+ } | null;
8
+ export declare function isRubocopMutating(args: string[]): boolean;
9
+ export declare function decodeBundleExecInner(innerHead: string, innerArgs: string[], segment: string): ShellEffectRequirement[] | null;
10
+ export declare function decodeRuby(args: string[], cwd: string, repoRoot: string, segment: string): ShellEffectRequirement[];
11
+ export declare function decodeRuntimeMetadataProcess(head: string, args: string[], segment: string): ShellEffectRequirement[] | null;
@@ -0,0 +1,147 @@
1
+ import path from 'node:path';
2
+ import { canonicalPath, pathWithinRoot } from '../../../path-utils.js';
3
+ import { processRequirement, requirement, unsupportedProcess } from '../requirement.js';
4
+ import { executableBaseName, isMetadataOnlyArgv, resolvePathOperand } from '../tokens.js';
5
+ const RAILS_READ_ONLY_SUBCOMMANDS = new Set(['routes', 'middleware', 'stats', 'about', 'version']);
6
+ export function railsReadOnlySubcommand(args) {
7
+ const subcommand = args[0];
8
+ if (!subcommand || subcommand.startsWith('-')) {
9
+ return false;
10
+ }
11
+ return RAILS_READ_ONLY_SUBCOMMANDS.has(subcommand);
12
+ }
13
+ export function isRubyTestScript(scriptPath) {
14
+ const base = path.basename(scriptPath);
15
+ return base.endsWith('_test.rb') || base.endsWith('_spec.rb');
16
+ }
17
+ export function parseRubyTestInvocation(args) {
18
+ const includePaths = [];
19
+ for (let index = 0; index < args.length; index += 1) {
20
+ const arg = args[index] ?? '';
21
+ if (arg === '-e' || arg === '-r') {
22
+ return null;
23
+ }
24
+ if (arg === '-I') {
25
+ const includePath = args[index + 1];
26
+ if (!includePath) {
27
+ return null;
28
+ }
29
+ includePaths.push(includePath);
30
+ index += 1;
31
+ continue;
32
+ }
33
+ if (arg.startsWith('-I') && arg.length > 2) {
34
+ includePaths.push(arg.slice(2));
35
+ continue;
36
+ }
37
+ if (arg.startsWith('-')) {
38
+ if (arg === '-n') {
39
+ if (!args[index + 1]) {
40
+ return null;
41
+ }
42
+ index += 1;
43
+ continue;
44
+ }
45
+ if (arg.startsWith('-n')) {
46
+ continue;
47
+ }
48
+ return null;
49
+ }
50
+ if (isRubyTestScript(arg)) {
51
+ return { includePaths, scriptPath: arg };
52
+ }
53
+ return null;
54
+ }
55
+ return null;
56
+ }
57
+ export function isRubocopMutating(args) {
58
+ return args.some((arg) => arg === '-A' ||
59
+ arg === '-a' ||
60
+ arg === '--auto-correct' ||
61
+ arg === '--autocorrect' ||
62
+ arg.startsWith('--auto-correct-all') ||
63
+ arg.startsWith('--autocorrect-all'));
64
+ }
65
+ export function decodeBundleExecInner(innerHead, innerArgs, segment) {
66
+ const innerBase = executableBaseName(innerHead);
67
+ if (innerBase === 'rubocop') {
68
+ const mutating = isRubocopMutating(innerArgs);
69
+ return [
70
+ processRequirement(innerHead, mutating ? 'spawn' : 'inspect', segment, mutating ? ['process.linter.mutating'] : ['process.inspect.linter']),
71
+ ];
72
+ }
73
+ if (innerBase === 'rspec') {
74
+ const targetArgs = innerArgs.filter((arg) => !arg.startsWith('-'));
75
+ if (targetArgs.length === 0) {
76
+ return null;
77
+ }
78
+ return [processRequirement(innerHead, 'spawn', segment, ['process.test_runner.rspec'])];
79
+ }
80
+ return null;
81
+ }
82
+ export function decodeRuby(args, cwd, repoRoot, segment) {
83
+ const parsed = parseRubyTestInvocation(args);
84
+ if (!parsed) {
85
+ return unsupportedProcess('ruby', segment, 'process.ruby_grammar_incomplete');
86
+ }
87
+ const scriptPath = resolvePathOperand(parsed.scriptPath, cwd);
88
+ if (!pathWithinRoot(canonicalPath(repoRoot), canonicalPath(scriptPath))) {
89
+ return unsupportedProcess('ruby', segment, 'process.ruby_outside_repo');
90
+ }
91
+ for (const includePath of parsed.includePaths) {
92
+ const resolvedInclude = resolvePathOperand(includePath, cwd);
93
+ if (!pathWithinRoot(canonicalPath(repoRoot), canonicalPath(resolvedInclude))) {
94
+ return unsupportedProcess('ruby', segment, 'process.ruby_outside_repo');
95
+ }
96
+ }
97
+ const lowered = [
98
+ processRequirement('ruby', 'spawn', segment, ['process.test_runner.minitest']),
99
+ requirement('fs.read', 'fs.read', { kind: 'path', path: scriptPath }, segment, [
100
+ 'ruby.minitest_script_read',
101
+ ]),
102
+ ];
103
+ for (const includePath of parsed.includePaths) {
104
+ lowered.push(requirement('fs.read', 'fs.read', { kind: 'path', path: resolvePathOperand(includePath, cwd) }, segment, ['ruby.minitest_load_path_read']));
105
+ }
106
+ return lowered;
107
+ }
108
+ export function decodeRuntimeMetadataProcess(head, args, segment) {
109
+ if (head === 'bundle') {
110
+ if (args.length === 1 && isMetadataOnlyArgv(args)) {
111
+ return [processRequirement(head, 'inspect', segment, ['process.inspect.runtime_metadata'])];
112
+ }
113
+ if (args[0] === 'exec' && args.length >= 2) {
114
+ const innerHead = args[1] ?? '';
115
+ const innerArgs = args.slice(2);
116
+ const innerBase = executableBaseName(innerHead);
117
+ if ((innerBase === 'rails' || innerBase === 'rake') && railsReadOnlySubcommand(innerArgs)) {
118
+ return [
119
+ processRequirement(innerHead, 'inspect', segment, ['process.inspect.rails_read_only']),
120
+ ];
121
+ }
122
+ if (isMetadataOnlyArgv(innerArgs)) {
123
+ return [
124
+ processRequirement(innerHead, 'inspect', segment, [
125
+ 'process.inspect.bundle_exec_metadata',
126
+ ]),
127
+ ];
128
+ }
129
+ const bundleExecInner = decodeBundleExecInner(innerHead, innerArgs, segment);
130
+ if (bundleExecInner) {
131
+ return bundleExecInner;
132
+ }
133
+ }
134
+ return null;
135
+ }
136
+ if ((head === 'ruby' || head === 'yarn') && args.length >= 1 && isMetadataOnlyArgv(args)) {
137
+ return [processRequirement(head, 'inspect', segment, ['process.inspect.runtime_metadata'])];
138
+ }
139
+ if (head === 'make' && args.includes('-n')) {
140
+ return [processRequirement(head, 'inspect', segment, ['process.inspect.make_dry_run'])];
141
+ }
142
+ const base = executableBaseName(head);
143
+ if ((base === 'rails' || base === 'rake') && railsReadOnlySubcommand(args)) {
144
+ return [processRequirement(head, 'inspect', segment, ['process.inspect.rails_read_only'])];
145
+ }
146
+ return null;
147
+ }
@@ -0,0 +1,7 @@
1
+ import type { ShellEffectRequirement } from '../../shell-build.js';
2
+ export declare function decodeTsc(args: string[], cwd: string, segment: string): ShellEffectRequirement[];
3
+ export declare function decodeGo(args: string[], segment: string): ShellEffectRequirement[];
4
+ export declare function decodeRsync(args: string[], cwd: string, segment: string): ShellEffectRequirement[];
5
+ export declare function isRemoteRsyncOperand(operand: string): boolean;
6
+ export declare function decodeSed(args: string[], cwd: string, segment: string): ShellEffectRequirement[];
7
+ export declare function decodeNode(args: string[], cwd: string, segment: string): ShellEffectRequirement[];
@@ -0,0 +1,155 @@
1
+ import { addWriteEffects, processRequirement, requirement, unsupportedProcess, } from '../requirement.js';
2
+ import { resolvePathOperand } from '../tokens.js';
3
+ export function decodeTsc(args, cwd, segment) {
4
+ const metadataOnly = args.length > 0 && args.every((arg) => ['--help', '--version', '-h', '-v'].includes(arg));
5
+ const requirements = [];
6
+ let hasOutput = false;
7
+ for (let index = 0; index < args.length; index += 1) {
8
+ const arg = args[index] ?? '';
9
+ const inline = arg.match(/^--(?:outDir|outFile)=(.+)$/)?.[1];
10
+ const separate = (arg === '--outDir' || arg === '--outFile') && args[index + 1] ? args[index + 1] : null;
11
+ const output = inline ?? separate;
12
+ if (!output) {
13
+ continue;
14
+ }
15
+ hasOutput = true;
16
+ addWriteEffects(requirements, resolvePathOperand(output, cwd), segment, ['tsc.output']);
17
+ if (separate) {
18
+ index += 1;
19
+ }
20
+ }
21
+ const typecheckOnly = args.includes('--noEmit') && !hasOutput;
22
+ const inspect = metadataOnly || typecheckOnly;
23
+ requirements.unshift(processRequirement('tsc', inspect ? 'inspect' : 'spawn', segment, [
24
+ metadataOnly
25
+ ? 'process.inspect.tsc_metadata'
26
+ : typecheckOnly
27
+ ? 'process.inspect.tsc_typecheck'
28
+ : 'process.known_local_spawn',
29
+ ]));
30
+ return requirements;
31
+ }
32
+ export function decodeGo(args, segment) {
33
+ if (['test', 'list', 'vet'].includes(args[0] ?? '')) {
34
+ return [processRequirement('go', 'spawn', segment, ['process.go_local_spawn'])];
35
+ }
36
+ if (args[0] === 'install' || (args[0] === 'mod' && args[1] === 'download')) {
37
+ return [
38
+ processRequirement('go', 'spawn', segment, ['process.go_acquire']),
39
+ requirement('network.acquire', 'network.connect', {
40
+ kind: 'network',
41
+ host: 'unknown',
42
+ protocol: 'go-module',
43
+ mode: 'ambiguous',
44
+ payload: 'none',
45
+ }, segment, ['go.network_acquisition_possible']),
46
+ ];
47
+ }
48
+ return unsupportedProcess('go', segment, 'process.go_grammar_incomplete');
49
+ }
50
+ export function decodeRsync(args, cwd, segment) {
51
+ const destructive = args.some((arg) => arg === '--delete' || arg === '--del' || arg.startsWith('--delete-'));
52
+ if (destructive) {
53
+ return [
54
+ processRequirement('rsync', 'spawn', segment, ['rsync_destructive']),
55
+ requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, segment, [
56
+ 'rsync_destructive',
57
+ ]),
58
+ ];
59
+ }
60
+ const allowedFlags = new Set([
61
+ '-a',
62
+ '-r',
63
+ '-v',
64
+ '-z',
65
+ '--archive',
66
+ '--delay-updates',
67
+ '--recursive',
68
+ '--verbose',
69
+ ]);
70
+ if (args.some((arg) => arg.startsWith('-') && !allowedFlags.has(arg))) {
71
+ return unsupportedProcess('rsync', segment, 'process.rsync_grammar_incomplete');
72
+ }
73
+ const operands = args.filter((arg) => !arg.startsWith('-'));
74
+ if (operands.length < 2) {
75
+ return unsupportedProcess('rsync', segment, 'process.rsync_operands_incomplete');
76
+ }
77
+ if (operands.some(isRemoteRsyncOperand)) {
78
+ return [
79
+ processRequirement('rsync', 'spawn', segment, ['rsync.remote']),
80
+ requirement('network.connect', 'network.connect', {
81
+ kind: 'network',
82
+ host: 'remote',
83
+ protocol: 'rsync',
84
+ mode: 'mutate',
85
+ payload: 'present',
86
+ }, segment, ['rsync.remote']),
87
+ ];
88
+ }
89
+ const destination = operands.at(-1) ?? '';
90
+ return [
91
+ processRequirement('rsync', 'spawn', segment, ['process.rsync_local']),
92
+ ...operands
93
+ .slice(0, -1)
94
+ .map((operand) => requirement('fs.read', 'fs.read', { kind: 'path', path: resolvePathOperand(operand, cwd) }, segment, ['rsync.local_source'])),
95
+ requirement('fs.write', 'fs.write', { kind: 'path', path: resolvePathOperand(destination, cwd) }, segment, ['rsync.local_destination']),
96
+ ];
97
+ }
98
+ export function isRemoteRsyncOperand(operand) {
99
+ return (/^rsync:\/\//i.test(operand) ||
100
+ /^[^/:\s]+::/.test(operand) ||
101
+ /^(?:[^@/:\s]+@)?[^/:\s]+:/.test(operand));
102
+ }
103
+ export function decodeSed(args, cwd, segment) {
104
+ let inline = false;
105
+ let scriptConsumed = false;
106
+ const files = [];
107
+ for (let index = 0; index < args.length; index += 1) {
108
+ const arg = args[index] ?? '';
109
+ if (arg === '-n' || arg === '--quiet' || arg === '--silent') {
110
+ continue;
111
+ }
112
+ if (arg === '-i' || arg === '--in-place' || arg.startsWith('-i')) {
113
+ inline = true;
114
+ continue;
115
+ }
116
+ if (arg === '-e' || arg === '--expression') {
117
+ if (!args[index + 1]) {
118
+ return unsupportedProcess('sed', segment, 'process.sed_expression_missing');
119
+ }
120
+ scriptConsumed = true;
121
+ index += 1;
122
+ continue;
123
+ }
124
+ if (arg.startsWith('--expression=')) {
125
+ scriptConsumed = true;
126
+ continue;
127
+ }
128
+ if (arg.startsWith('-')) {
129
+ return unsupportedProcess('sed', segment, 'process.sed_grammar_incomplete');
130
+ }
131
+ if (!scriptConsumed) {
132
+ scriptConsumed = true;
133
+ continue;
134
+ }
135
+ files.push(arg);
136
+ }
137
+ const operation = inline ? 'spawn' : 'inspect';
138
+ const lowered = [processRequirement('sed', operation, segment, ['process.sed'])];
139
+ for (const file of files) {
140
+ lowered.push(requirement(inline ? 'fs.write' : 'fs.read', inline ? 'fs.write' : 'fs.read', { kind: 'path', path: resolvePathOperand(file, cwd) }, segment, [inline ? 'sed.in_place_write' : 'sed.file_read']));
141
+ }
142
+ return lowered;
143
+ }
144
+ export function decodeNode(args, cwd, segment) {
145
+ if (args.length > 0 && args.every((arg) => ['--help', '--version', '-h', '-v'].includes(arg))) {
146
+ return [processRequirement('node', 'inspect', segment, ['process.inspect.node_metadata'])];
147
+ }
148
+ if ((args[0] === '--check' || args[0] === '-c') && args.length === 2 && args[1]) {
149
+ return [
150
+ processRequirement('node', 'inspect', segment, ['process.inspect.node_syntax']),
151
+ requirement('fs.read', 'fs.read', { kind: 'path', path: resolvePathOperand(args[1], cwd) }, segment, ['node.syntax_source_read']),
152
+ ];
153
+ }
154
+ return unsupportedProcess('node', segment, 'process.node_grammar_incomplete');
155
+ }
@@ -0,0 +1,12 @@
1
+ import type { ShellEffectRequirement } from '../shell-build.js';
2
+ import type { EffectProvenance } from '../types.js';
3
+ export declare const SECRET_PATH_PATTERN: RegExp;
4
+ export declare const CONTROL_PLANE_PATH_PATTERN: RegExp;
5
+ export declare function requirement(tag: ShellEffectRequirement['tag'], action: ShellEffectRequirement['action'], resource: ShellEffectRequirement['resource'], segment: string, signals: string[]): ShellEffectRequirement;
6
+ export declare function processRequirement(command: string, operation: 'inspect' | 'spawn', segment: string, signals: string[]): ShellEffectRequirement;
7
+ export declare function unsupportedProcess(command: string, segment: string, signal: string): ShellEffectRequirement[];
8
+ export declare function addWriteEffects(requirements: ShellEffectRequirement[], resolved: string, segment: string, signals: string[]): void;
9
+ export declare function addSecretRead(requirements: ShellEffectRequirement[], resolved: string, segment: string): void;
10
+ export declare function ensureRequirement(requirements: ShellEffectRequirement[], candidate: ShellEffectRequirement): void;
11
+ export declare function withProvenances(requirementValue: ShellEffectRequirement, provenance: EffectProvenance, contributing: EffectProvenance): ShellEffectRequirement;
12
+ export declare function withInnerProvenance(requirementValue: ShellEffectRequirement, innerCommand: string, launcher: string, outerSegment: string): ShellEffectRequirement;