@guilz-dev/belay 0.6.0 → 0.8.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.
- package/README.md +39 -20
- package/dist/adapters/claude/runtime-entry.js +54 -25
- package/dist/adapters/shared/gate-runtime.d.ts +7 -0
- package/dist/adapters/shared/gate-runtime.js +443 -225
- package/dist/bundle/claude-runtime.mjs +13027 -7271
- package/dist/bundle/codex-runtime.mjs +12700 -6977
- package/dist/bundle/cursor-runtime.mjs +12695 -6977
- package/dist/cli.js +37 -1
- package/dist/commands/approve.js +34 -10
- package/dist/commands/doctor.js +58 -0
- package/dist/commands/dogfood.js +1 -1
- package/dist/commands/explain.js +2 -2
- package/dist/commands/harvest.d.ts +0 -1
- package/dist/commands/harvest.js +1 -4
- package/dist/commands/metrics.js +6 -0
- package/dist/commands/quality.js +1 -3
- package/dist/commands/recovery-checkpoints.d.ts +164 -0
- package/dist/commands/recovery-checkpoints.js +347 -0
- package/dist/commands/revoke.js +19 -7
- package/dist/commands/status.js +7 -0
- package/dist/config-io.js +49 -18
- package/dist/conformance/guarantee-table.js +137 -9
- package/dist/conformance/types.d.ts +2 -0
- package/dist/core/approval-replay.d.ts +1 -1
- package/dist/core/approval-replay.js +4 -1
- package/dist/core/approval-service.d.ts +6 -1
- package/dist/core/approval-service.js +86 -27
- package/dist/core/approval.d.ts +2 -0
- package/dist/core/approval.js +6 -3
- package/dist/core/audit-metrics.d.ts +1 -0
- package/dist/core/audit-metrics.js +12 -4
- package/dist/core/audit-types.d.ts +16 -0
- package/dist/core/capability/approval-state-mutation.d.ts +19 -0
- package/dist/core/capability/approval-state-mutation.js +76 -3
- package/dist/core/capability/approval-v3.d.ts +18 -1
- package/dist/core/capability/approval-v3.js +63 -23
- package/dist/core/capability/attestation.d.ts +3 -0
- package/dist/core/capability/attestation.js +7 -0
- package/dist/core/capability/boundary-driver-container.d.ts +12 -0
- package/dist/core/capability/boundary-driver-container.js +101 -34
- package/dist/core/capability/boundary-driver.js +1 -0
- package/dist/core/capability/boundary-grant-materialize.d.ts +1 -0
- package/dist/core/capability/boundary-grant-materialize.js +15 -0
- package/dist/core/capability/boundary-run.d.ts +25 -0
- package/dist/core/capability/boundary-run.js +37 -9
- package/dist/core/capability/boundary-session.d.ts +4 -0
- package/dist/core/capability/boundary-session.js +9 -0
- package/dist/core/capability/boundary-workspace-mount.d.ts +5 -0
- package/dist/core/capability/boundary-workspace-mount.js +68 -0
- package/dist/core/capability/gate-policy-shadow.js +23 -1
- package/dist/core/capability/grant-consumption.d.ts +4 -0
- package/dist/core/capability/grant-consumption.js +11 -0
- package/dist/core/capability/grant-lease.d.ts +36 -1
- package/dist/core/capability/grant-lease.js +213 -16
- package/dist/core/capability/grant-match.js +3 -0
- package/dist/core/capability/index.d.ts +2 -2
- package/dist/core/capability/index.js +1 -1
- package/dist/core/capability/policy-engine.d.ts +38 -1
- package/dist/core/capability/policy-engine.js +388 -15
- package/dist/core/capability/request.d.ts +12 -0
- package/dist/core/capability/resolver.d.ts +0 -1
- package/dist/core/capability/resolver.js +0 -1
- package/dist/core/config.d.ts +20 -0
- package/dist/core/config.js +62 -2
- package/dist/core/effect-ir/audit.d.ts +9 -0
- package/dist/core/effect-ir/audit.js +87 -0
- package/dist/core/effect-ir/build.d.ts +31 -0
- package/dist/core/effect-ir/build.js +281 -0
- package/dist/core/effect-ir/index.d.ts +13 -0
- package/dist/core/effect-ir/index.js +9 -0
- package/dist/core/effect-ir/normalize.d.ts +4 -0
- package/dist/core/effect-ir/normalize.js +110 -0
- package/dist/core/effect-ir/package-exec-eval.d.ts +8 -0
- package/dist/core/effect-ir/package-exec-eval.js +152 -0
- package/dist/core/effect-ir/package-exec.d.ts +29 -0
- package/dist/core/effect-ir/package-exec.js +292 -0
- package/dist/core/effect-ir/policy.d.ts +14 -0
- package/dist/core/effect-ir/policy.js +164 -0
- package/dist/core/effect-ir/shell-build.d.ts +43 -0
- package/dist/core/effect-ir/shell-build.js +77 -0
- package/dist/core/effect-ir/shell-lower.d.ts +14 -0
- package/dist/core/effect-ir/shell-lower.js +1509 -0
- package/dist/core/effect-ir/types.d.ts +62 -0
- package/dist/core/effect-ir/types.js +1 -0
- package/dist/core/egress/allowlist.d.ts +1 -0
- package/dist/core/egress/allowlist.js +8 -0
- package/dist/core/egress-approval.js +45 -51
- package/dist/core/gate-contract.d.ts +2 -0
- package/dist/core/gate-contract.js +2 -0
- package/dist/core/gate-engine.js +82 -25
- package/dist/core/git-resource-identity.d.ts +26 -0
- package/dist/core/git-resource-identity.js +284 -0
- package/dist/core/harvest.d.ts +3 -7
- package/dist/core/harvest.js +4 -27
- package/dist/core/index.d.ts +5 -1
- package/dist/core/index.js +3 -1
- package/dist/core/judge-cloud-consent.js +29 -15
- package/dist/core/network-endpoint.d.ts +10 -0
- package/dist/core/network-endpoint.js +51 -0
- package/dist/core/path-utils.js +42 -23
- package/dist/core/process-runner.d.ts +10 -0
- package/dist/core/process-runner.js +124 -0
- package/dist/core/recover-advice.js +2 -1
- package/dist/core/recovery/artifact-store.d.ts +29 -0
- package/dist/core/recovery/artifact-store.js +336 -0
- package/dist/core/recovery/checkpoint.d.ts +25 -0
- package/dist/core/recovery/checkpoint.js +260 -0
- package/dist/core/recovery/index.d.ts +3 -1
- package/dist/core/recovery/index.js +2 -0
- package/dist/core/recovery/operator-guidance.d.ts +7 -0
- package/dist/core/recovery/operator-guidance.js +39 -0
- package/dist/core/recovery/reconcile.d.ts +3 -0
- package/dist/core/recovery/reconcile.js +48 -0
- package/dist/core/recovery/resource-identity.d.ts +4 -0
- package/dist/core/recovery/resource-identity.js +34 -0
- package/dist/core/recovery/restore.d.ts +13 -0
- package/dist/core/recovery/restore.js +114 -0
- package/dist/core/recovery/snapshot-node.d.ts +27 -0
- package/dist/core/recovery/snapshot-node.js +261 -0
- package/dist/core/recovery/types.d.ts +85 -1
- package/dist/core/shell-substitution.d.ts +1 -0
- package/dist/core/shell-substitution.js +61 -0
- package/dist/core/standing-allow.d.ts +1 -2
- package/dist/core/standing-allow.js +3 -20
- package/dist/core/transactional/apply-observed-changes.d.ts +13 -0
- package/dist/core/transactional/apply-observed-changes.js +275 -0
- package/dist/core/transactional/backend-selector.d.ts +8 -0
- package/dist/core/transactional/backend-selector.js +118 -0
- package/dist/core/transactional/backend.d.ts +55 -0
- package/dist/core/transactional/backend.js +1 -0
- package/dist/core/transactional/file-checkpoint-backend.d.ts +3 -0
- package/dist/core/transactional/file-checkpoint-backend.js +278 -0
- package/dist/core/transactional/file-checkpoint-git.d.ts +11 -0
- package/dist/core/transactional/file-checkpoint-git.js +217 -0
- package/dist/core/transactional/file-checkpoint-staging.d.ts +13 -0
- package/dist/core/transactional/file-checkpoint-staging.js +54 -0
- package/dist/core/transactional/file-clone.d.ts +15 -0
- package/dist/core/transactional/file-clone.js +139 -0
- package/dist/core/transactional/file-tree-path.d.ts +7 -0
- package/dist/core/transactional/file-tree-path.js +50 -0
- package/dist/core/transactional/file-tree.d.ts +41 -0
- package/dist/core/transactional/file-tree.js +186 -0
- package/dist/core/transactional/git-worktree-backend.d.ts +2 -0
- package/dist/core/transactional/git-worktree-backend.js +50 -0
- package/dist/core/transactional/git-worktree.d.ts +9 -11
- package/dist/core/transactional/git-worktree.js +24 -119
- package/dist/core/transactional/index.d.ts +11 -0
- package/dist/core/transactional/index.js +10 -0
- package/dist/core/transactional/reasons.js +1 -0
- package/dist/core/transactional/runner.js +157 -25
- package/dist/core/transactional/snapshot-node.d.ts +26 -0
- package/dist/core/transactional/snapshot-node.js +87 -0
- package/dist/core/transactional/types.d.ts +17 -1
- package/dist/core/types.d.ts +13 -1
- package/dist/core/verdict/adapter.js +23 -15
- package/dist/core/verdict/containment.js +4 -0
- package/dist/core/verdict/egress-classify.d.ts +12 -0
- package/dist/core/verdict/egress-classify.js +746 -0
- package/dist/core/verdict/git-classifier.d.ts +12 -0
- package/dist/core/verdict/git-classifier.js +320 -0
- package/dist/core/verdict/launcher-resolve.js +79 -37
- package/dist/core/verdict/parser.d.ts +2 -0
- package/dist/core/verdict/parser.js +95 -1
- package/dist/core/verdict/types.d.ts +6 -2
- package/dist/core/verdict/verdict.js +103 -935
- package/dist/corpus/must-allow-commands.d.ts +2 -1
- package/dist/corpus/must-allow-commands.js +2 -1
- package/dist/corpus/runtime-match.d.ts +2 -1
- package/dist/corpus/runtime-match.js +2 -1
- package/dist/corpus/types.d.ts +2 -2
- package/dist/templates.js +7 -2
- package/dist/types.d.ts +11 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/skills/belay/SKILL.md +8 -3
- package/skills/belay/belay-approve.md +10 -7
- package/dist/core/verdict/overrides.d.ts +0 -7
- package/dist/core/verdict/overrides.js +0 -37
- package/dist/core/verdict/shell-policy.d.ts +0 -25
- package/dist/core/verdict/shell-policy.js +0 -40
- package/dist/corpus/standing-allow-catalog.generated.d.ts +0 -13
- package/dist/corpus/standing-allow-catalog.generated.js +0 -99
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ShellEffectRequirement } from '../effect-ir/shell-build.js';
|
|
1
2
|
import type { VerdictEffect } from './types.js';
|
|
2
3
|
export interface NormalizedGitInvocation {
|
|
3
4
|
subcommand: string;
|
|
@@ -21,6 +22,17 @@ export interface GitCommandSemantics {
|
|
|
21
22
|
normalizedKey: string;
|
|
22
23
|
isReadOnly: boolean;
|
|
23
24
|
}
|
|
25
|
+
export interface DecodeGitEffectsParams {
|
|
26
|
+
tokens: string[];
|
|
27
|
+
cwd: string;
|
|
28
|
+
repoRoot: string;
|
|
29
|
+
segment: string;
|
|
30
|
+
}
|
|
24
31
|
export declare function normalizeGitInvocation(tokens: string[], baseCwd: string): NormalizedGitInvocation | null;
|
|
25
32
|
export declare function isGitReadOnlyInvocation(tokens: string[], baseCwd: string): boolean;
|
|
26
33
|
export declare function classifyGitCommand(tokens: string[], baseCwd: string): GitCommandSemantics | null;
|
|
34
|
+
/**
|
|
35
|
+
* Decode git CLI grammar to typed effects without making an authorization
|
|
36
|
+
* decision. Legacy classification remains separate until the authority cutover.
|
|
37
|
+
*/
|
|
38
|
+
export declare function decodeGitEffects(params: DecodeGitEffectsParams): ShellEffectRequirement[] | null;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
+
import { parseNetworkEndpoint } from '../network-endpoint.js';
|
|
2
3
|
import { extractRedirectTargets } from '../shell-tokenizer.js';
|
|
3
4
|
const GIT_BRANCH_MUTATION_FLAGS = new Set([
|
|
4
5
|
'--copy',
|
|
@@ -531,3 +532,322 @@ export function classifyGitCommand(tokens, baseCwd) {
|
|
|
531
532
|
isReadOnly: false,
|
|
532
533
|
};
|
|
533
534
|
}
|
|
535
|
+
/**
|
|
536
|
+
* Decode git CLI grammar to typed effects without making an authorization
|
|
537
|
+
* decision. Legacy classification remains separate until the authority cutover.
|
|
538
|
+
*/
|
|
539
|
+
export function decodeGitEffects(params) {
|
|
540
|
+
const normalized = normalizeGitInvocation(params.tokens, params.cwd);
|
|
541
|
+
if (!normalized) {
|
|
542
|
+
return null;
|
|
543
|
+
}
|
|
544
|
+
const { subcommand, args } = normalized;
|
|
545
|
+
const signals = [
|
|
546
|
+
`git.${subcommand.replaceAll(' ', '.')}`,
|
|
547
|
+
...(subcommand === 'push' ? ['tier0_external'] : []),
|
|
548
|
+
];
|
|
549
|
+
const effectiveCwd = normalized.effectiveCwd ?? params.cwd;
|
|
550
|
+
const workTreeRoot = normalized.workTree
|
|
551
|
+
? path.resolve(normalized.effectiveCwd ?? params.cwd, normalized.workTree)
|
|
552
|
+
: (normalized.effectiveCwd ?? params.repoRoot);
|
|
553
|
+
const gitRefRoot = normalized.gitDir
|
|
554
|
+
? path.resolve(effectiveCwd, normalized.gitDir)
|
|
555
|
+
: workTreeRoot;
|
|
556
|
+
const gitControlRoot = normalized.gitDir ? gitRefRoot : path.join(gitRefRoot, '.git');
|
|
557
|
+
const requirements = [];
|
|
558
|
+
if (subcommand === 'fetch' || subcommand === 'pull') {
|
|
559
|
+
const positionals = gitRemotePositionals(args);
|
|
560
|
+
if (subcommand === 'fetch' && args.includes('--all')) {
|
|
561
|
+
return [
|
|
562
|
+
gitRequirement('network.connect', 'network.connect', {
|
|
563
|
+
kind: 'network',
|
|
564
|
+
host: 'unknown',
|
|
565
|
+
protocol: 'git-remote',
|
|
566
|
+
mode: 'read',
|
|
567
|
+
payload: 'none',
|
|
568
|
+
}, params.segment, [...signals, 'git.fetch_all_remotes_unknown']),
|
|
569
|
+
gitRequirement('git.ref.write', 'git.ref.write', { kind: 'git-ref', ref: 'refs/remotes/*', scope: 'local', repoPath: gitRefRoot }, params.segment, [...signals, 'git.ref.local']),
|
|
570
|
+
gitRequirement('indeterminate', 'indeterminate', { kind: 'unknown' }, params.segment, [
|
|
571
|
+
...signals,
|
|
572
|
+
'git.fetch_all_remotes_unknown',
|
|
573
|
+
]),
|
|
574
|
+
];
|
|
575
|
+
}
|
|
576
|
+
const multiple = subcommand === 'fetch' && args.includes('--multiple');
|
|
577
|
+
const remoteTokens = multiple ? positionals : [positionals[0]];
|
|
578
|
+
for (const remoteToken of remoteTokens) {
|
|
579
|
+
const remote = gitRemoteResource(remoteToken);
|
|
580
|
+
requirements.push(gitRequirement('network.connect', 'network.connect', {
|
|
581
|
+
kind: 'network',
|
|
582
|
+
...remote.endpoint,
|
|
583
|
+
mode: 'read',
|
|
584
|
+
payload: 'none',
|
|
585
|
+
}, params.segment, [...signals, 'git.network.read']), gitRequirement('git.ref.write', 'git.ref.write', {
|
|
586
|
+
kind: 'git-ref',
|
|
587
|
+
ref: remote.ref ? `refs/remotes/${remote.ref}/*` : 'refs/remotes/*',
|
|
588
|
+
scope: 'local',
|
|
589
|
+
repoPath: gitRefRoot,
|
|
590
|
+
}, params.segment, [...signals, 'git.ref.local']));
|
|
591
|
+
}
|
|
592
|
+
const hazardousFetchSignals = gitFetchControlSignals(params.tokens, args, remoteTokens);
|
|
593
|
+
if (hazardousFetchSignals.length > 0) {
|
|
594
|
+
requirements.push(gitRequirement('indeterminate', 'indeterminate', { kind: 'unknown' }, params.segment, [
|
|
595
|
+
...signals,
|
|
596
|
+
...hazardousFetchSignals,
|
|
597
|
+
]));
|
|
598
|
+
}
|
|
599
|
+
if (multiple && positionals.length === 0) {
|
|
600
|
+
requirements.push(gitRequirement('indeterminate', 'indeterminate', { kind: 'unknown' }, params.segment, [
|
|
601
|
+
...signals,
|
|
602
|
+
'git.fetch_multiple_remote_missing',
|
|
603
|
+
]));
|
|
604
|
+
}
|
|
605
|
+
if (subcommand === 'pull') {
|
|
606
|
+
requirements.push(gitRequirement('fs.write', 'fs.write', { kind: 'path', path: workTreeRoot }, params.segment, [...signals, 'git.worktree.update']));
|
|
607
|
+
}
|
|
608
|
+
return requirements;
|
|
609
|
+
}
|
|
610
|
+
if (subcommand === 'push') {
|
|
611
|
+
const positionals = gitRemotePositionals(args);
|
|
612
|
+
const repositoryOption = gitOptionValue(args, '--repo');
|
|
613
|
+
const remote = gitRemoteResource(repositoryOption.value ?? positionals[0]);
|
|
614
|
+
const refs = repositoryOption.value ? positionals : positionals.slice(1);
|
|
615
|
+
const lowered = [
|
|
616
|
+
gitRequirement('network.connect', 'network.connect', {
|
|
617
|
+
kind: 'network',
|
|
618
|
+
...remote.endpoint,
|
|
619
|
+
mode: 'mutate',
|
|
620
|
+
payload: 'present',
|
|
621
|
+
}, params.segment, [...signals, 'git.network.mutate']),
|
|
622
|
+
];
|
|
623
|
+
for (const ref of refs.length > 0 ? refs : ['*']) {
|
|
624
|
+
lowered.push(gitRequirement('git.ref.write', 'git.ref.write', {
|
|
625
|
+
kind: 'git-ref',
|
|
626
|
+
ref: ref.startsWith('refs/') ? ref : `refs/heads/${ref}`,
|
|
627
|
+
scope: 'remote',
|
|
628
|
+
}, params.segment, [...signals, 'git.ref.remote']));
|
|
629
|
+
}
|
|
630
|
+
if (repositoryOption.missing) {
|
|
631
|
+
lowered.push(gitRequirement('indeterminate', 'indeterminate', { kind: 'unknown' }, params.segment, [
|
|
632
|
+
...signals,
|
|
633
|
+
'git.push_repository_missing',
|
|
634
|
+
]));
|
|
635
|
+
}
|
|
636
|
+
return lowered;
|
|
637
|
+
}
|
|
638
|
+
if (subcommand === 'reflog') {
|
|
639
|
+
const operation = args[0];
|
|
640
|
+
const mutationFlags = [
|
|
641
|
+
'--expire',
|
|
642
|
+
'--expire-unreachable',
|
|
643
|
+
'--rewrite',
|
|
644
|
+
'--stale-fix',
|
|
645
|
+
'--updateref',
|
|
646
|
+
];
|
|
647
|
+
const mutating = operation === 'expire' ||
|
|
648
|
+
operation === 'delete' ||
|
|
649
|
+
operation === 'drop' ||
|
|
650
|
+
args.some((arg) => mutationFlags.some((flag) => arg === flag || arg.startsWith(`${flag}=`)));
|
|
651
|
+
if (mutating) {
|
|
652
|
+
return [
|
|
653
|
+
gitRequirement('process.exec', 'process.exec', { kind: 'executable', command: 'git', operation: 'spawn' }, params.segment, [...signals, 'git_history_destructive', 'git.reflog.mutate']),
|
|
654
|
+
gitRequirement('control_plane.write', 'control_plane.write', { kind: 'path', path: path.join(gitControlRoot, 'logs') }, params.segment, [...signals, 'git_history_destructive', 'git.reflog.mutate']),
|
|
655
|
+
];
|
|
656
|
+
}
|
|
657
|
+
if (isReadOnlyReflogInvocation(args)) {
|
|
658
|
+
return [
|
|
659
|
+
gitRequirement('process.exec', 'process.exec', { kind: 'executable', command: 'git', operation: 'inspect' }, params.segment, [...signals, 'git.inspect']),
|
|
660
|
+
gitRequirement('fs.read', 'fs.read', { kind: 'path', path: workTreeRoot }, params.segment, [
|
|
661
|
+
...signals,
|
|
662
|
+
'git.repository.read',
|
|
663
|
+
]),
|
|
664
|
+
];
|
|
665
|
+
}
|
|
666
|
+
return [
|
|
667
|
+
gitRequirement('process.exec', 'process.exec', { kind: 'executable', command: 'git', operation: 'spawn' }, params.segment, signals),
|
|
668
|
+
gitRequirement('indeterminate', 'indeterminate', { kind: 'unknown' }, params.segment, [
|
|
669
|
+
...signals,
|
|
670
|
+
'git.reflog.grammar_incomplete',
|
|
671
|
+
]),
|
|
672
|
+
];
|
|
673
|
+
}
|
|
674
|
+
const semantics = classifyGitCommand(params.tokens, params.cwd);
|
|
675
|
+
if (!semantics) {
|
|
676
|
+
return null;
|
|
677
|
+
}
|
|
678
|
+
if (semantics.isReadOnly) {
|
|
679
|
+
requirements.push(gitRequirement('process.exec', 'process.exec', { kind: 'executable', command: 'git', operation: 'inspect' }, params.segment, [...signals, 'git.inspect']), gitRequirement('fs.read', 'fs.read', { kind: 'path', path: workTreeRoot }, params.segment, [
|
|
680
|
+
...signals,
|
|
681
|
+
'git.repository.read',
|
|
682
|
+
]));
|
|
683
|
+
for (const operand of semantics.pathTargets) {
|
|
684
|
+
requirements.push(gitRequirement('fs.read', 'fs.read', { kind: 'path', path: path.resolve(workTreeRoot, operand) }, params.segment, [...signals, 'git.path.read']));
|
|
685
|
+
}
|
|
686
|
+
return requirements;
|
|
687
|
+
}
|
|
688
|
+
if (semantics.effect === 'local_mutation') {
|
|
689
|
+
requirements.push(gitRequirement('process.exec', 'process.exec', { kind: 'executable', command: 'git', operation: 'spawn' }, params.segment, signals), gitRequirement('git.ref.write', 'git.ref.write', {
|
|
690
|
+
kind: 'git-ref',
|
|
691
|
+
ref: localGitRef(subcommand, args),
|
|
692
|
+
scope: 'local',
|
|
693
|
+
repoPath: gitRefRoot,
|
|
694
|
+
}, params.segment, [...signals, 'git.ref.local']));
|
|
695
|
+
for (const operand of semantics.pathTargets) {
|
|
696
|
+
requirements.push(gitRequirement('fs.write', 'fs.write', { kind: 'path', path: path.resolve(workTreeRoot, operand) }, params.segment, [...signals, 'git.path.write']));
|
|
697
|
+
}
|
|
698
|
+
if (semantics.requiresAsk) {
|
|
699
|
+
requirements.push(gitRequirement('fs.write', 'fs.write', { kind: 'path', path: workTreeRoot }, params.segment, [...signals, ...semantics.requiresAsk.signals, 'git.destructive_worktree_write']), gitRequirement('control_plane.write', 'control_plane.write', { kind: 'path', path: gitControlRoot }, params.segment, [...signals, ...semantics.requiresAsk.signals, 'git.destructive_history_write']));
|
|
700
|
+
}
|
|
701
|
+
return requirements;
|
|
702
|
+
}
|
|
703
|
+
return [
|
|
704
|
+
gitRequirement('process.exec', 'process.exec', { kind: 'executable', command: 'git', operation: 'spawn' }, params.segment, signals),
|
|
705
|
+
gitRequirement('indeterminate', 'indeterminate', { kind: 'unknown' }, params.segment, [
|
|
706
|
+
...signals,
|
|
707
|
+
'git.grammar_incomplete',
|
|
708
|
+
]),
|
|
709
|
+
];
|
|
710
|
+
}
|
|
711
|
+
function isReadOnlyReflogInvocation(args) {
|
|
712
|
+
if (args[0] === 'exists') {
|
|
713
|
+
return args.length === 2 && Boolean(args[1]) && !(args[1] ?? '').startsWith('-');
|
|
714
|
+
}
|
|
715
|
+
let index = args[0] === 'show' || args[0] === 'list' ? 1 : 0;
|
|
716
|
+
let positionalCount = 0;
|
|
717
|
+
while (index < args.length) {
|
|
718
|
+
const arg = args[index] ?? '';
|
|
719
|
+
if (arg === '--all' || /^-\d+$/.test(arg)) {
|
|
720
|
+
index += 1;
|
|
721
|
+
continue;
|
|
722
|
+
}
|
|
723
|
+
if (arg === '-n' || arg === '--max-count') {
|
|
724
|
+
const count = args[index + 1];
|
|
725
|
+
if (!count || !/^\d+$/.test(count)) {
|
|
726
|
+
return false;
|
|
727
|
+
}
|
|
728
|
+
index += 2;
|
|
729
|
+
continue;
|
|
730
|
+
}
|
|
731
|
+
if (/^-n\d+$/.test(arg) || /^--(?:max-count|date|format|pretty)=.+/.test(arg)) {
|
|
732
|
+
index += 1;
|
|
733
|
+
continue;
|
|
734
|
+
}
|
|
735
|
+
if (arg.startsWith('-')) {
|
|
736
|
+
return false;
|
|
737
|
+
}
|
|
738
|
+
positionalCount += 1;
|
|
739
|
+
if (args[0] !== 'show' && args[0] !== 'list') {
|
|
740
|
+
return false;
|
|
741
|
+
}
|
|
742
|
+
if (positionalCount > 1) {
|
|
743
|
+
return false;
|
|
744
|
+
}
|
|
745
|
+
index += 1;
|
|
746
|
+
}
|
|
747
|
+
return true;
|
|
748
|
+
}
|
|
749
|
+
const GIT_REMOTE_OPTIONS_WITH_VALUES = new Set([
|
|
750
|
+
'--depth',
|
|
751
|
+
'--exec',
|
|
752
|
+
'--filter',
|
|
753
|
+
'--jobs',
|
|
754
|
+
'--negotiation-tip',
|
|
755
|
+
'--push-option',
|
|
756
|
+
'--repo',
|
|
757
|
+
'--receive-pack',
|
|
758
|
+
'--recurse-submodules',
|
|
759
|
+
'--server-option',
|
|
760
|
+
'--shallow-exclude',
|
|
761
|
+
'--shallow-since',
|
|
762
|
+
'--upload-pack',
|
|
763
|
+
'-j',
|
|
764
|
+
'-o',
|
|
765
|
+
]);
|
|
766
|
+
function gitFetchControlSignals(tokens, args, remoteTokens) {
|
|
767
|
+
const signals = [];
|
|
768
|
+
if (tokens.some((token) => token === '-c' ||
|
|
769
|
+
(token.startsWith('-c') && token.length > 2) ||
|
|
770
|
+
token === '--config-env' ||
|
|
771
|
+
token.startsWith('--config-env=') ||
|
|
772
|
+
token === '--exec-path' ||
|
|
773
|
+
token.startsWith('--exec-path='))) {
|
|
774
|
+
signals.push('git.fetch.global_execution_override');
|
|
775
|
+
}
|
|
776
|
+
if (args.some((arg) => ['--exec', '--server-option', '--upload-pack'].some((option) => arg === option || arg.startsWith(`${option}=`)))) {
|
|
777
|
+
signals.push('git.fetch.caller_controlled_execution_or_payload');
|
|
778
|
+
}
|
|
779
|
+
if (remoteTokens.some((remote) => remote?.startsWith('ext::'))) {
|
|
780
|
+
signals.push('git.fetch.external_remote_helper');
|
|
781
|
+
}
|
|
782
|
+
return signals;
|
|
783
|
+
}
|
|
784
|
+
function gitOptionValue(args, option) {
|
|
785
|
+
const exactIndex = args.indexOf(option);
|
|
786
|
+
if (exactIndex >= 0) {
|
|
787
|
+
const value = args[exactIndex + 1];
|
|
788
|
+
return value && !value.startsWith('-') ? { value, missing: false } : { missing: true };
|
|
789
|
+
}
|
|
790
|
+
const prefix = `${option}=`;
|
|
791
|
+
const inline = args.find((arg) => arg.startsWith(prefix));
|
|
792
|
+
if (inline !== undefined) {
|
|
793
|
+
const value = inline.slice(prefix.length);
|
|
794
|
+
return value ? { value, missing: false } : { missing: true };
|
|
795
|
+
}
|
|
796
|
+
return { missing: false };
|
|
797
|
+
}
|
|
798
|
+
function gitRemotePositionals(args) {
|
|
799
|
+
const positionals = [];
|
|
800
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
801
|
+
const arg = args[index] ?? '';
|
|
802
|
+
if (arg === '--') {
|
|
803
|
+
positionals.push(...args.slice(index + 1));
|
|
804
|
+
break;
|
|
805
|
+
}
|
|
806
|
+
if (arg.startsWith('-')) {
|
|
807
|
+
if (GIT_REMOTE_OPTIONS_WITH_VALUES.has(arg) && !arg.includes('=')) {
|
|
808
|
+
index += args[index + 1] ? 1 : 0;
|
|
809
|
+
}
|
|
810
|
+
continue;
|
|
811
|
+
}
|
|
812
|
+
positionals.push(arg);
|
|
813
|
+
}
|
|
814
|
+
return positionals;
|
|
815
|
+
}
|
|
816
|
+
function gitRemoteResource(remoteToken) {
|
|
817
|
+
const token = remoteToken && !remoteToken.startsWith('-') ? remoteToken : undefined;
|
|
818
|
+
const endpoint = token
|
|
819
|
+
? parseNetworkEndpoint(token, { allowHostedGitShorthand: true, allowScpStyle: true })
|
|
820
|
+
: null;
|
|
821
|
+
if (endpoint) {
|
|
822
|
+
return { endpoint };
|
|
823
|
+
}
|
|
824
|
+
const ref = token && !token.includes('/') ? token : undefined;
|
|
825
|
+
return {
|
|
826
|
+
endpoint: { host: ref ?? 'origin', protocol: 'git-remote' },
|
|
827
|
+
...(ref ? { ref } : {}),
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
function localGitRef(subcommand, args) {
|
|
831
|
+
if (subcommand === 'branch') {
|
|
832
|
+
const name = args.find((arg) => arg && !arg.startsWith('-'));
|
|
833
|
+
return name ? `refs/heads/${name}` : 'refs/heads/*';
|
|
834
|
+
}
|
|
835
|
+
if (subcommand === 'tag') {
|
|
836
|
+
const name = args.find((arg) => arg && !arg.startsWith('-'));
|
|
837
|
+
return name ? `refs/tags/${name}` : 'refs/tags/*';
|
|
838
|
+
}
|
|
839
|
+
return 'refs/local/*';
|
|
840
|
+
}
|
|
841
|
+
function gitRequirement(tag, action, resource, segment, signals) {
|
|
842
|
+
return {
|
|
843
|
+
tag,
|
|
844
|
+
action,
|
|
845
|
+
resource,
|
|
846
|
+
evidence: {
|
|
847
|
+
level: tag === 'indeterminate' ? 'indeterminate' : 'certain',
|
|
848
|
+
signals: [...new Set(signals)].sort(),
|
|
849
|
+
basis: ['git_grammar'],
|
|
850
|
+
},
|
|
851
|
+
provenance: { segment },
|
|
852
|
+
};
|
|
853
|
+
}
|
|
@@ -34,17 +34,6 @@ const PNPM_BUILTIN_COMMANDS = new Set([
|
|
|
34
34
|
'up',
|
|
35
35
|
'why',
|
|
36
36
|
]);
|
|
37
|
-
const PNPM_EXEC_LIKE_HEADS = new Set([
|
|
38
|
-
'vitest',
|
|
39
|
-
'vite',
|
|
40
|
-
'biome',
|
|
41
|
-
'eslint',
|
|
42
|
-
'jest',
|
|
43
|
-
'mocha',
|
|
44
|
-
'tsc',
|
|
45
|
-
'tsx',
|
|
46
|
-
'node',
|
|
47
|
-
]);
|
|
48
37
|
function readPackageJson(dir) {
|
|
49
38
|
const packagePath = path.join(dir, 'package.json');
|
|
50
39
|
if (!existsSync(packagePath)) {
|
|
@@ -134,32 +123,47 @@ function resolveNpmRecipe(cwd, repoRoot, scriptName, extraArgs) {
|
|
|
134
123
|
if (!scripts || typeof scripts !== 'object') {
|
|
135
124
|
return { recipes: [], opaque: true, reason: 'package_scripts_missing' };
|
|
136
125
|
}
|
|
137
|
-
const
|
|
126
|
+
const scriptMap = scripts;
|
|
127
|
+
const recipe = scriptMap[scriptName];
|
|
138
128
|
if (!recipe || typeof recipe !== 'string') {
|
|
139
129
|
if (/deploy|publish|release|ship|prod/i.test(scriptName)) {
|
|
140
130
|
return { recipes: [], opaque: true, reason: 'external_script' };
|
|
141
131
|
}
|
|
142
132
|
return { recipes: [], opaque: true, reason: 'npm_script_undefined' };
|
|
143
133
|
}
|
|
144
|
-
|
|
145
|
-
|
|
134
|
+
const lifecycleRecipes = [
|
|
135
|
+
scriptMap[`pre${scriptName}`],
|
|
136
|
+
applyForwardedArgs(recipe, extraArgs),
|
|
137
|
+
scriptMap[`post${scriptName}`],
|
|
138
|
+
].filter((entry) => typeof entry === 'string' && entry.trim().length > 0);
|
|
139
|
+
if (lifecycleRecipes.some((entry) => /\$\(/.test(entry) || /\$\{/.test(entry))) {
|
|
140
|
+
return { recipes: lifecycleRecipes, opaque: true, reason: 'npm_script_dynamic' };
|
|
146
141
|
}
|
|
147
142
|
return {
|
|
148
|
-
recipes:
|
|
143
|
+
recipes: lifecycleRecipes,
|
|
149
144
|
opaque: false,
|
|
150
145
|
reason: 'npm_script_resolved',
|
|
151
146
|
};
|
|
152
147
|
}
|
|
153
148
|
function parseMakefileRecipes(makefilePath) {
|
|
154
|
-
const
|
|
149
|
+
const targets = new Map();
|
|
155
150
|
try {
|
|
156
151
|
const content = readFileSync(makefilePath, 'utf8');
|
|
157
152
|
const lines = content.split('\n');
|
|
158
153
|
let currentTarget = null;
|
|
159
154
|
let recipeLines = [];
|
|
160
155
|
const flush = () => {
|
|
161
|
-
if (currentTarget
|
|
162
|
-
|
|
156
|
+
if (currentTarget) {
|
|
157
|
+
const current = targets.get(currentTarget) ?? {
|
|
158
|
+
prerequisites: [],
|
|
159
|
+
recipes: [],
|
|
160
|
+
opaquePrerequisites: false,
|
|
161
|
+
};
|
|
162
|
+
targets.set(currentTarget, {
|
|
163
|
+
prerequisites: current.prerequisites,
|
|
164
|
+
recipes: recipeLines.map((line) => line.trim()).filter((line) => line.length > 0),
|
|
165
|
+
opaquePrerequisites: current.opaquePrerequisites,
|
|
166
|
+
});
|
|
163
167
|
}
|
|
164
168
|
currentTarget = null;
|
|
165
169
|
recipeLines = [];
|
|
@@ -168,13 +172,26 @@ function parseMakefileRecipes(makefilePath) {
|
|
|
168
172
|
if (line.trim().startsWith('#')) {
|
|
169
173
|
continue;
|
|
170
174
|
}
|
|
171
|
-
const targetMatch = /^([A-Za-z0-9_.-]+)\s*:(?!=)/.exec(line);
|
|
175
|
+
const targetMatch = /^([A-Za-z0-9_.-]+)\s*:(?!=)\s*([^#]*)/.exec(line);
|
|
172
176
|
if (targetMatch) {
|
|
173
177
|
flush();
|
|
174
178
|
currentTarget = targetMatch[1] ?? null;
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
179
|
+
if (currentTarget) {
|
|
180
|
+
const targetBody = (targetMatch[2] ?? '').trim();
|
|
181
|
+
const inlineRecipeIndex = targetBody.indexOf(';');
|
|
182
|
+
const prerequisiteText = inlineRecipeIndex >= 0 ? targetBody.slice(0, inlineRecipeIndex).trim() : targetBody;
|
|
183
|
+
const inlineRecipe = inlineRecipeIndex >= 0 ? targetBody.slice(inlineRecipeIndex + 1).trim() : '';
|
|
184
|
+
const prerequisiteTokens = prerequisiteText
|
|
185
|
+
.split(/\s+/)
|
|
186
|
+
.filter((token) => token && token !== '|');
|
|
187
|
+
targets.set(currentTarget, {
|
|
188
|
+
prerequisites: prerequisiteTokens.filter((token) => !token.includes('$(') && !token.includes('${')),
|
|
189
|
+
recipes: [],
|
|
190
|
+
opaquePrerequisites: prerequisiteTokens.some((token) => token.includes('$(') || token.includes('${')),
|
|
191
|
+
});
|
|
192
|
+
if (inlineRecipe) {
|
|
193
|
+
recipeLines.push(inlineRecipe);
|
|
194
|
+
}
|
|
178
195
|
}
|
|
179
196
|
continue;
|
|
180
197
|
}
|
|
@@ -185,9 +202,9 @@ function parseMakefileRecipes(makefilePath) {
|
|
|
185
202
|
flush();
|
|
186
203
|
}
|
|
187
204
|
catch {
|
|
188
|
-
return
|
|
205
|
+
return targets;
|
|
189
206
|
}
|
|
190
|
-
return
|
|
207
|
+
return targets;
|
|
191
208
|
}
|
|
192
209
|
function resolveMakeRecipe(cwd, repoRoot, target) {
|
|
193
210
|
const candidates = ['Makefile', 'makefile', 'GNUmakefile'];
|
|
@@ -210,16 +227,48 @@ function resolveMakeRecipe(cwd, repoRoot, target) {
|
|
|
210
227
|
if (!makefilePath) {
|
|
211
228
|
return { recipes: [], opaque: true, reason: 'unknown_local_effect' };
|
|
212
229
|
}
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
if (!recipeLines || recipeLines.length === 0) {
|
|
230
|
+
const targets = parseMakefileRecipes(makefilePath);
|
|
231
|
+
if (!targets.has(target)) {
|
|
216
232
|
return { recipes: [], opaque: true, reason: 'make_target_undefined' };
|
|
217
233
|
}
|
|
234
|
+
const recipeLines = [];
|
|
235
|
+
const visiting = new Set();
|
|
236
|
+
const visited = new Set();
|
|
237
|
+
let opaquePrerequisites = false;
|
|
238
|
+
const collect = (name) => {
|
|
239
|
+
if (visited.has(name)) {
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
if (visiting.has(name)) {
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
const entry = targets.get(name);
|
|
246
|
+
if (!entry) {
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
visiting.add(name);
|
|
250
|
+
opaquePrerequisites ||= entry.opaquePrerequisites;
|
|
251
|
+
for (const prerequisite of entry.prerequisites) {
|
|
252
|
+
if (targets.has(prerequisite) && !collect(prerequisite)) {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
recipeLines.push(...entry.recipes);
|
|
257
|
+
visiting.delete(name);
|
|
258
|
+
visited.add(name);
|
|
259
|
+
return true;
|
|
260
|
+
};
|
|
261
|
+
if (!collect(target)) {
|
|
262
|
+
return { recipes: recipeLines, opaque: true, reason: 'make_dependency_cycle' };
|
|
263
|
+
}
|
|
218
264
|
for (const line of recipeLines) {
|
|
219
265
|
if (/\$\(/.test(line) || /\$\{/.test(line)) {
|
|
220
|
-
return { recipes:
|
|
266
|
+
return { recipes: recipeLines, opaque: true, reason: 'make_recipe_dynamic' };
|
|
221
267
|
}
|
|
222
268
|
}
|
|
269
|
+
if (opaquePrerequisites) {
|
|
270
|
+
return { recipes: recipeLines, opaque: true, reason: 'make_prerequisite_dynamic' };
|
|
271
|
+
}
|
|
223
272
|
return { recipes: recipeLines, opaque: false, reason: 'make_recipe_resolved' };
|
|
224
273
|
}
|
|
225
274
|
export function resolveLauncherRecipe(params) {
|
|
@@ -232,12 +281,12 @@ export function resolveLauncherRecipe(params) {
|
|
|
232
281
|
const resolution = resolveNpmRecipe(params.cwd, params.repoRoot, scriptName, forwardedArgs(tokens));
|
|
233
282
|
if (tokens[0] === 'pnpm' &&
|
|
234
283
|
tokens[1] &&
|
|
235
|
-
|
|
284
|
+
!PNPM_BUILTIN_COMMANDS.has(tokens[1]) &&
|
|
236
285
|
resolution.reason === 'npm_script_undefined') {
|
|
237
286
|
return {
|
|
238
287
|
recipes: [tokens.slice(1).join(' ')],
|
|
239
288
|
opaque: false,
|
|
240
|
-
reason: '
|
|
289
|
+
reason: 'pnpm_exec_shorthand',
|
|
241
290
|
};
|
|
242
291
|
}
|
|
243
292
|
return resolution;
|
|
@@ -252,13 +301,6 @@ export function resolveLauncherRecipe(params) {
|
|
|
252
301
|
reason: 'pnpm_exec',
|
|
253
302
|
};
|
|
254
303
|
}
|
|
255
|
-
if (tokens[0] === 'pnpm' && tokens[1] && PNPM_EXEC_LIKE_HEADS.has(tokens[1])) {
|
|
256
|
-
return {
|
|
257
|
-
recipes: [tokens.slice(1).join(' ')],
|
|
258
|
-
opaque: false,
|
|
259
|
-
reason: 'pnpm_exec_like',
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
304
|
return null;
|
|
263
305
|
}
|
|
264
306
|
const READ_ONLY_LAUNCHER_SUFFIXES = new Set(['--version', '-v', '--help', '-h']);
|
|
@@ -15,7 +15,9 @@ export declare function extractEvalBody(tokens: string[]): string | null;
|
|
|
15
15
|
export declare function extractRecursiveScript(tokens: string[]): string | null;
|
|
16
16
|
export declare function isBareInterpreter(tokens: string[]): boolean;
|
|
17
17
|
export declare function splitTopLevelSegments(command: string): string[];
|
|
18
|
+
export declare function splitStructuralShellSegments(command: string): string[];
|
|
18
19
|
export declare function parseSegment(command: string): ParsedSegment;
|
|
19
20
|
export declare function segmentOpacity(command: string): VerdictOpacity;
|
|
20
21
|
export declare function substitutionInners(command: string): string[];
|
|
22
|
+
export declare function structuralSubstitutionInners(command: string): string[];
|
|
21
23
|
export declare function redactCommand(command: string): string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import { findCommandSubstitutions } from '../shell-substitution.js';
|
|
2
|
+
import { findCommandSubstitutions, findStructuralCommandSubstitutions, } from '../shell-substitution.js';
|
|
3
3
|
import { commandKey, tokenizeShell } from '../shell-tokenizer.js';
|
|
4
4
|
import { detectUnparseableShell } from '../shell-unparseable.js';
|
|
5
5
|
const ENV_PREFIX_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*=(?:'[^']*'|"[^"]*"|\S+)$/;
|
|
@@ -174,6 +174,97 @@ export function splitTopLevelSegments(command) {
|
|
|
174
174
|
flush();
|
|
175
175
|
return segments.filter((segment) => segment.trim().length > 0);
|
|
176
176
|
}
|
|
177
|
+
export function splitStructuralShellSegments(command) {
|
|
178
|
+
const segments = [];
|
|
179
|
+
let current = '';
|
|
180
|
+
const outer = { quote: null, escaping: false, backtick: false };
|
|
181
|
+
const substitutions = [];
|
|
182
|
+
const flush = () => {
|
|
183
|
+
const segment = current.trim();
|
|
184
|
+
if (segment) {
|
|
185
|
+
segments.push(segment);
|
|
186
|
+
}
|
|
187
|
+
current = '';
|
|
188
|
+
};
|
|
189
|
+
for (let index = 0; index < command.length; index += 1) {
|
|
190
|
+
const char = command[index] ?? '';
|
|
191
|
+
const next = command[index + 1] ?? '';
|
|
192
|
+
const frame = substitutions.at(-1) ?? outer;
|
|
193
|
+
if (frame.escaping) {
|
|
194
|
+
current += char;
|
|
195
|
+
frame.escaping = false;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (char === '\\' && frame.quote !== "'") {
|
|
199
|
+
current += char;
|
|
200
|
+
frame.escaping = true;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (frame.quote === "'") {
|
|
204
|
+
current += char;
|
|
205
|
+
if (char === "'") {
|
|
206
|
+
frame.quote = null;
|
|
207
|
+
}
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
if (char === '`') {
|
|
211
|
+
frame.backtick = !frame.backtick;
|
|
212
|
+
current += char;
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
if (frame.backtick) {
|
|
216
|
+
current += char;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (char === '$' && next === '(') {
|
|
220
|
+
current += '$(';
|
|
221
|
+
substitutions.push({ quote: null, escaping: false, backtick: false });
|
|
222
|
+
index += 1;
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
if (frame.quote) {
|
|
226
|
+
current += char;
|
|
227
|
+
if (char === frame.quote) {
|
|
228
|
+
frame.quote = null;
|
|
229
|
+
}
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (char === "'" || char === '"') {
|
|
233
|
+
frame.quote = char;
|
|
234
|
+
current += char;
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
if (substitutions.length > 0 && char === ')') {
|
|
238
|
+
substitutions.pop();
|
|
239
|
+
current += char;
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
if (substitutions.length > 0) {
|
|
243
|
+
current += char;
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
if (char === '&' &&
|
|
247
|
+
(command[index - 1] === '>' || command[index - 1] === '<' || next === '>')) {
|
|
248
|
+
current += char;
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
const twoCharacterOperator = (char === '&' && next === '&') ||
|
|
252
|
+
(char === '|' && next === '|') ||
|
|
253
|
+
(char === '|' && next === '&');
|
|
254
|
+
if (twoCharacterOperator) {
|
|
255
|
+
flush();
|
|
256
|
+
index += 1;
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
if (char === ';' || char === '|' || char === '&' || char === '\n' || char === '\r') {
|
|
260
|
+
flush();
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
current += char;
|
|
264
|
+
}
|
|
265
|
+
flush();
|
|
266
|
+
return segments;
|
|
267
|
+
}
|
|
177
268
|
export function parseSegment(command) {
|
|
178
269
|
const tokens = tokenizeShell(command);
|
|
179
270
|
const { tokens: peeled } = peelTransparentWrappers(tokens);
|
|
@@ -210,6 +301,9 @@ export function segmentOpacity(command) {
|
|
|
210
301
|
export function substitutionInners(command) {
|
|
211
302
|
return findCommandSubstitutions(command);
|
|
212
303
|
}
|
|
304
|
+
export function structuralSubstitutionInners(command) {
|
|
305
|
+
return findStructuralCommandSubstitutions(command);
|
|
306
|
+
}
|
|
213
307
|
export function redactCommand(command) {
|
|
214
308
|
return command
|
|
215
309
|
.replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gi, 'Bearer [REDACTED]')
|