@guilz-dev/belay 0.9.3 → 0.10.0

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 (110) hide show
  1. package/README.md +38 -3
  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 +10 -0
  7. package/dist/adapters/cursor/hook-dispatch-entry.js +137 -0
  8. package/dist/adapters/cursor/hook-router.d.ts +23 -0
  9. package/dist/adapters/cursor/hook-router.js +273 -0
  10. package/dist/adapters/cursor/hooks.d.ts +5 -1
  11. package/dist/adapters/cursor/hooks.js +112 -10
  12. package/dist/adapters/cursor/routing-config-trust.d.ts +1 -0
  13. package/dist/adapters/cursor/routing-config-trust.js +67 -0
  14. package/dist/adapters/cursor/routing-layout.d.ts +5 -0
  15. package/dist/adapters/cursor/routing-layout.js +29 -0
  16. package/dist/adapters/cursor/runtime-entry.d.ts +17 -0
  17. package/dist/adapters/cursor/runtime-entry.js +74 -49
  18. package/dist/adapters/layouts/scope.d.ts +5 -0
  19. package/dist/adapters/layouts/scope.js +71 -0
  20. package/dist/adapters/shared/gate-runtime.js +29 -17
  21. package/dist/bundle/claude-runtime.mjs +2428 -2072
  22. package/dist/bundle/codex-runtime.mjs +2440 -2084
  23. package/dist/bundle/cursor-dispatcher.mjs +517 -0
  24. package/dist/bundle/cursor-runtime.mjs +2521 -2133
  25. package/dist/cli.js +53 -4
  26. package/dist/commands/approval-token.d.ts +10 -0
  27. package/dist/commands/approval-token.js +26 -0
  28. package/dist/commands/audit.d.ts +2 -1
  29. package/dist/commands/audit.js +2 -2
  30. package/dist/commands/config.d.ts +1 -1
  31. package/dist/commands/config.js +28 -2
  32. package/dist/commands/doctor.js +149 -17
  33. package/dist/commands/dogfood-check.d.ts +3 -0
  34. package/dist/commands/dogfood-check.js +116 -0
  35. package/dist/commands/dogfood.d.ts +1 -0
  36. package/dist/commands/dogfood.js +4 -3
  37. package/dist/commands/health-snapshot.js +9 -4
  38. package/dist/commands/judge.js +2 -2
  39. package/dist/commands/recovery-checkpoints.d.ts +3 -3
  40. package/dist/commands/recovery-checkpoints.js +2 -11
  41. package/dist/config-io.d.ts +1 -0
  42. package/dist/config-io.js +19 -3
  43. package/dist/core/audit-legacy-archive.js +43 -25
  44. package/dist/core/classify-tool.js +16 -8
  45. package/dist/core/dogfood-environment.d.ts +8 -0
  46. package/dist/core/dogfood-environment.js +55 -0
  47. package/dist/core/effect-ir/argv-delegate.d.ts +9 -0
  48. package/dist/core/effect-ir/argv-delegate.js +42 -0
  49. package/dist/core/effect-ir/shell-lower/argv-delegate-gate.d.ts +3 -0
  50. package/dist/core/effect-ir/shell-lower/argv-delegate-gate.js +40 -0
  51. package/dist/core/effect-ir/shell-lower/augment.d.ts +4 -0
  52. package/dist/core/effect-ir/shell-lower/augment.js +97 -0
  53. package/dist/core/effect-ir/shell-lower/context.d.ts +10 -0
  54. package/dist/core/effect-ir/shell-lower/context.js +1 -0
  55. package/dist/core/effect-ir/shell-lower/decode-process.d.ts +9 -0
  56. package/dist/core/effect-ir/shell-lower/decode-process.js +201 -0
  57. package/dist/core/effect-ir/shell-lower/decoders/belay.d.ts +2 -0
  58. package/dist/core/effect-ir/shell-lower/decoders/belay.js +41 -0
  59. package/dist/core/effect-ir/shell-lower/decoders/builtins.d.ts +7 -0
  60. package/dist/core/effect-ir/shell-lower/decoders/builtins.js +194 -0
  61. package/dist/core/effect-ir/shell-lower/decoders/decode-package-exec.d.ts +4 -0
  62. package/dist/core/effect-ir/shell-lower/decoders/decode-package-exec.js +68 -0
  63. package/dist/core/effect-ir/shell-lower/decoders/docker.d.ts +3 -0
  64. package/dist/core/effect-ir/shell-lower/decoders/docker.js +43 -0
  65. package/dist/core/effect-ir/shell-lower/decoders/filesystem.d.ts +9 -0
  66. package/dist/core/effect-ir/shell-lower/decoders/filesystem.js +217 -0
  67. package/dist/core/effect-ir/shell-lower/decoders/prisma.d.ts +2 -0
  68. package/dist/core/effect-ir/shell-lower/decoders/prisma.js +54 -0
  69. package/dist/core/effect-ir/shell-lower/decoders/ruby.d.ts +11 -0
  70. package/dist/core/effect-ir/shell-lower/decoders/ruby.js +147 -0
  71. package/dist/core/effect-ir/shell-lower/decoders/toolchain.d.ts +7 -0
  72. package/dist/core/effect-ir/shell-lower/decoders/toolchain.js +155 -0
  73. package/dist/core/effect-ir/shell-lower/requirement.d.ts +12 -0
  74. package/dist/core/effect-ir/shell-lower/requirement.js +65 -0
  75. package/dist/core/effect-ir/shell-lower/segment.d.ts +11 -0
  76. package/dist/core/effect-ir/shell-lower/segment.js +82 -0
  77. package/dist/core/effect-ir/shell-lower/tokens.d.ts +26 -0
  78. package/dist/core/effect-ir/shell-lower/tokens.js +183 -0
  79. package/dist/core/effect-ir/shell-lower.d.ts +2 -7
  80. package/dist/core/effect-ir/shell-lower.js +60 -1502
  81. package/dist/core/egress-approval.js +0 -18
  82. package/dist/core/glob.js +32 -16
  83. package/dist/core/integrity.d.ts +2 -2
  84. package/dist/core/integrity.js +52 -12
  85. package/dist/core/notify.d.ts +8 -2
  86. package/dist/core/notify.js +63 -7
  87. package/dist/core/recovery/operator-guidance.js +4 -4
  88. package/dist/core/replay-scrub.d.ts +2 -0
  89. package/dist/core/replay-scrub.js +8 -0
  90. package/dist/core/repo-config-trust.d.ts +31 -0
  91. package/dist/core/repo-config-trust.js +152 -0
  92. package/dist/core/shell-substitution.js +2 -2
  93. package/dist/core/shell-unparseable.js +3 -1
  94. package/dist/corpus/benign-probe-cores.d.ts +1 -1
  95. package/dist/corpus/benign-probe-cores.js +0 -1
  96. package/dist/defaults.d.ts +7 -3
  97. package/dist/defaults.js +29 -47
  98. package/dist/installer/bootstrap.d.ts +1 -0
  99. package/dist/installer/bootstrap.js +2 -2
  100. package/dist/installer/runtime-artifacts.js +43 -13
  101. package/dist/installer/scope-config.js +2 -2
  102. package/dist/installer.js +48 -13
  103. package/dist/node-resolution.d.ts +1 -0
  104. package/dist/node-resolution.js +61 -0
  105. package/dist/templates.d.ts +7 -4
  106. package/dist/templates.js +42 -4
  107. package/dist/types.d.ts +20 -0
  108. package/dist/version.d.ts +1 -1
  109. package/dist/version.js +1 -1
  110. package/package.json +5 -1
