@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.
- package/README.md +36 -2
- package/dist/adapters/codex/hooks.d.ts +1 -0
- package/dist/adapters/codex/hooks.js +3 -0
- package/dist/adapters/codex/runtime-entry.d.ts +3 -0
- package/dist/adapters/codex/runtime-entry.js +27 -4
- package/dist/adapters/cursor/cwd-resolution.d.ts +10 -0
- package/dist/adapters/cursor/cwd-resolution.js +58 -0
- package/dist/adapters/cursor/dispatcher-generation.d.ts +3 -0
- package/dist/adapters/cursor/dispatcher-generation.js +5 -0
- package/dist/adapters/cursor/hook-dispatch-entry.d.ts +7 -0
- package/dist/adapters/cursor/hook-dispatch-entry.js +126 -0
- package/dist/adapters/cursor/hook-router.d.ts +23 -0
- package/dist/adapters/cursor/hook-router.js +266 -0
- package/dist/adapters/cursor/hooks.d.ts +5 -0
- package/dist/adapters/cursor/hooks.js +115 -3
- package/dist/adapters/cursor/routing-layout.d.ts +5 -0
- package/dist/adapters/cursor/routing-layout.js +29 -0
- package/dist/adapters/cursor/runtime-entry.d.ts +18 -0
- package/dist/adapters/cursor/runtime-entry.js +135 -34
- package/dist/adapters/layouts/scope.d.ts +5 -0
- package/dist/adapters/layouts/scope.js +71 -0
- package/dist/adapters/shared/gate-runtime.js +27 -3
- package/dist/adapters/shared/repo-root.js +20 -1
- package/dist/bundle/claude-runtime.mjs +2660 -1951
- package/dist/bundle/codex-runtime.mjs +2680 -1953
- package/dist/bundle/cursor-dispatcher.mjs +443 -0
- package/dist/bundle/cursor-runtime.mjs +5638 -4697
- package/dist/cli.js +33 -3
- package/dist/commands/doctor.js +215 -16
- package/dist/commands/health-snapshot.d.ts +3 -0
- package/dist/commands/health-snapshot.js +61 -0
- package/dist/commands/recovery-checkpoints.d.ts +3 -3
- package/dist/commands/report.js +14 -0
- package/dist/commands/status.js +15 -0
- package/dist/commands/where.d.ts +4 -0
- package/dist/commands/where.js +52 -0
- package/dist/config-io.js +11 -2
- package/dist/core/approval-repo-lookup.d.ts +16 -0
- package/dist/core/approval-repo-lookup.js +48 -0
- package/dist/core/audit-io.d.ts +1 -1
- package/dist/core/audit-io.js +1 -1
- package/dist/core/audit-legacy-archive.js +43 -25
- package/dist/core/audit-query.d.ts +1 -0
- package/dist/core/audit-query.js +7 -0
- package/dist/core/audit-serialize.d.ts +3 -0
- package/dist/core/audit-serialize.js +39 -3
- package/dist/core/audit-summary.d.ts +9 -0
- package/dist/core/audit-summary.js +58 -1
- package/dist/core/audit-types.d.ts +4 -0
- package/dist/core/classify-tool.js +16 -8
- package/dist/core/effect-ir/argv-delegate.d.ts +9 -0
- package/dist/core/effect-ir/argv-delegate.js +42 -0
- package/dist/core/effect-ir/shell-lower/argv-delegate-gate.d.ts +3 -0
- package/dist/core/effect-ir/shell-lower/argv-delegate-gate.js +40 -0
- package/dist/core/effect-ir/shell-lower/augment.d.ts +4 -0
- package/dist/core/effect-ir/shell-lower/augment.js +97 -0
- package/dist/core/effect-ir/shell-lower/context.d.ts +10 -0
- package/dist/core/effect-ir/shell-lower/context.js +1 -0
- package/dist/core/effect-ir/shell-lower/decode-process.d.ts +9 -0
- package/dist/core/effect-ir/shell-lower/decode-process.js +201 -0
- package/dist/core/effect-ir/shell-lower/decoders/belay.d.ts +2 -0
- package/dist/core/effect-ir/shell-lower/decoders/belay.js +29 -0
- package/dist/core/effect-ir/shell-lower/decoders/builtins.d.ts +7 -0
- package/dist/core/effect-ir/shell-lower/decoders/builtins.js +194 -0
- package/dist/core/effect-ir/shell-lower/decoders/decode-package-exec.d.ts +4 -0
- package/dist/core/effect-ir/shell-lower/decoders/decode-package-exec.js +68 -0
- package/dist/core/effect-ir/shell-lower/decoders/docker.d.ts +3 -0
- package/dist/core/effect-ir/shell-lower/decoders/docker.js +43 -0
- package/dist/core/effect-ir/shell-lower/decoders/filesystem.d.ts +9 -0
- package/dist/core/effect-ir/shell-lower/decoders/filesystem.js +217 -0
- package/dist/core/effect-ir/shell-lower/decoders/prisma.d.ts +2 -0
- package/dist/core/effect-ir/shell-lower/decoders/prisma.js +54 -0
- package/dist/core/effect-ir/shell-lower/decoders/ruby.d.ts +11 -0
- package/dist/core/effect-ir/shell-lower/decoders/ruby.js +147 -0
- package/dist/core/effect-ir/shell-lower/decoders/toolchain.d.ts +7 -0
- package/dist/core/effect-ir/shell-lower/decoders/toolchain.js +155 -0
- package/dist/core/effect-ir/shell-lower/requirement.d.ts +12 -0
- package/dist/core/effect-ir/shell-lower/requirement.js +65 -0
- package/dist/core/effect-ir/shell-lower/segment.d.ts +11 -0
- package/dist/core/effect-ir/shell-lower/segment.js +82 -0
- package/dist/core/effect-ir/shell-lower/tokens.d.ts +26 -0
- package/dist/core/effect-ir/shell-lower/tokens.js +183 -0
- package/dist/core/effect-ir/shell-lower.d.ts +2 -7
- package/dist/core/effect-ir/shell-lower.js +122 -1511
- package/dist/core/glob.js +32 -16
- package/dist/core/integrity.d.ts +2 -2
- package/dist/core/integrity.js +52 -12
- package/dist/core/replay-scrub.d.ts +3 -0
- package/dist/core/replay-scrub.js +30 -3
- package/dist/core/shell-substitution.js +2 -2
- package/dist/core/shell-tokenizer.d.ts +28 -0
- package/dist/core/shell-tokenizer.js +111 -29
- package/dist/core/shell-unparseable.js +3 -1
- package/dist/core/verdict/docker-compose-run.d.ts +18 -0
- package/dist/core/verdict/docker-compose-run.js +136 -0
- package/dist/core/verdict/launcher-resolve.js +28 -28
- package/dist/core/verdict/parser.d.ts +3 -0
- package/dist/core/verdict/parser.js +23 -40
- package/dist/core/verdict/recursive-invocation.d.ts +20 -0
- package/dist/core/verdict/recursive-invocation.js +224 -0
- package/dist/defaults.d.ts +7 -3
- package/dist/defaults.js +35 -21
- package/dist/installer/bootstrap.d.ts +1 -0
- package/dist/installer/bootstrap.js +2 -2
- package/dist/installer/runtime-artifacts.js +43 -13
- package/dist/installer/scope-config.d.ts +2 -2
- package/dist/installer.d.ts +10 -1
- package/dist/installer.js +83 -7
- package/dist/node-resolution.d.ts +1 -0
- package/dist/node-resolution.js +61 -0
- package/dist/templates.d.ts +7 -4
- package/dist/templates.js +42 -4
- package/dist/types.d.ts +35 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -2
- package/skills/belay/SKILL.md +5 -0
- package/skills/belay/belay-report.md +4 -1
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export const SECRET_PATH_PATTERN = /(?:^|[/\\])(?:\.env(?:\..*)?|credentials?|secrets?|id_rsa)(?:$|[/\\])/i;
|
|
2
|
+
export const CONTROL_PLANE_PATH_PATTERN = /^(?:\/etc(?:\/|$)|\/var\/run(?:\/|$)|.*(?:^|[/\\])\.(?:git|ssh|cursor|claude)(?:[/\\]|$))/;
|
|
3
|
+
export function requirement(tag, action, resource, segment, signals) {
|
|
4
|
+
return {
|
|
5
|
+
tag,
|
|
6
|
+
action,
|
|
7
|
+
resource,
|
|
8
|
+
evidence: {
|
|
9
|
+
level: tag === 'indeterminate' ? 'indeterminate' : 'certain',
|
|
10
|
+
signals: [...new Set(signals)].sort(),
|
|
11
|
+
basis: ['shell_semantic_lowering'],
|
|
12
|
+
},
|
|
13
|
+
provenance: { segment },
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function processRequirement(command, operation, segment, signals) {
|
|
17
|
+
return requirement('process.exec', 'process.exec', { kind: 'executable', command, operation }, segment, signals);
|
|
18
|
+
}
|
|
19
|
+
export function unsupportedProcess(command, segment, signal) {
|
|
20
|
+
return [
|
|
21
|
+
processRequirement(command, 'spawn', segment, [signal]),
|
|
22
|
+
requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, segment, [signal]),
|
|
23
|
+
];
|
|
24
|
+
}
|
|
25
|
+
export function addWriteEffects(requirements, resolved, segment, signals) {
|
|
26
|
+
requirements.push(requirement('fs.write', 'fs.write', { kind: 'path', path: resolved }, segment, signals));
|
|
27
|
+
if (CONTROL_PLANE_PATH_PATTERN.test(resolved)) {
|
|
28
|
+
requirements.push(requirement('control_plane.write', 'control_plane.write', { kind: 'path', path: resolved }, segment, [...signals, 'protected_path']));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function addSecretRead(requirements, resolved, segment) {
|
|
32
|
+
if (SECRET_PATH_PATTERN.test(resolved)) {
|
|
33
|
+
requirements.push(requirement('secret.read', 'secret.read', { kind: 'path', path: resolved }, segment, [
|
|
34
|
+
'secret_path_read',
|
|
35
|
+
]));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export function ensureRequirement(requirements, candidate) {
|
|
39
|
+
if (!requirements.some((entry) => entry.action === candidate.action &&
|
|
40
|
+
JSON.stringify(entry.resource) === JSON.stringify(candidate.resource))) {
|
|
41
|
+
requirements.push(candidate);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export function withProvenances(requirementValue, provenance, contributing) {
|
|
45
|
+
const provenances = [
|
|
46
|
+
...(requirementValue.provenances ?? [requirementValue.provenance]),
|
|
47
|
+
contributing,
|
|
48
|
+
provenance,
|
|
49
|
+
].filter((candidate, index, all) => all.findIndex((entry) => JSON.stringify(entry) === JSON.stringify(candidate)) === index);
|
|
50
|
+
return { ...requirementValue, provenance, provenances };
|
|
51
|
+
}
|
|
52
|
+
export function withInnerProvenance(requirementValue, innerCommand, launcher, outerSegment) {
|
|
53
|
+
const provenance = {
|
|
54
|
+
...requirementValue.provenance,
|
|
55
|
+
segment: outerSegment,
|
|
56
|
+
innerCommand: requirementValue.provenance.innerCommand ?? innerCommand,
|
|
57
|
+
...(launcher === 'npm' || launcher === 'pnpm' ? { launcher, phase: 'delegate' } : {}),
|
|
58
|
+
};
|
|
59
|
+
const launcherProvenance = {
|
|
60
|
+
segment: outerSegment,
|
|
61
|
+
innerCommand,
|
|
62
|
+
...(launcher === 'npm' || launcher === 'pnpm' ? { launcher, phase: 'delegate' } : {}),
|
|
63
|
+
};
|
|
64
|
+
return withProvenances(requirementValue, provenance, launcherProvenance);
|
|
65
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ShellEffectRequirement, ShellEffectSegment } from '../shell-build.js';
|
|
2
|
+
import type { EffectPlan } from '../types.js';
|
|
3
|
+
export declare function requiresKnownCwd(requirementValue: ShellEffectRequirement): boolean;
|
|
4
|
+
export declare function joinNestedOpacity(outer: EffectPlan['opacity'], nested: ShellEffectSegment): EffectPlan['opacity'];
|
|
5
|
+
export declare function startsLocalPostgresService(command: string): boolean;
|
|
6
|
+
export declare function resolveCdTransition(command: string, currentCwd: string): {
|
|
7
|
+
cwd: string;
|
|
8
|
+
known: boolean;
|
|
9
|
+
} | null;
|
|
10
|
+
export declare function shellSegment(commandRedacted: string, segmentHead: string, requirements: ShellEffectRequirement[], opacity: EffectPlan['opacity'], signals: Set<string>): ShellEffectSegment;
|
|
11
|
+
export declare function dynamicResourceSignal(resource: ShellEffectRequirement['resource']): string | null;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { tokenizeShell } from '../../shell-tokenizer.js';
|
|
3
|
+
import { joinEffectOpacity } from '../normalize.js';
|
|
4
|
+
import { requirement } from './requirement.js';
|
|
5
|
+
import { resolvePathOperand } from './tokens.js';
|
|
6
|
+
const DYNAMIC_SHELL_VALUE_PATTERN = /(?:\$\(|`|\$(?:\d+|[@*#?$!-]|\{[^}]*\}|[A-Za-z_][A-Za-z0-9_]*))/;
|
|
7
|
+
const SHELL_GLOB_PATTERN = /[*?[]/;
|
|
8
|
+
export function requiresKnownCwd(requirementValue) {
|
|
9
|
+
if (requirementValue.action === 'fs.write' ||
|
|
10
|
+
requirementValue.action === 'git.ref.write' ||
|
|
11
|
+
requirementValue.action === 'control_plane.write') {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
return (requirementValue.action === 'process.exec' &&
|
|
15
|
+
requirementValue.resource.kind === 'executable' &&
|
|
16
|
+
requirementValue.resource.operation === 'spawn');
|
|
17
|
+
}
|
|
18
|
+
export function joinNestedOpacity(outer, nested) {
|
|
19
|
+
const nestedOpacity = nested.completeness === 'partial' && nested.opacity === 'transparent'
|
|
20
|
+
? 'recursive'
|
|
21
|
+
: nested.opacity;
|
|
22
|
+
return joinEffectOpacity(outer, nestedOpacity);
|
|
23
|
+
}
|
|
24
|
+
export function startsLocalPostgresService(command) {
|
|
25
|
+
const tokens = tokenizeShell(command);
|
|
26
|
+
return (path.basename(tokens[0] ?? '') === 'docker' &&
|
|
27
|
+
tokens[1] === 'compose' &&
|
|
28
|
+
['up', 'start', 'restart'].includes(tokens[2] ?? '') &&
|
|
29
|
+
tokens.includes('postgres'));
|
|
30
|
+
}
|
|
31
|
+
export function resolveCdTransition(command, currentCwd) {
|
|
32
|
+
const tokens = tokenizeShell(command);
|
|
33
|
+
if (path.basename(tokens[0] ?? '') !== 'cd') {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
const target = tokens[1] ?? '~';
|
|
37
|
+
if (!target || target === '-' || target.includes('$') || target.includes('`')) {
|
|
38
|
+
return { cwd: currentCwd, known: false };
|
|
39
|
+
}
|
|
40
|
+
return { cwd: resolvePathOperand(target, currentCwd), known: true };
|
|
41
|
+
}
|
|
42
|
+
export function shellSegment(commandRedacted, segmentHead, requirements, opacity, signals) {
|
|
43
|
+
const normalizedRequirements = requirements.flatMap((entry) => {
|
|
44
|
+
const dynamicSignal = dynamicResourceSignal(entry.resource);
|
|
45
|
+
if (dynamicSignal) {
|
|
46
|
+
return [
|
|
47
|
+
{ ...entry, resource: { kind: 'unknown' } },
|
|
48
|
+
requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, entry.provenance.segment ?? commandRedacted, [...entry.evidence.signals, dynamicSignal]),
|
|
49
|
+
];
|
|
50
|
+
}
|
|
51
|
+
return [entry];
|
|
52
|
+
});
|
|
53
|
+
const partial = normalizedRequirements.some((entry) => entry.tag === 'indeterminate' || entry.action === 'indeterminate');
|
|
54
|
+
return {
|
|
55
|
+
commandRedacted,
|
|
56
|
+
segmentHead,
|
|
57
|
+
requirements: normalizedRequirements,
|
|
58
|
+
completeness: partial ? 'partial' : 'complete',
|
|
59
|
+
opacity,
|
|
60
|
+
signals: [...signals].sort(),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
export function dynamicResourceSignal(resource) {
|
|
64
|
+
const expanded = (value) => Boolean(value && DYNAMIC_SHELL_VALUE_PATTERN.test(value));
|
|
65
|
+
const pathLike = (value, allowSemanticWildcard = false) => Boolean(value && (expanded(value) || (!allowSemanticWildcard && SHELL_GLOB_PATTERN.test(value))));
|
|
66
|
+
switch (resource.kind) {
|
|
67
|
+
case 'path':
|
|
68
|
+
return pathLike(resource.path) ? 'shell.path_unresolved' : null;
|
|
69
|
+
case 'network':
|
|
70
|
+
return expanded(resource.host) || expanded(resource.protocol)
|
|
71
|
+
? 'shell.network_resource_unresolved'
|
|
72
|
+
: null;
|
|
73
|
+
case 'git-ref':
|
|
74
|
+
return pathLike(resource.repoPath) || pathLike(resource.ref, resource.ref.endsWith('/*'))
|
|
75
|
+
? 'shell.git_resource_unresolved'
|
|
76
|
+
: null;
|
|
77
|
+
case 'executable':
|
|
78
|
+
return expanded(resource.command) ? 'shell.executable_unresolved' : null;
|
|
79
|
+
default:
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type ShellToken } from '../../shell-tokenizer.js';
|
|
2
|
+
export declare const ENV_PREFIX_PATTERN: RegExp;
|
|
3
|
+
export declare const LOOPBACK_HOSTS: Set<string>;
|
|
4
|
+
export declare const METADATA_ONLY_FLAGS: Set<string>;
|
|
5
|
+
export interface EnvironmentExtraction {
|
|
6
|
+
env: Readonly<Record<string, string | undefined>>;
|
|
7
|
+
commandTokens?: string[];
|
|
8
|
+
malformed: boolean;
|
|
9
|
+
changedNames: ReadonlySet<string>;
|
|
10
|
+
}
|
|
11
|
+
export declare function extractEnvironment(tokens: string[], inherited: Readonly<Record<string, string | undefined>> | undefined): EnvironmentExtraction;
|
|
12
|
+
export declare function expandKnownVariables(token: string, env: Readonly<Record<string, string | undefined>>): string;
|
|
13
|
+
export declare function stripRedirects(tokens: string[]): string[];
|
|
14
|
+
export declare function stripStructuredRedirects(tokens: readonly ShellToken[]): ShellToken[];
|
|
15
|
+
export declare function alignStructuredTokens(tokens: readonly ShellToken[], values: readonly string[]): ShellToken[];
|
|
16
|
+
export declare function resolvePathOperand(operand: string, cwd: string): string;
|
|
17
|
+
export declare function isOptionToken(value: string): boolean;
|
|
18
|
+
export declare function isShellHead(head: string): boolean;
|
|
19
|
+
export declare function pipeToShell(command: string): boolean;
|
|
20
|
+
export declare function executableBaseName(head: string): string;
|
|
21
|
+
export declare function isMetadataOnlyArgv(argv: string[]): boolean;
|
|
22
|
+
export declare function databaseEndpoint(raw: string | undefined): {
|
|
23
|
+
host: string;
|
|
24
|
+
protocol: string;
|
|
25
|
+
port?: number;
|
|
26
|
+
} | null;
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { isFdDuplication, isRedirectOperator } from '../../shell-tokenizer.js';
|
|
3
|
+
export const ENV_PREFIX_PATTERN = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
|
|
4
|
+
export const LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '::1', '0:0:0:0:0:0:0:1']);
|
|
5
|
+
export const METADATA_ONLY_FLAGS = new Set(['--version', '-v', '-V', '--help', '-h']);
|
|
6
|
+
export function extractEnvironment(tokens, inherited) {
|
|
7
|
+
const env = { ...inherited };
|
|
8
|
+
const changedNames = new Set();
|
|
9
|
+
if (tokens[0] === 'env') {
|
|
10
|
+
let index = 1;
|
|
11
|
+
let malformed = false;
|
|
12
|
+
while (index < tokens.length) {
|
|
13
|
+
const option = tokens[index] ?? '';
|
|
14
|
+
if (option === '-u' || option === '--unset') {
|
|
15
|
+
const name = tokens[index + 1] ?? '';
|
|
16
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
|
|
17
|
+
malformed = true;
|
|
18
|
+
index += 1;
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
delete env[name];
|
|
22
|
+
changedNames.add(name);
|
|
23
|
+
index += 2;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (option.startsWith('--unset=')) {
|
|
27
|
+
const name = option.slice('--unset='.length);
|
|
28
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
|
|
29
|
+
malformed = true;
|
|
30
|
+
index += 1;
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
delete env[name];
|
|
34
|
+
changedNames.add(name);
|
|
35
|
+
index += 1;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (option === '-i' || option === '--ignore-environment') {
|
|
39
|
+
for (const name of Object.keys(env)) {
|
|
40
|
+
delete env[name];
|
|
41
|
+
}
|
|
42
|
+
changedNames.add('*');
|
|
43
|
+
index += 1;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (option === '--') {
|
|
47
|
+
index += 1;
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
if (option.startsWith('-')) {
|
|
51
|
+
malformed = true;
|
|
52
|
+
index += 1;
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
while (index < tokens.length) {
|
|
58
|
+
const token = tokens[index] ?? '';
|
|
59
|
+
const match = ENV_PREFIX_PATTERN.exec(token);
|
|
60
|
+
if (!match) {
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
const [, name, value] = match;
|
|
64
|
+
if (name) {
|
|
65
|
+
env[name] = expandKnownVariables(value ?? '', env);
|
|
66
|
+
changedNames.add(name);
|
|
67
|
+
}
|
|
68
|
+
index += 1;
|
|
69
|
+
}
|
|
70
|
+
const commandTokens = tokens.slice(index);
|
|
71
|
+
return {
|
|
72
|
+
env,
|
|
73
|
+
commandTokens,
|
|
74
|
+
malformed: malformed || commandTokens.length === 0,
|
|
75
|
+
changedNames,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
for (const token of tokens) {
|
|
79
|
+
const match = ENV_PREFIX_PATTERN.exec(token);
|
|
80
|
+
if (!match) {
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
const [, name, value] = match;
|
|
84
|
+
if (name) {
|
|
85
|
+
env[name] = expandKnownVariables(value ?? '', env);
|
|
86
|
+
changedNames.add(name);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return { env, malformed: false, changedNames };
|
|
90
|
+
}
|
|
91
|
+
export function expandKnownVariables(token, env) {
|
|
92
|
+
return token.replace(/\$(?:\{([A-Za-z_][A-Za-z0-9_]*)\}|([A-Za-z_][A-Za-z0-9_]*))/g, (raw, a, b) => {
|
|
93
|
+
const value = env[a ?? b];
|
|
94
|
+
return value === undefined ? raw : value;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
export function stripRedirects(tokens) {
|
|
98
|
+
const stripped = [];
|
|
99
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
100
|
+
const token = tokens[index] ?? '';
|
|
101
|
+
if (isFdDuplication(token)) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (!isRedirectOperator(token)) {
|
|
105
|
+
stripped.push(token);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
index += 1;
|
|
109
|
+
}
|
|
110
|
+
return stripped;
|
|
111
|
+
}
|
|
112
|
+
export function stripStructuredRedirects(tokens) {
|
|
113
|
+
const stripped = [];
|
|
114
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
115
|
+
const token = tokens[index];
|
|
116
|
+
if (!token)
|
|
117
|
+
continue;
|
|
118
|
+
if (isFdDuplication(token.value)) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (!isRedirectOperator(token.value)) {
|
|
122
|
+
stripped.push(token);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
index += 1;
|
|
126
|
+
}
|
|
127
|
+
return stripped;
|
|
128
|
+
}
|
|
129
|
+
export function alignStructuredTokens(tokens, values) {
|
|
130
|
+
if (values.length === 0)
|
|
131
|
+
return [];
|
|
132
|
+
for (let start = tokens.length - values.length; start >= 0; start -= 1) {
|
|
133
|
+
const candidate = tokens.slice(start);
|
|
134
|
+
if (candidate.length === values.length &&
|
|
135
|
+
candidate.every((token, index) => token.value === values[index])) {
|
|
136
|
+
return candidate;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
export function resolvePathOperand(operand, cwd) {
|
|
142
|
+
if (operand === '~') {
|
|
143
|
+
return process.env.HOME ?? operand;
|
|
144
|
+
}
|
|
145
|
+
if (operand.startsWith('~/')) {
|
|
146
|
+
return path.join(process.env.HOME ?? '~', operand.slice(2));
|
|
147
|
+
}
|
|
148
|
+
return path.resolve(cwd, operand);
|
|
149
|
+
}
|
|
150
|
+
export function isOptionToken(value) {
|
|
151
|
+
return value.startsWith('-') || value.startsWith('+');
|
|
152
|
+
}
|
|
153
|
+
export function isShellHead(head) {
|
|
154
|
+
return head === 'bash' || head === 'sh' || head === 'zsh' || head === 'dash' || head === 'fish';
|
|
155
|
+
}
|
|
156
|
+
export function pipeToShell(command) {
|
|
157
|
+
return /(?:^|[|;&]\s*)(?:bash|sh|zsh|dash|fish)(?:\s|$)/.test(command) && /\|/.test(command);
|
|
158
|
+
}
|
|
159
|
+
export function executableBaseName(head) {
|
|
160
|
+
return path.basename(head);
|
|
161
|
+
}
|
|
162
|
+
export function isMetadataOnlyArgv(argv) {
|
|
163
|
+
return argv.length > 0 && argv.every((token) => METADATA_ONLY_FLAGS.has(token));
|
|
164
|
+
}
|
|
165
|
+
export function databaseEndpoint(raw) {
|
|
166
|
+
if (!raw || raw.includes('$')) {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
try {
|
|
170
|
+
const url = new URL(raw);
|
|
171
|
+
if (!url.hostname || !url.protocol) {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
host: url.hostname.toLowerCase(),
|
|
176
|
+
protocol: url.protocol.slice(0, -1),
|
|
177
|
+
...(url.port ? { port: Number(url.port) } : {}),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -1,11 +1,6 @@
|
|
|
1
|
+
import type { LowerShellEffectPlanParams } from './shell-lower/context.js';
|
|
1
2
|
import type { EffectPlan } from './types.js';
|
|
2
|
-
export
|
|
3
|
-
command: string;
|
|
4
|
-
cwd: string;
|
|
5
|
-
repoRoot: string;
|
|
6
|
-
inputFingerprint: string;
|
|
7
|
-
env?: Readonly<Record<string, string | undefined>>;
|
|
8
|
-
}
|
|
3
|
+
export type { LowerShellEffectPlanParams } from './shell-lower/context.js';
|
|
9
4
|
/**
|
|
10
5
|
* Parse and lower a general shell command into the canonical Task 2 model.
|
|
11
6
|
* This function only produces effects; it does not evaluate policy or return a
|