@@ -0,0 +1,68 @@
1
+ import { classifyPackageAcquisitionSpec, resolveLocalBin, } from '../../package-exec.js';
2
+ import { requirement } from '../requirement.js';
3
+ export function decodePackageExec(peel, cwd, repoRoot, segment) {
4
+ if (peel.opaque) {
5
+ return [
6
+ requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, segment, [
7
+ peel.reason,
8
+ ...peel.signals,
9
+ ]),
10
+ ];
11
+ }
12
+ if (peel.reason === 'npx_wrapper_readonly') {
13
+ return [];
14
+ }
15
+ const binHead = peel.innerTokens[0] ?? '';
16
+ const metadataOnly = packageExecInnerIsMetadata(peel);
17
+ const local = !peel.forceAcquire ? resolveLocalBin(binHead, cwd, repoRoot) : null;
18
+ if (local) {
19
+ return metadataOnly
20
+ ? [
21
+ requirement('process.exec', 'process.exec', { kind: 'executable', command: local.path, operation: 'inspect' }, segment, ['package_exec.delegate', 'package_exec.local_bin_resolved']),
22
+ ]
23
+ : [];
24
+ }
25
+ const networkResources = peel.acquisitionSpecs
26
+ .map((spec) => classifyPackageAcquisitionSpec(spec))
27
+ .filter((source) => source.kind !== 'local')
28
+ .map((source) => source.kind === 'registry'
29
+ ? {
30
+ kind: 'network',
31
+ host: 'registry.npmjs.org',
32
+ protocol: 'registry',
33
+ mode: 'read',
34
+ payload: 'none',
35
+ }
36
+ : {
37
+ kind: 'network',
38
+ host: source.host,
39
+ ...(source.port ? { port: source.port } : {}),
40
+ protocol: source.protocol,
41
+ mode: 'read',
42
+ payload: 'none',
43
+ });
44
+ if (networkResources.length === 0) {
45
+ networkResources.push({
46
+ kind: 'network',
47
+ host: 'registry.npmjs.org',
48
+ protocol: 'registry',
49
+ mode: 'read',
50
+ payload: 'none',
51
+ });
52
+ }
53
+ return [
54
+ ...networkResources.map((resource) => requirement('network.acquire', 'network.connect', resource, segment, [
55
+ 'package_acquire_possible',
56
+ ])),
57
+ requirement('fs.write', 'fs.write', { kind: 'package-cache', manager: peel.launcher === 'pnpm' ? 'pnpm' : 'npm' }, segment, ['package_cache_write']),
58
+ ...(metadataOnly
59
+ ? [
60
+ requirement('process.exec', 'process.exec', { kind: 'executable', command: binHead || peel.launcher, operation: 'inspect' }, segment, ['package_exec.delegate']),
61
+ ]
62
+ : []),
63
+ ];
64
+ }
65
+ export function packageExecInnerIsMetadata(peel) {
66
+ const args = peel.innerTokens.slice(1);
67
+ return (args.length > 0 && args.every((arg) => ['--help', '--version', '-V', '-h', '-v'].includes(arg)));
68
+ }
@@ -0,0 +1,3 @@
1
+ import type { ShellEffectRequirement } from '../../shell-build.js';
2
+ export declare function decodeDockerComposeRun(head: string, args: string[], segment: string): ShellEffectRequirement[] | null;
3
+ export declare function validDockerInfo(args: string[]): boolean;
@@ -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[];