@iowarp/clio-coder 0.3.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/CHANGELOG.md +407 -0
- package/CODE_OF_CONDUCT.md +21 -0
- package/CONTRIBUTING.md +224 -0
- package/LICENSE +202 -0
- package/NOTICE +9 -0
- package/README.md +798 -0
- package/SECURITY.md +72 -0
- package/assets/clio-coder-logo-128.webp +0 -0
- package/damage-control-rules.yaml +419 -0
- package/dist/acp-UMLFVA3F.js +92 -0
- package/dist/agents-Q4MYPMUW.js +91 -0
- package/dist/auth-O6HYIJ6J.js +521 -0
- package/dist/chunk-262G75JS.js +35 -0
- package/dist/chunk-26BZQOAD.js +1281 -0
- package/dist/chunk-2J63S4SF.js +508 -0
- package/dist/chunk-3DANZDGR.js +717 -0
- package/dist/chunk-4UQA7NCT.js +29 -0
- package/dist/chunk-527KG6XR.js +497 -0
- package/dist/chunk-5LDRNKX2.js +1063 -0
- package/dist/chunk-5N2FG33Q.js +25 -0
- package/dist/chunk-67MTHP2E.js +135 -0
- package/dist/chunk-6CWDTGUC.js +20 -0
- package/dist/chunk-7BHLZB3A.js +2115 -0
- package/dist/chunk-7RBKDI66.js +348 -0
- package/dist/chunk-AMFR5YA3.js +541 -0
- package/dist/chunk-BBUH4VAA.js +1224 -0
- package/dist/chunk-BYEU76JP.js +899 -0
- package/dist/chunk-CLJ5HLUD.js +458 -0
- package/dist/chunk-D5YD55AR.js +116 -0
- package/dist/chunk-DXQNI4PC.js +61 -0
- package/dist/chunk-E3NYWENM.js +1004 -0
- package/dist/chunk-GNGDQYDU.js +34688 -0
- package/dist/chunk-GOTUR54M.js +9 -0
- package/dist/chunk-HBU5MTAM.js +41 -0
- package/dist/chunk-HMYNFFY4.js +28 -0
- package/dist/chunk-JPOWPFCU.js +1010 -0
- package/dist/chunk-JWHCJDCI.js +1215 -0
- package/dist/chunk-KBR4MZZR.js +41 -0
- package/dist/chunk-KKKPTZLM.js +93 -0
- package/dist/chunk-ME6DNWIU.js +66 -0
- package/dist/chunk-NI4DEJMC.js +88 -0
- package/dist/chunk-O4EJEDHO.js +659 -0
- package/dist/chunk-PIDUD6M2.js +31 -0
- package/dist/chunk-PS4PFJQP.js +29459 -0
- package/dist/chunk-QV47YRF4.js +48 -0
- package/dist/chunk-RQDWMVRB.js +279 -0
- package/dist/chunk-TFSSEXL6.js +136 -0
- package/dist/chunk-TKHQ4DGZ.js +8290 -0
- package/dist/chunk-TPOCL34A.js +2876 -0
- package/dist/chunk-UGYAX5YI.js +565 -0
- package/dist/chunk-UHTSULZS.js +461 -0
- package/dist/chunk-UU3R62TT.js +128 -0
- package/dist/chunk-UWIJNAOB.js +3906 -0
- package/dist/chunk-VOO7NYPP.js +914 -0
- package/dist/chunk-VPAWTYLY.js +117 -0
- package/dist/chunk-WD6AJM35.js +1216 -0
- package/dist/chunk-X3BR7HWV.js +115 -0
- package/dist/chunk-X3NE4WVW.js +120 -0
- package/dist/chunk-XNISANGE.js +1395 -0
- package/dist/chunk-XV4ZJ6ZM.js +3177 -0
- package/dist/cli/index.js +236 -0
- package/dist/clio-KIQ5SNDS.js +53 -0
- package/dist/components-JVHMUBEB.js +653 -0
- package/dist/config-ZFCDBMDC.js +372 -0
- package/dist/configure-G4E3A2PG.js +27 -0
- package/dist/context-CDXTP2MP.js +293 -0
- package/dist/context-E3KIFVXI.js +185 -0
- package/dist/context-clear-3F4PLXOS.js +102 -0
- package/dist/context-index-Q7YSYTR3.js +106 -0
- package/dist/docs-YIETIWZI.js +280 -0
- package/dist/doctor-M5HJJZOL.js +61 -0
- package/dist/domains/agents/builtins/architect.md +33 -0
- package/dist/domains/agents/builtins/coder.md +31 -0
- package/dist/domains/agents/builtins/context-bootstrap.md +38 -0
- package/dist/domains/agents/builtins/debugger.md +30 -0
- package/dist/domains/agents/builtins/documenter.md +31 -0
- package/dist/domains/agents/builtins/git-master.md +30 -0
- package/dist/domains/agents/builtins/provenance.md +30 -0
- package/dist/domains/agents/builtins/researcher.md +71 -0
- package/dist/domains/agents/builtins/scout.md +42 -0
- package/dist/domains/agents/builtins/tester.md +31 -0
- package/dist/domains/agents/builtins/verifier.md +30 -0
- package/dist/domains/agents/builtins/wiki-writer.md +41 -0
- package/dist/eval-B3KZZESM.js +2674 -0
- package/dist/evidence-V67CHM35.js +233 -0
- package/dist/evolve-YDZSUQYA.js +518 -0
- package/dist/extensions-SRG7XCAH.js +207 -0
- package/dist/fleet-CA2CRTVG.js +760 -0
- package/dist/fleet-preflight-CLIAX7YR.js +21 -0
- package/dist/init-2OZDJE2D.js +227 -0
- package/dist/memory-3PIQQAKX.js +207 -0
- package/dist/models-DY35XI7Y.js +237 -0
- package/dist/paths-5OMXW7Z4.js +57 -0
- package/dist/preload-KZVHET2B.js +11 -0
- package/dist/reset-PIFYNOS3.js +216 -0
- package/dist/run-3VSPP24F.js +735 -0
- package/dist/share-D36RQCXM.js +241 -0
- package/dist/skills-F2MRLELY.js +445 -0
- package/dist/skills-eval-E2ZTW4PL.js +932 -0
- package/dist/targets-DZMEZAH4.js +977 -0
- package/dist/trace-7NYCUI2J.js +250 -0
- package/dist/uninstall-AD3JWHBB.js +322 -0
- package/dist/upgrade-WYYBKGDY.js +301 -0
- package/dist/usage-ULIDAGFF.js +755 -0
- package/dist/version-ROZ6CZKH.js +16 -0
- package/dist/wiki-generate-PKFIX6OB.js +377 -0
- package/dist/worker/entry.js +1739 -0
- package/docs/README.md +93 -0
- package/docs/acp.md +120 -0
- package/docs/alcf-provider.md +72 -0
- package/docs/architecture.md +172 -0
- package/docs/artifact-versions.md +54 -0
- package/docs/built-in-agents.md +265 -0
- package/docs/capacity-and-scheduling.md +97 -0
- package/docs/commands-and-modes.md +554 -0
- package/docs/config-knobs-audit.md +115 -0
- package/docs/configuration-and-targets.md +812 -0
- package/docs/context-engine.md +236 -0
- package/docs/dispatch-architecture-rationale.md +126 -0
- package/docs/documentation-coverage.md +46 -0
- package/docs/documentation-guide.md +166 -0
- package/docs/environment-variables.md +105 -0
- package/docs/eval-runner.md +205 -0
- package/docs/evals-internal.md +298 -0
- package/docs/evidence-and-memory.md +243 -0
- package/docs/evolution.md +143 -0
- package/docs/exit-codes-and-output.md +74 -0
- package/docs/extensions-and-sharing.md +306 -0
- package/docs/fleet-demo-runbook.md +179 -0
- package/docs/fleet-dispatch.md +591 -0
- package/docs/glossary.md +75 -0
- package/docs/html/agents_blueprint.html +936 -0
- package/docs/html/alcf_blueprint.html +324 -0
- package/docs/html/architecture_blueprint.html +850 -0
- package/docs/html/commands_blueprint.html +794 -0
- package/docs/html/config_knobs_audit_blueprint.html +178 -0
- package/docs/html/configuration_blueprint.html +1080 -0
- package/docs/html/context_blueprint.html +603 -0
- package/docs/html/documentation_blueprint.html +832 -0
- package/docs/html/environment_blueprint.html +404 -0
- package/docs/html/eval_blueprint.html +743 -0
- package/docs/html/evals_internal_blueprint.html +190 -0
- package/docs/html/evolution_blueprint.html +674 -0
- package/docs/html/extensions_blueprint.html +2065 -0
- package/docs/html/fleet_dispatch_blueprint.html +286 -0
- package/docs/html/index.html +919 -0
- package/docs/html/lifecycle_blueprint.html +723 -0
- package/docs/html/memory_blueprint.html +699 -0
- package/docs/html/middleware_blueprint.html +664 -0
- package/docs/html/models_blueprint.html +2366 -0
- package/docs/html/observability_blueprint.html +683 -0
- package/docs/html/provider_adapter_blueprint.html +245 -0
- package/docs/html/safety_blueprint.html +1386 -0
- package/docs/html/shared.css +571 -0
- package/docs/html/shared.js +143 -0
- package/docs/html/skills_blueprint.html +671 -0
- package/docs/html/soak_blueprint.html +182 -0
- package/docs/html/tool_usage_blueprint.html +350 -0
- package/docs/html/tools_blueprint.html +2249 -0
- package/docs/html/trace_blueprint.html +235 -0
- package/docs/html/tui_design_blueprint.html +314 -0
- package/docs/html/validation_blueprint.html +961 -0
- package/docs/html/worker_dispatch_blueprint.html +231 -0
- package/docs/installation-and-lifecycle.md +308 -0
- package/docs/middleware-and-components.md +148 -0
- package/docs/model-catalog.md +189 -0
- package/docs/observability.md +233 -0
- package/docs/proactive-memory.md +452 -0
- package/docs/prompt-envelope-and-tools.md +142 -0
- package/docs/provider-adapter-cookbook.md +148 -0
- package/docs/release-cut-checklist.md +138 -0
- package/docs/safety-model.md +357 -0
- package/docs/scientific-validation.md +105 -0
- package/docs/session-lifecycle.md +156 -0
- package/docs/skills-marketplace.md +46 -0
- package/docs/tool-usage.md +527 -0
- package/docs/trace-store.md +132 -0
- package/docs/troubleshooting.md +33 -0
- package/docs/tui-design.md +239 -0
- package/docs/worker-dispatch-mechanics.md +242 -0
- package/package.json +132 -0
- package/skills/README.md +408 -0
- package/skills/git/commit-crafting/SKILL.md +79 -0
- package/skills/git/commit-crafting/evals.md +92 -0
- package/skills/git/create-pr/SKILL.md +116 -0
- package/skills/git/create-pr/evals.md +114 -0
- package/skills/git/investigate-issue/SKILL.md +139 -0
- package/skills/git/investigate-issue/evals.md +94 -0
- package/skills/git/resolve-merge-conflicts/SKILL.md +96 -0
- package/skills/git/resolve-merge-conflicts/evals.md +58 -0
- package/skills/git/review-changes/SKILL.md +103 -0
- package/skills/git/review-changes/evals.md +85 -0
- package/skills/git/worktree-create/SKILL.md +92 -0
- package/skills/git/worktree-create/evals.md +97 -0
- package/skills/git/worktree-create/references/worktree-setup.md +66 -0
- package/skills/git/worktree-merge/SKILL.md +95 -0
- package/skills/git/worktree-merge/evals.md +114 -0
- package/skills/skill-marketplace.json +261 -0
- package/skills/workflow/cut-it/SKILL.md +86 -0
- package/skills/workflow/cut-it/evals.md +42 -0
- package/src/domains/agents/builtins/architect.md +33 -0
- package/src/domains/agents/builtins/coder.md +31 -0
- package/src/domains/agents/builtins/context-bootstrap.md +38 -0
- package/src/domains/agents/builtins/debugger.md +30 -0
- package/src/domains/agents/builtins/documenter.md +31 -0
- package/src/domains/agents/builtins/git-master.md +30 -0
- package/src/domains/agents/builtins/provenance.md +30 -0
- package/src/domains/agents/builtins/researcher.md +71 -0
- package/src/domains/agents/builtins/scout.md +42 -0
- package/src/domains/agents/builtins/tester.md +31 -0
- package/src/domains/agents/builtins/verifier.md +30 -0
- package/src/domains/agents/builtins/wiki-writer.md +41 -0
- package/src/domains/agents/fleets/build-review.md +34 -0
- package/src/domains/agents/fleets/build-test.md +35 -0
- package/src/domains/agents/fleets/sdlc.md +86 -0
- package/src/domains/prompts/fragments/identity/clio-worker.md +11 -0
- package/src/domains/prompts/fragments/identity/clio.md +26 -0
- package/src/domains/prompts/fragments/operating/contract.md +64 -0
- package/src/domains/prompts/fragments/safety/auto-edit.md +14 -0
- package/src/domains/prompts/fragments/safety/full-auto.md +14 -0
- package/src/domains/prompts/fragments/safety/read-only.md +13 -0
- package/src/domains/prompts/fragments/safety/suggest.md +13 -0
- package/src/domains/prompts/fragments/wiki/page.md +75 -0
- package/src/domains/prompts/fragments/wiki/plan.md +48 -0
- package/src/domains/providers/models/cloud-models/alcf.yaml +40 -0
- package/src/domains/providers/models/local-models/clio-local-coding-targets.yaml +993 -0
|
@@ -0,0 +1,1216 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ToolNames,
|
|
3
|
+
isBuiltinToolName
|
|
4
|
+
} from "./chunk-QV47YRF4.js";
|
|
5
|
+
|
|
6
|
+
// src/domains/agents/spec.ts
|
|
7
|
+
import { createHash } from "node:crypto";
|
|
8
|
+
|
|
9
|
+
// src/domains/safety/action-classifier.ts
|
|
10
|
+
import path3 from "node:path";
|
|
11
|
+
|
|
12
|
+
// src/core/path-canonical.ts
|
|
13
|
+
import { realpathSync } from "node:fs";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
function canonicalizeExistingPath(absPath) {
|
|
16
|
+
const resolved = path.resolve(absPath);
|
|
17
|
+
try {
|
|
18
|
+
return path.resolve(realpathSync(resolved));
|
|
19
|
+
} catch {
|
|
20
|
+
}
|
|
21
|
+
const tail = [];
|
|
22
|
+
let cursor = resolved;
|
|
23
|
+
while (true) {
|
|
24
|
+
const parent = path.dirname(cursor);
|
|
25
|
+
if (parent === cursor) {
|
|
26
|
+
try {
|
|
27
|
+
return path.resolve(realpathSync(cursor), ...tail);
|
|
28
|
+
} catch {
|
|
29
|
+
return resolved;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
tail.unshift(path.basename(cursor));
|
|
33
|
+
cursor = parent;
|
|
34
|
+
try {
|
|
35
|
+
return path.resolve(realpathSync(cursor), ...tail);
|
|
36
|
+
} catch {
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/domains/safety/protected-artifacts.ts
|
|
42
|
+
import path2 from "node:path";
|
|
43
|
+
|
|
44
|
+
// src/core/verification-scripts.ts
|
|
45
|
+
var VERIFICATION_SCRIPT_FAMILY_HINT = "test*/lint*/build*/typecheck*/check*/format*/ci*";
|
|
46
|
+
var VERIFICATION_SCRIPT_PATTERN = /^(?:test|lint|build|typecheck|check|format|ci)(?:[:.-].*)?$/;
|
|
47
|
+
function isVerificationScriptName(name) {
|
|
48
|
+
return VERIFICATION_SCRIPT_PATTERN.test(name);
|
|
49
|
+
}
|
|
50
|
+
function declaredVerificationScripts(scripts) {
|
|
51
|
+
return Object.keys(scripts).filter((name) => isVerificationScriptName(name)).sort((a, b) => a.localeCompare(b));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/domains/safety/protected-artifacts.ts
|
|
55
|
+
var COMMAND_SEPARATORS = /* @__PURE__ */ new Set([";", "&&", "||", "|"]);
|
|
56
|
+
var SHELL_WRAPPERS = /* @__PURE__ */ new Set(["command", "builtin", "sudo", "doas"]);
|
|
57
|
+
function toolMutationPaths(toolName, args) {
|
|
58
|
+
if (toolName === ToolNames.Artifact) {
|
|
59
|
+
return [mutationPathArg(args) ?? artifactDefaultPath(args)];
|
|
60
|
+
}
|
|
61
|
+
if (toolName === ToolNames.Write || toolName === ToolNames.Edit) {
|
|
62
|
+
const candidate = mutationPathArg(args);
|
|
63
|
+
return candidate === null ? [] : [candidate];
|
|
64
|
+
}
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
function protectedArtifactMutationBlockReason(state, toolName, args) {
|
|
68
|
+
if (state.artifacts.length === 0) return null;
|
|
69
|
+
for (const candidate of toolMutationPaths(toolName, args)) {
|
|
70
|
+
if (isProtectedPath(state, candidate)) {
|
|
71
|
+
return `protected artifact blocked: ${toolName} would modify protected path ${candidate}`;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (toolName !== ToolNames.Bash) return null;
|
|
75
|
+
const command = typeof args?.command === "string" && args.command.length > 0 ? args.command : null;
|
|
76
|
+
if (command === null) return null;
|
|
77
|
+
const classification = classifyDestructiveCommand(command, state.artifacts);
|
|
78
|
+
if (classification.kind === "benign") return null;
|
|
79
|
+
const affected = classification.matches.map((match) => match.artifactPath).join(", ");
|
|
80
|
+
return `protected artifact blocked: ${classification.operation} would affect ${affected}`;
|
|
81
|
+
}
|
|
82
|
+
function artifactDefaultPath(args) {
|
|
83
|
+
if (args?.kind === "review") return "REVIEW.md";
|
|
84
|
+
if (args?.kind === "report") return "REPORT.md";
|
|
85
|
+
return "PLAN.md";
|
|
86
|
+
}
|
|
87
|
+
function mutationPathArg(args) {
|
|
88
|
+
if (!args) return null;
|
|
89
|
+
const candidate = args.path ?? args.file_path ?? args.filePath;
|
|
90
|
+
return typeof candidate === "string" && candidate.length > 0 ? candidate : null;
|
|
91
|
+
}
|
|
92
|
+
function protectArtifact(state, artifact) {
|
|
93
|
+
const artifacts = artifactMap(state.artifacts);
|
|
94
|
+
const key = normalizePathKey(artifact.path);
|
|
95
|
+
if (key !== null) artifacts.set(key, cloneArtifact(artifact));
|
|
96
|
+
return stateFromMap(artifacts);
|
|
97
|
+
}
|
|
98
|
+
function isProtectedPath(state, artifactPath) {
|
|
99
|
+
const candidate = normalizePathKey(artifactPath);
|
|
100
|
+
if (candidate === null) return false;
|
|
101
|
+
for (const artifact of normalizedArtifacts(state.artifacts)) {
|
|
102
|
+
if (isSameOrDescendant(candidate, artifact.key)) return true;
|
|
103
|
+
}
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
function classifyDestructiveCommand(command, protectedArtifacts) {
|
|
107
|
+
const artifacts = normalizedArtifacts(protectedArtifacts);
|
|
108
|
+
if (artifacts.length === 0) return { kind: "benign", matches: [] };
|
|
109
|
+
const tokens = tokenizeShellLike(command);
|
|
110
|
+
for (const segment of splitSegments(tokens)) {
|
|
111
|
+
const redirect = classifyRedirect(segment, artifacts);
|
|
112
|
+
if (redirect.kind === "destructive") return redirect;
|
|
113
|
+
const commandIndex = commandTokenIndex(segment);
|
|
114
|
+
if (commandIndex === null) continue;
|
|
115
|
+
const executable = basenameToken(segment[commandIndex]);
|
|
116
|
+
if (executable === "rm") {
|
|
117
|
+
const result = classifyPathOperation(
|
|
118
|
+
"rm",
|
|
119
|
+
"rm can remove protected artifacts",
|
|
120
|
+
pathArgs(segment, commandIndex),
|
|
121
|
+
artifacts
|
|
122
|
+
);
|
|
123
|
+
if (result.kind === "destructive") return result;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (executable === "mv") {
|
|
127
|
+
const result = classifyPathOperation(
|
|
128
|
+
"mv",
|
|
129
|
+
"mv can move or overwrite protected artifacts",
|
|
130
|
+
pathArgs(segment, commandIndex),
|
|
131
|
+
artifacts
|
|
132
|
+
);
|
|
133
|
+
if (result.kind === "destructive") return result;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (executable === "truncate") {
|
|
137
|
+
const result = classifyPathOperation(
|
|
138
|
+
"truncate",
|
|
139
|
+
"truncate can overwrite protected artifacts",
|
|
140
|
+
pathArgs(segment, commandIndex),
|
|
141
|
+
artifacts
|
|
142
|
+
);
|
|
143
|
+
if (result.kind === "destructive") return result;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (executable === "cp") {
|
|
147
|
+
const args = pathArgs(segment, commandIndex);
|
|
148
|
+
const destination = args.at(-1);
|
|
149
|
+
const result = classifyPathOperation(
|
|
150
|
+
"cp",
|
|
151
|
+
"cp can overwrite protected artifacts",
|
|
152
|
+
destination === void 0 ? [] : [destination],
|
|
153
|
+
artifacts
|
|
154
|
+
);
|
|
155
|
+
if (result.kind === "destructive") return result;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (executable === "git") {
|
|
159
|
+
const result = classifyGitOperation(segment.slice(commandIndex + 1), artifacts);
|
|
160
|
+
if (result.kind === "destructive") return result;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (executable === "find") {
|
|
164
|
+
const result = classifyFindDelete(segment.slice(commandIndex + 1), artifacts);
|
|
165
|
+
if (result.kind === "destructive") return result;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return { kind: "benign", matches: [] };
|
|
169
|
+
}
|
|
170
|
+
function detectValidationCommand(command, scope = "finish-contract") {
|
|
171
|
+
const tokens = tokenizeShellLike(command);
|
|
172
|
+
for (const segment of splitSegments(tokens)) {
|
|
173
|
+
const commandIndex = commandTokenIndex(segment);
|
|
174
|
+
if (commandIndex === null) continue;
|
|
175
|
+
const executable = basenameToken(segment[commandIndex]);
|
|
176
|
+
const args = segment.slice(commandIndex + 1);
|
|
177
|
+
const matched = validationMatch(executable, args) ?? (scope === "grounding" ? groundingMatch(executable, args) : null);
|
|
178
|
+
if (matched !== null) return { kind: "validation", matched };
|
|
179
|
+
}
|
|
180
|
+
return { kind: "none" };
|
|
181
|
+
}
|
|
182
|
+
function extractCommandWriteTargets(command) {
|
|
183
|
+
const targets = [];
|
|
184
|
+
for (const segment of expandedSegments(command)) {
|
|
185
|
+
collectRedirectTargets(segment, targets);
|
|
186
|
+
collectInvokedWriteTargets(segment, targets);
|
|
187
|
+
collectInPlaceEditTargets(segment, targets);
|
|
188
|
+
}
|
|
189
|
+
return targets.filter(isInterestingWriteTarget);
|
|
190
|
+
}
|
|
191
|
+
var INNER_SHELLS = /* @__PURE__ */ new Set(["sh", "bash", "zsh", "dash", "ksh"]);
|
|
192
|
+
var INNER_SHELL_MAX_DEPTH = 3;
|
|
193
|
+
function segmentShellScript(segment) {
|
|
194
|
+
const commandIndex = commandTokenIndex(segment);
|
|
195
|
+
if (commandIndex === null) return null;
|
|
196
|
+
if (!INNER_SHELLS.has(basenameToken(segment[commandIndex]))) return null;
|
|
197
|
+
for (let index = commandIndex + 1; index < segment.length; index += 1) {
|
|
198
|
+
const token = segment[index];
|
|
199
|
+
if (token === void 0) continue;
|
|
200
|
+
if (token.startsWith("-") && token.length > 1) {
|
|
201
|
+
if (!token.slice(1).includes("c")) continue;
|
|
202
|
+
const script = segment[index + 1];
|
|
203
|
+
return script !== void 0 && script.length > 0 ? script : null;
|
|
204
|
+
}
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
function expandedSegments(command, depth = 0) {
|
|
210
|
+
const segments = splitSegments(tokenizeShellLike(command));
|
|
211
|
+
if (depth >= INNER_SHELL_MAX_DEPTH) return segments;
|
|
212
|
+
const out = [];
|
|
213
|
+
for (const segment of segments) {
|
|
214
|
+
out.push(segment);
|
|
215
|
+
const script = segmentShellScript(segment);
|
|
216
|
+
if (script !== null) out.push(...expandedSegments(script, depth + 1));
|
|
217
|
+
}
|
|
218
|
+
return out;
|
|
219
|
+
}
|
|
220
|
+
function inlineShellScript(command) {
|
|
221
|
+
const segments = splitSegments(tokenizeShellLike(command));
|
|
222
|
+
if (segments.length !== 1) return null;
|
|
223
|
+
const segment = segments[0];
|
|
224
|
+
if (segment === void 0) return null;
|
|
225
|
+
return segmentShellScript(segment);
|
|
226
|
+
}
|
|
227
|
+
function extractCommandCdTargets(command) {
|
|
228
|
+
const targets = [];
|
|
229
|
+
for (const segment of expandedSegments(command)) {
|
|
230
|
+
const commandIndex = commandTokenIndex(segment);
|
|
231
|
+
if (commandIndex === null) continue;
|
|
232
|
+
const executable = basenameToken(segment[commandIndex]);
|
|
233
|
+
if (executable !== "cd" && executable !== "pushd") continue;
|
|
234
|
+
const target = pathArgs(segment, commandIndex).at(0);
|
|
235
|
+
targets.push(target ?? "~");
|
|
236
|
+
}
|
|
237
|
+
return targets;
|
|
238
|
+
}
|
|
239
|
+
function extractCommandDeleteTargets(command) {
|
|
240
|
+
const targets = [];
|
|
241
|
+
for (const segment of expandedSegments(command)) {
|
|
242
|
+
const commandIndex = commandTokenIndex(segment);
|
|
243
|
+
if (commandIndex === null) continue;
|
|
244
|
+
const executable = basenameToken(segment[commandIndex]);
|
|
245
|
+
if (executable === "rm") {
|
|
246
|
+
targets.push(...pathArgs(segment, commandIndex));
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
if (executable === "mv") {
|
|
250
|
+
const args = pathArgs(segment, commandIndex);
|
|
251
|
+
if (args.length >= 2) targets.push(...args.slice(0, -1));
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
if (executable === "find" && segment.includes("-delete")) {
|
|
255
|
+
targets.push(...findRoots(segment.slice(commandIndex + 1)));
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return targets.filter(isInterestingWriteTarget);
|
|
259
|
+
}
|
|
260
|
+
var STANDARD_DEV_TARGETS = /* @__PURE__ */ new Set(["/dev/null", "/dev/stdout", "/dev/stderr", "/dev/tty", "/dev/zero"]);
|
|
261
|
+
function collectRedirectTargets(segment, out) {
|
|
262
|
+
for (let index = 0; index < segment.length - 1; index += 1) {
|
|
263
|
+
const token = segment[index];
|
|
264
|
+
if (token !== ">" && token !== ">>") continue;
|
|
265
|
+
const target = segment[index + 1];
|
|
266
|
+
if (target !== void 0) out.push(target);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
function collectInvokedWriteTargets(segment, out) {
|
|
270
|
+
const cmdIndex = commandTokenIndex(segment);
|
|
271
|
+
if (cmdIndex === null) return;
|
|
272
|
+
const executable = basenameToken(segment[cmdIndex]);
|
|
273
|
+
if (executable === "tee") {
|
|
274
|
+
for (const arg of pathArgs(segment, cmdIndex)) out.push(arg);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if (executable === "mkdir" || executable === "touch") {
|
|
278
|
+
for (const arg of pathArgs(segment, cmdIndex)) out.push(arg);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (executable === "ln") {
|
|
282
|
+
const args = pathArgs(segment, cmdIndex);
|
|
283
|
+
const destination = args.at(-1);
|
|
284
|
+
if (args.length >= 2 && destination !== void 0) out.push(destination);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
if (executable === "cp" || executable === "mv") {
|
|
288
|
+
const args = pathArgs(segment, cmdIndex);
|
|
289
|
+
const destination = args.at(-1);
|
|
290
|
+
if (args.length >= 2 && destination !== void 0) out.push(destination);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
function collectInPlaceEditTargets(segment, out) {
|
|
294
|
+
const cmdIndex = commandTokenIndex(segment);
|
|
295
|
+
if (cmdIndex === null) return;
|
|
296
|
+
if (basenameToken(segment[cmdIndex]) !== "sed") return;
|
|
297
|
+
if (!hasSedInPlaceFlag(segment, cmdIndex)) return;
|
|
298
|
+
for (const operand of sedFileOperands(segment, cmdIndex)) out.push(operand);
|
|
299
|
+
}
|
|
300
|
+
function hasSedInPlaceFlag(segment, cmdIndex) {
|
|
301
|
+
for (let index = cmdIndex + 1; index < segment.length; index += 1) {
|
|
302
|
+
const token = segment[index];
|
|
303
|
+
if (token === void 0) continue;
|
|
304
|
+
if (COMMAND_SEPARATORS.has(token)) break;
|
|
305
|
+
if (token === "--") break;
|
|
306
|
+
if (token === "--in-place" || token.startsWith("--in-place=")) return true;
|
|
307
|
+
if (token.startsWith("--")) continue;
|
|
308
|
+
if (token.startsWith("-") && token.length > 1 && token.slice(1).includes("i")) return true;
|
|
309
|
+
}
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
function sedFileOperands(segment, cmdIndex) {
|
|
313
|
+
const operands = [];
|
|
314
|
+
let sawScriptFlag = false;
|
|
315
|
+
let endOfOptions = false;
|
|
316
|
+
for (let index = cmdIndex + 1; index < segment.length; index += 1) {
|
|
317
|
+
const token = segment[index];
|
|
318
|
+
if (token === void 0) continue;
|
|
319
|
+
if (COMMAND_SEPARATORS.has(token)) break;
|
|
320
|
+
if (!endOfOptions && token === "--") {
|
|
321
|
+
endOfOptions = true;
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
if (!endOfOptions && token.startsWith("-") && token.length > 1) {
|
|
325
|
+
if (token === "-e" || token === "-f") {
|
|
326
|
+
sawScriptFlag = true;
|
|
327
|
+
index += 1;
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
if (token === "-l") {
|
|
331
|
+
index += 1;
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
if (token === "--expression" || token === "--file") {
|
|
335
|
+
sawScriptFlag = true;
|
|
336
|
+
index += 1;
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
if (token.startsWith("--expression=") || token.startsWith("--file=")) {
|
|
340
|
+
sawScriptFlag = true;
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
operands.push(token);
|
|
346
|
+
}
|
|
347
|
+
if (!sawScriptFlag && operands.length > 0) operands.shift();
|
|
348
|
+
return operands;
|
|
349
|
+
}
|
|
350
|
+
function isInterestingWriteTarget(target) {
|
|
351
|
+
if (target.length === 0) return false;
|
|
352
|
+
if (target.startsWith("&")) return false;
|
|
353
|
+
if (STANDARD_DEV_TARGETS.has(target)) return false;
|
|
354
|
+
if (target.startsWith("/dev/fd/")) return false;
|
|
355
|
+
return true;
|
|
356
|
+
}
|
|
357
|
+
function classifyRedirect(segment, artifacts) {
|
|
358
|
+
for (let index = 0; index < segment.length; index += 1) {
|
|
359
|
+
if (segment[index] !== ">") continue;
|
|
360
|
+
const target = segment[index + 1];
|
|
361
|
+
if (target === void 0) continue;
|
|
362
|
+
const matches = matchesForPaths([target], artifacts, "target", "redirect can overwrite protected artifacts");
|
|
363
|
+
if (matches.length > 0) {
|
|
364
|
+
return {
|
|
365
|
+
kind: "destructive",
|
|
366
|
+
operation: "redirect",
|
|
367
|
+
matches
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return { kind: "benign", matches: [] };
|
|
372
|
+
}
|
|
373
|
+
function classifyGitOperation(args, artifacts) {
|
|
374
|
+
const subcommand = args[0];
|
|
375
|
+
if (subcommand === "reset" && args.includes("--hard")) {
|
|
376
|
+
return {
|
|
377
|
+
kind: "destructive",
|
|
378
|
+
operation: "git_reset_hard",
|
|
379
|
+
matches: artifacts.map(({ artifact }) => ({
|
|
380
|
+
artifactPath: artifact.path,
|
|
381
|
+
commandPath: ".",
|
|
382
|
+
reason: "git reset --hard can overwrite protected artifacts"
|
|
383
|
+
}))
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
if (subcommand === "checkout" && args.includes("--")) {
|
|
387
|
+
const paths = args.slice(args.indexOf("--") + 1);
|
|
388
|
+
const matches = matchesForPaths(paths, artifacts, "intersects", "git checkout -- can overwrite protected artifacts");
|
|
389
|
+
if (matches.length > 0) {
|
|
390
|
+
return {
|
|
391
|
+
kind: "destructive",
|
|
392
|
+
operation: "git_checkout",
|
|
393
|
+
matches
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
return { kind: "benign", matches: [] };
|
|
397
|
+
}
|
|
398
|
+
if (subcommand === "restore") {
|
|
399
|
+
const matches = matchesForPaths(
|
|
400
|
+
pathArgs(args.slice(1), -1),
|
|
401
|
+
artifacts,
|
|
402
|
+
"intersects",
|
|
403
|
+
"git restore can overwrite protected artifacts"
|
|
404
|
+
);
|
|
405
|
+
if (matches.length > 0) {
|
|
406
|
+
return {
|
|
407
|
+
kind: "destructive",
|
|
408
|
+
operation: "git_restore",
|
|
409
|
+
matches
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return { kind: "benign", matches: [] };
|
|
414
|
+
}
|
|
415
|
+
function classifyFindDelete(args, artifacts) {
|
|
416
|
+
if (!args.includes("-delete")) return { kind: "benign", matches: [] };
|
|
417
|
+
const roots = findRoots(args);
|
|
418
|
+
const matches = matchesForPaths(
|
|
419
|
+
roots.length === 0 ? ["."] : roots,
|
|
420
|
+
artifacts,
|
|
421
|
+
"intersects",
|
|
422
|
+
"find -delete can remove protected artifacts"
|
|
423
|
+
);
|
|
424
|
+
if (matches.length > 0) {
|
|
425
|
+
return {
|
|
426
|
+
kind: "destructive",
|
|
427
|
+
operation: "find_delete",
|
|
428
|
+
matches
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
return { kind: "benign", matches: [] };
|
|
432
|
+
}
|
|
433
|
+
function findRoots(args) {
|
|
434
|
+
const roots = [];
|
|
435
|
+
for (const token of args) {
|
|
436
|
+
if (token === "-delete" || token.startsWith("-") || token === "(" || token === "!" || token === "not") break;
|
|
437
|
+
if (token === "--") continue;
|
|
438
|
+
roots.push(token);
|
|
439
|
+
}
|
|
440
|
+
return roots.length === 0 ? ["."] : roots;
|
|
441
|
+
}
|
|
442
|
+
function classifyPathOperation(operation, reason, paths, artifacts) {
|
|
443
|
+
const matches = matchesForPaths(paths, artifacts, "intersects", reason);
|
|
444
|
+
if (matches.length === 0) return { kind: "benign", matches: [] };
|
|
445
|
+
return {
|
|
446
|
+
kind: "destructive",
|
|
447
|
+
operation,
|
|
448
|
+
matches
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
function matchesForPaths(commandPaths, artifacts, mode, reason) {
|
|
452
|
+
const matches = [];
|
|
453
|
+
const seen = /* @__PURE__ */ new Set();
|
|
454
|
+
for (const commandPath of commandPaths) {
|
|
455
|
+
for (const artifact of artifacts) {
|
|
456
|
+
if (!pathMatchesArtifact(commandPath, artifact.key, mode)) continue;
|
|
457
|
+
const key = `${artifact.key}\0${commandPath}\0${reason}`;
|
|
458
|
+
if (seen.has(key)) continue;
|
|
459
|
+
seen.add(key);
|
|
460
|
+
matches.push({
|
|
461
|
+
artifactPath: artifact.artifact.path,
|
|
462
|
+
commandPath,
|
|
463
|
+
reason
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
matches.sort(compareMatches);
|
|
468
|
+
return matches;
|
|
469
|
+
}
|
|
470
|
+
function pathMatchesArtifact(commandPath, artifactKey, mode) {
|
|
471
|
+
const wildcardRoot = wildcardRootKey(commandPath);
|
|
472
|
+
if (wildcardRoot !== null) {
|
|
473
|
+
return isSameOrDescendant(artifactKey, wildcardRoot);
|
|
474
|
+
}
|
|
475
|
+
const commandKey = normalizePathKey(commandPath);
|
|
476
|
+
if (commandKey === null) return false;
|
|
477
|
+
if (mode === "target") return isSameOrDescendant(commandKey, artifactKey);
|
|
478
|
+
return isSameOrDescendant(commandKey, artifactKey) || isSameOrDescendant(artifactKey, commandKey);
|
|
479
|
+
}
|
|
480
|
+
function validationMatch(executable, args) {
|
|
481
|
+
if (executable === "npm") {
|
|
482
|
+
if (args[0] === "test") return "npm test";
|
|
483
|
+
const script = args[0] === "run" && typeof args[1] === "string" ? args[1] : null;
|
|
484
|
+
if (script !== null && isVerificationScriptName(script)) return `npm run ${script}`;
|
|
485
|
+
}
|
|
486
|
+
if (executable === "pytest") return "pytest";
|
|
487
|
+
if (isPythonExecutable(executable) && moduleArg(args) === "pytest") return "python -m pytest";
|
|
488
|
+
if (executable === "cargo" && args[0] === "test") return "cargo test";
|
|
489
|
+
if (executable === "go" && args[0] === "test") return "go test";
|
|
490
|
+
if (executable === "ctest") return "ctest";
|
|
491
|
+
if (executable === "make" && args[0] === "test") return "make test";
|
|
492
|
+
if (executable === "ninja" && args[0] === "test") return "ninja test";
|
|
493
|
+
if (executable === "mvn" && args[0] === "test") return "mvn test";
|
|
494
|
+
if ((executable === "gradle" || executable === "gradlew") && args[0] === "test") return "gradle test";
|
|
495
|
+
return null;
|
|
496
|
+
}
|
|
497
|
+
function groundingMatch(executable, args) {
|
|
498
|
+
if (executable === "git") {
|
|
499
|
+
if (args[0] === "diff") return "git diff";
|
|
500
|
+
if (args[0] === "status") return "git status";
|
|
501
|
+
return null;
|
|
502
|
+
}
|
|
503
|
+
if (executable === "node") {
|
|
504
|
+
if (args.includes("--test")) return "node --test";
|
|
505
|
+
if (args.includes("-e") || args.includes("--eval")) return "node -e";
|
|
506
|
+
return null;
|
|
507
|
+
}
|
|
508
|
+
if (executable === "tsc") return typescriptCheckMatch(args);
|
|
509
|
+
if (executable === "npx") {
|
|
510
|
+
const inner = npxInvocation(args);
|
|
511
|
+
return inner === null ? null : npxRunnerMatch(inner.executable, inner.args);
|
|
512
|
+
}
|
|
513
|
+
return null;
|
|
514
|
+
}
|
|
515
|
+
var NPX_TEST_RUNNERS = /* @__PURE__ */ new Set(["vitest", "jest", "mocha"]);
|
|
516
|
+
function npxRunnerMatch(executable, args) {
|
|
517
|
+
if (NPX_TEST_RUNNERS.has(executable)) return `npx ${executable}`;
|
|
518
|
+
if (executable === "tsx" && args.includes("--test")) return "npx tsx --test";
|
|
519
|
+
if (executable === "tsc") return typescriptCheckMatch(args);
|
|
520
|
+
return null;
|
|
521
|
+
}
|
|
522
|
+
function typescriptCheckMatch(args) {
|
|
523
|
+
return args.some((arg) => arg.toLowerCase() === "--noemit") ? "tsc --noEmit" : "tsc";
|
|
524
|
+
}
|
|
525
|
+
function npxInvocation(args) {
|
|
526
|
+
const VALUE_FLAGS = /* @__PURE__ */ new Set(["-p", "--package", "-c", "--call", "--shell"]);
|
|
527
|
+
let index = 0;
|
|
528
|
+
while (index < args.length) {
|
|
529
|
+
const token = args[index];
|
|
530
|
+
if (token === void 0) return null;
|
|
531
|
+
if (VALUE_FLAGS.has(token)) {
|
|
532
|
+
index += 2;
|
|
533
|
+
continue;
|
|
534
|
+
}
|
|
535
|
+
if (token.startsWith("-")) {
|
|
536
|
+
index += 1;
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
return { executable: basenameToken(token), args: args.slice(index + 1) };
|
|
540
|
+
}
|
|
541
|
+
return null;
|
|
542
|
+
}
|
|
543
|
+
function moduleArg(args) {
|
|
544
|
+
for (let index = 0; index < args.length - 1; index += 1) {
|
|
545
|
+
if (args[index] === "-m") return args[index + 1] ?? null;
|
|
546
|
+
}
|
|
547
|
+
return null;
|
|
548
|
+
}
|
|
549
|
+
function isPythonExecutable(executable) {
|
|
550
|
+
return executable === "python" || executable === "python3" || /^python3\.\d+$/.test(executable);
|
|
551
|
+
}
|
|
552
|
+
function pathArgs(segment, commandIndex) {
|
|
553
|
+
const args = [];
|
|
554
|
+
let endOfOptions = false;
|
|
555
|
+
for (let index = commandIndex + 1; index < segment.length; index += 1) {
|
|
556
|
+
const token = segment[index];
|
|
557
|
+
if (token === void 0) continue;
|
|
558
|
+
if (COMMAND_SEPARATORS.has(token)) break;
|
|
559
|
+
if (!endOfOptions && token === "--") {
|
|
560
|
+
endOfOptions = true;
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
if (!endOfOptions && token.startsWith("-")) continue;
|
|
564
|
+
if (token === ">" || token === ">>" || token === "<" || token === "<<") {
|
|
565
|
+
index += 1;
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
args.push(token);
|
|
569
|
+
}
|
|
570
|
+
return args;
|
|
571
|
+
}
|
|
572
|
+
function tokenizeShellLike(command) {
|
|
573
|
+
const tokens = [];
|
|
574
|
+
let current = "";
|
|
575
|
+
let quote = null;
|
|
576
|
+
const pushCurrent = () => {
|
|
577
|
+
if (current.length === 0) return;
|
|
578
|
+
tokens.push(current);
|
|
579
|
+
current = "";
|
|
580
|
+
};
|
|
581
|
+
for (let index = 0; index < command.length; index += 1) {
|
|
582
|
+
const char = command[index];
|
|
583
|
+
if (char === void 0) continue;
|
|
584
|
+
if (quote !== null) {
|
|
585
|
+
if (char === quote) {
|
|
586
|
+
quote = null;
|
|
587
|
+
continue;
|
|
588
|
+
}
|
|
589
|
+
if (quote === '"' && char === "\\" && index + 1 < command.length) {
|
|
590
|
+
index += 1;
|
|
591
|
+
current += command[index] ?? "";
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
current += char;
|
|
595
|
+
continue;
|
|
596
|
+
}
|
|
597
|
+
if (char === "'" || char === '"') {
|
|
598
|
+
quote = char;
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
601
|
+
if (char === "\\" && index + 1 < command.length) {
|
|
602
|
+
index += 1;
|
|
603
|
+
current += command[index] ?? "";
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
if (/\s/.test(char)) {
|
|
607
|
+
pushCurrent();
|
|
608
|
+
if (char === "\n") tokens.push(";");
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
if (char === ";") {
|
|
612
|
+
pushCurrent();
|
|
613
|
+
tokens.push(";");
|
|
614
|
+
continue;
|
|
615
|
+
}
|
|
616
|
+
if (char === "&" && command[index + 1] === "&") {
|
|
617
|
+
pushCurrent();
|
|
618
|
+
tokens.push("&&");
|
|
619
|
+
index += 1;
|
|
620
|
+
continue;
|
|
621
|
+
}
|
|
622
|
+
if (char === "|") {
|
|
623
|
+
pushCurrent();
|
|
624
|
+
if (command[index + 1] === "|") {
|
|
625
|
+
tokens.push("||");
|
|
626
|
+
index += 1;
|
|
627
|
+
} else {
|
|
628
|
+
tokens.push("|");
|
|
629
|
+
}
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
if (char === ">") {
|
|
633
|
+
pushCurrent();
|
|
634
|
+
if (command[index + 1] === ">") {
|
|
635
|
+
tokens.push(">>");
|
|
636
|
+
index += 1;
|
|
637
|
+
} else {
|
|
638
|
+
tokens.push(">");
|
|
639
|
+
}
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
if (char === "<") {
|
|
643
|
+
pushCurrent();
|
|
644
|
+
if (command[index + 1] === "<") {
|
|
645
|
+
tokens.push("<<");
|
|
646
|
+
index += 1;
|
|
647
|
+
} else {
|
|
648
|
+
tokens.push("<");
|
|
649
|
+
}
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
current += char;
|
|
653
|
+
}
|
|
654
|
+
pushCurrent();
|
|
655
|
+
return tokens;
|
|
656
|
+
}
|
|
657
|
+
function splitSegments(tokens) {
|
|
658
|
+
const segments = [];
|
|
659
|
+
let current = [];
|
|
660
|
+
for (const token of tokens) {
|
|
661
|
+
if (COMMAND_SEPARATORS.has(token)) {
|
|
662
|
+
if (current.length > 0) segments.push(current);
|
|
663
|
+
current = [];
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
current.push(token);
|
|
667
|
+
}
|
|
668
|
+
if (current.length > 0) segments.push(current);
|
|
669
|
+
return segments;
|
|
670
|
+
}
|
|
671
|
+
function commandTokenIndex(segment) {
|
|
672
|
+
let index = 0;
|
|
673
|
+
while (index < segment.length) {
|
|
674
|
+
const token = segment[index];
|
|
675
|
+
if (token === void 0) return null;
|
|
676
|
+
if (isEnvAssignment(token)) {
|
|
677
|
+
index += 1;
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
if (token === "env") {
|
|
681
|
+
index += 1;
|
|
682
|
+
while (index < segment.length && (segment[index]?.startsWith("-") === true || isEnvAssignment(segment[index] ?? ""))) {
|
|
683
|
+
index += 1;
|
|
684
|
+
}
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
687
|
+
if (SHELL_WRAPPERS.has(token)) {
|
|
688
|
+
index += 1;
|
|
689
|
+
while (index < segment.length && segment[index]?.startsWith("-") === true) index += 1;
|
|
690
|
+
continue;
|
|
691
|
+
}
|
|
692
|
+
return index;
|
|
693
|
+
}
|
|
694
|
+
return null;
|
|
695
|
+
}
|
|
696
|
+
function isEnvAssignment(token) {
|
|
697
|
+
return /^[A-Za-z_][A-Za-z0-9_]*=/.test(token);
|
|
698
|
+
}
|
|
699
|
+
function basenameToken(token) {
|
|
700
|
+
if (token === void 0) return "";
|
|
701
|
+
const normalized = token.replace(/\\/g, "/");
|
|
702
|
+
const basename = normalized.slice(normalized.lastIndexOf("/") + 1);
|
|
703
|
+
return basename.toLowerCase();
|
|
704
|
+
}
|
|
705
|
+
function artifactMap(artifacts) {
|
|
706
|
+
const out = /* @__PURE__ */ new Map();
|
|
707
|
+
for (const artifact of artifacts) {
|
|
708
|
+
const key = normalizePathKey(artifact.path);
|
|
709
|
+
if (key !== null) out.set(key, cloneArtifact(artifact));
|
|
710
|
+
}
|
|
711
|
+
return out;
|
|
712
|
+
}
|
|
713
|
+
function stateFromMap(artifacts) {
|
|
714
|
+
return {
|
|
715
|
+
artifacts: Array.from(artifacts.entries()).sort(([left], [right]) => compareStrings(left, right)).map(([, artifact]) => cloneArtifact(artifact))
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
function normalizedArtifacts(artifacts) {
|
|
719
|
+
const out = Array.from(artifactMap(artifacts).entries()).map(([key, artifact]) => ({ key, artifact }));
|
|
720
|
+
out.sort((left, right) => compareStrings(left.key, right.key));
|
|
721
|
+
return out;
|
|
722
|
+
}
|
|
723
|
+
function cloneArtifact(artifact) {
|
|
724
|
+
const cloned = {
|
|
725
|
+
path: artifact.path,
|
|
726
|
+
protectedAt: artifact.protectedAt,
|
|
727
|
+
reason: artifact.reason,
|
|
728
|
+
source: artifact.source
|
|
729
|
+
};
|
|
730
|
+
if (artifact.validationCommand !== void 0) cloned.validationCommand = artifact.validationCommand;
|
|
731
|
+
if (artifact.validationExitCode !== void 0) cloned.validationExitCode = artifact.validationExitCode;
|
|
732
|
+
return cloned;
|
|
733
|
+
}
|
|
734
|
+
function normalizePathKey(input) {
|
|
735
|
+
const trimmed = input.trim();
|
|
736
|
+
if (trimmed.length === 0) return null;
|
|
737
|
+
if (isWindowsAbsolutePath(trimmed) && path2.sep !== "\\") {
|
|
738
|
+
return toSlashKey(path2.win32.normalize(trimmed));
|
|
739
|
+
}
|
|
740
|
+
const resolved = path2.isAbsolute(trimmed) ? trimmed : path2.resolve(trimmed);
|
|
741
|
+
return toSlashKey(canonicalizeExistingPath(resolved));
|
|
742
|
+
}
|
|
743
|
+
function wildcardRootKey(input) {
|
|
744
|
+
const normalizedInput = toSlashKey(input.trim());
|
|
745
|
+
const firstWildcard = firstWildcardIndex(normalizedInput);
|
|
746
|
+
if (firstWildcard === null) return null;
|
|
747
|
+
const prefix = normalizedInput.slice(0, firstWildcard);
|
|
748
|
+
const slash = prefix.lastIndexOf("/");
|
|
749
|
+
const root = slash >= 0 ? prefix.slice(0, slash + 1) : ".";
|
|
750
|
+
return normalizePathKey(root);
|
|
751
|
+
}
|
|
752
|
+
function firstWildcardIndex(input) {
|
|
753
|
+
const indexes = ["*", "?", "["].map((char) => input.indexOf(char)).filter((index) => index >= 0);
|
|
754
|
+
if (indexes.length === 0) return null;
|
|
755
|
+
return Math.min(...indexes);
|
|
756
|
+
}
|
|
757
|
+
function isWindowsAbsolutePath(input) {
|
|
758
|
+
return /^[A-Za-z]:[\\/]/.test(input);
|
|
759
|
+
}
|
|
760
|
+
function toSlashKey(input) {
|
|
761
|
+
return input.replace(/\\/g, "/");
|
|
762
|
+
}
|
|
763
|
+
function isSameOrDescendant(candidate, parent) {
|
|
764
|
+
if (candidate === parent) return true;
|
|
765
|
+
const relative = path2.posix.relative(parent, candidate);
|
|
766
|
+
return relative !== "" && !relative.startsWith("..") && !path2.posix.isAbsolute(relative);
|
|
767
|
+
}
|
|
768
|
+
function compareMatches(left, right) {
|
|
769
|
+
return compareStrings(left.artifactPath, right.artifactPath) || compareStrings(left.commandPath, right.commandPath) || compareStrings(left.reason, right.reason);
|
|
770
|
+
}
|
|
771
|
+
function compareStrings(left, right) {
|
|
772
|
+
if (left < right) return -1;
|
|
773
|
+
if (left > right) return 1;
|
|
774
|
+
return 0;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// src/domains/safety/action-classifier.ts
|
|
778
|
+
var GIT_DESTRUCTIVE_PATTERNS = [
|
|
779
|
+
{ name: "git-push-force-long", re: /\bgit\s+push\s+--force\b/i },
|
|
780
|
+
{ name: "git-push-force-short", re: /\bgit\s+push\s+-f\b/i },
|
|
781
|
+
{ name: "git-reset-hard", re: /\bgit\s+reset\s+--hard\b/i },
|
|
782
|
+
{ name: "git-clean-fd", re: /\bgit\s+clean\s+-fd?\b/i },
|
|
783
|
+
{ name: "git-checkout-dot", re: /\bgit\s+checkout\s+--\s+\./i },
|
|
784
|
+
{ name: "git-branch-D", re: /\bgit\s+branch\s+-D\b/i },
|
|
785
|
+
{ name: "git-restore-source", re: /\bgit\s+restore\s+--source\b/i }
|
|
786
|
+
];
|
|
787
|
+
var SYSTEM_MODIFY_PATTERNS = [
|
|
788
|
+
{ name: "sudo-or-doas", re: /\b(sudo|doas)\b/i },
|
|
789
|
+
// allow rm -rf /tmp/... and /var/tmp/... but flag everything else rooted at /
|
|
790
|
+
{ name: "rm-rf-root", re: /\brm\s+-rf?\s+\/(?!(tmp|var\/tmp)(?:\/|\s|$))/i },
|
|
791
|
+
{ name: "apt-install", re: /\bapt(-get)?\s+(install|remove|purge)/i },
|
|
792
|
+
{ name: "brew-install", re: /\bbrew\s+(install|uninstall|reinstall)/i },
|
|
793
|
+
{ name: "npm-install-global", re: /\bnpm\s+install\s+-g\b/i },
|
|
794
|
+
{ name: "pip-install", re: /\bpip\s+install\b/i },
|
|
795
|
+
{ name: "systemctl", re: /\bsystemctl\s+/i },
|
|
796
|
+
{ name: "chmod-root", re: /\bchmod\s+[0-7]{3,4}\s+\//i },
|
|
797
|
+
{ name: "chown", re: /\bchown\s+/i }
|
|
798
|
+
];
|
|
799
|
+
var SYSTEM_WRITE_ROOT_PREFIXES = ["/etc", "/usr", "/var", "/bin", "/sbin", "/run"];
|
|
800
|
+
var SYSTEM_WRITE_EXEMPT_PREFIXES = ["/var/tmp", "/var/folders"];
|
|
801
|
+
function isUnderPrefix(abs, prefix) {
|
|
802
|
+
return abs === prefix || abs.startsWith(`${prefix}/`);
|
|
803
|
+
}
|
|
804
|
+
function baseClassify(tool) {
|
|
805
|
+
switch (tool) {
|
|
806
|
+
case ToolNames.Read:
|
|
807
|
+
case ToolNames.Grep:
|
|
808
|
+
case ToolNames.Find:
|
|
809
|
+
case ToolNames.Ls:
|
|
810
|
+
case ToolNames.WebFetch:
|
|
811
|
+
case ToolNames.Git:
|
|
812
|
+
case ToolNames.CodeNav:
|
|
813
|
+
case ToolNames.Context:
|
|
814
|
+
case ToolNames.Monitor:
|
|
815
|
+
case ToolNames.AskUser:
|
|
816
|
+
case ToolNames.CredentialPresent:
|
|
817
|
+
// tasks mutates only the session's task ledger, never the workspace,
|
|
818
|
+
// so it stays read class and is never gated behind a confirmation.
|
|
819
|
+
case ToolNames.Tasks:
|
|
820
|
+
// ledger posts a typed contribution to a coordination board and reads a
|
|
821
|
+
// local mirror. Neither touches the workspace, so it is never gated
|
|
822
|
+
// behind a confirmation.
|
|
823
|
+
case ToolNames.Ledger:
|
|
824
|
+
return "read";
|
|
825
|
+
case ToolNames.Write:
|
|
826
|
+
case ToolNames.Edit:
|
|
827
|
+
case ToolNames.Artifact:
|
|
828
|
+
return "write";
|
|
829
|
+
case ToolNames.Bash:
|
|
830
|
+
case ToolNames.Verify:
|
|
831
|
+
return "execute";
|
|
832
|
+
case ToolNames.Dispatch:
|
|
833
|
+
case ToolNames.Steer:
|
|
834
|
+
return "dispatch";
|
|
835
|
+
default:
|
|
836
|
+
return null;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
function scanStringOf(args) {
|
|
840
|
+
if (!args) return "";
|
|
841
|
+
const parts = [];
|
|
842
|
+
for (const value of Object.values(args)) {
|
|
843
|
+
if (value == null) continue;
|
|
844
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
845
|
+
parts.push(String(value));
|
|
846
|
+
} else {
|
|
847
|
+
try {
|
|
848
|
+
parts.push(JSON.stringify(value));
|
|
849
|
+
} catch {
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
return parts.join(" ");
|
|
854
|
+
}
|
|
855
|
+
function matchFirst(patterns, haystack) {
|
|
856
|
+
for (const p of patterns) {
|
|
857
|
+
if (p.re.test(haystack)) return p;
|
|
858
|
+
}
|
|
859
|
+
return null;
|
|
860
|
+
}
|
|
861
|
+
function resolveCandidate(p, baseCwd) {
|
|
862
|
+
if (p.startsWith("~")) return p;
|
|
863
|
+
const base = canonicalizeExistingPath(path3.resolve(baseCwd ?? process.cwd()));
|
|
864
|
+
const resolved = path3.isAbsolute(p) ? path3.resolve(p) : path3.resolve(base, p);
|
|
865
|
+
return canonicalizeExistingPath(resolved);
|
|
866
|
+
}
|
|
867
|
+
function isInsideCwd(abs) {
|
|
868
|
+
const cwd = canonicalizeExistingPath(path3.resolve(process.cwd()));
|
|
869
|
+
const candidate = canonicalizeExistingPath(abs);
|
|
870
|
+
const rel = path3.relative(cwd, candidate);
|
|
871
|
+
return rel === "" || !rel.startsWith("..") && !path3.isAbsolute(rel);
|
|
872
|
+
}
|
|
873
|
+
function writePathClass(pathArg, baseCwd) {
|
|
874
|
+
if (pathArg.startsWith("~")) {
|
|
875
|
+
return { cls: "system_modify", reason: `write-path-home-escape: ${pathArg}` };
|
|
876
|
+
}
|
|
877
|
+
const abs = resolveCandidate(pathArg, baseCwd);
|
|
878
|
+
const exempt = SYSTEM_WRITE_EXEMPT_PREFIXES.some((prefix) => isUnderPrefix(abs, prefix));
|
|
879
|
+
if (!exempt) {
|
|
880
|
+
for (const prefix of SYSTEM_WRITE_ROOT_PREFIXES) {
|
|
881
|
+
if (isUnderPrefix(abs, prefix)) {
|
|
882
|
+
return { cls: "system_modify", reason: `write-path-system-root: ${prefix}` };
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
if (!isInsideCwd(abs)) {
|
|
887
|
+
return { cls: "system_modify", reason: `write-path-outside-cwd: ${abs}` };
|
|
888
|
+
}
|
|
889
|
+
return { cls: "write" };
|
|
890
|
+
}
|
|
891
|
+
function artifactDefaultPath2(args) {
|
|
892
|
+
const kind = args?.kind;
|
|
893
|
+
return kind === "review" ? "REVIEW.md" : kind === "report" ? "REPORT.md" : "PLAN.md";
|
|
894
|
+
}
|
|
895
|
+
function extractWritePath(tool, args) {
|
|
896
|
+
const candidate = args?.path ?? args?.file_path ?? args?.filePath;
|
|
897
|
+
if (typeof candidate === "string" && candidate.length > 0) return candidate;
|
|
898
|
+
return tool === ToolNames.Artifact ? artifactDefaultPath2(args) : null;
|
|
899
|
+
}
|
|
900
|
+
function classify(call) {
|
|
901
|
+
const base = baseClassify(call.tool);
|
|
902
|
+
if (base === null) {
|
|
903
|
+
return { actionClass: "unknown", reasons: [`unknown tool: ${call.tool}`] };
|
|
904
|
+
}
|
|
905
|
+
const reasons = [];
|
|
906
|
+
if (call.tool === ToolNames.Bash) {
|
|
907
|
+
const scan = scanStringOf(call.args);
|
|
908
|
+
const gitHit = matchFirst(GIT_DESTRUCTIVE_PATTERNS, scan);
|
|
909
|
+
if (gitHit) {
|
|
910
|
+
return { actionClass: "git_destructive", reasons: [`pattern:${gitHit.name}`] };
|
|
911
|
+
}
|
|
912
|
+
const sysHit = matchFirst(SYSTEM_MODIFY_PATTERNS, scan);
|
|
913
|
+
if (sysHit) {
|
|
914
|
+
return { actionClass: "system_modify", reasons: [`pattern:${sysHit.name}`] };
|
|
915
|
+
}
|
|
916
|
+
const command = typeof call.args?.command === "string" ? call.args.command : null;
|
|
917
|
+
const argCwd = typeof call.args?.cwd === "string" && call.args.cwd.length > 0 ? call.args.cwd : void 0;
|
|
918
|
+
if (command !== null) {
|
|
919
|
+
const targetReasons = [];
|
|
920
|
+
for (const target of extractCommandWriteTargets(command)) {
|
|
921
|
+
const decision = writePathClass(target, argCwd);
|
|
922
|
+
if (decision.cls === "system_modify") {
|
|
923
|
+
targetReasons.push(decision.reason ?? `bash-write-target: ${target}`);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
for (const target of extractCommandCdTargets(command)) {
|
|
927
|
+
if (target.startsWith("~")) {
|
|
928
|
+
targetReasons.push(`bash-cd-home-escape: ${target}`);
|
|
929
|
+
continue;
|
|
930
|
+
}
|
|
931
|
+
const abs = resolveCandidate(target, argCwd);
|
|
932
|
+
if (!isInsideCwd(abs)) {
|
|
933
|
+
targetReasons.push(`bash-cd-outside-workspace: ${abs}`);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
if (targetReasons.length > 0) {
|
|
937
|
+
return { actionClass: "system_modify", reasons: targetReasons };
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
return { actionClass: "execute", reasons };
|
|
941
|
+
}
|
|
942
|
+
if (base === "write") {
|
|
943
|
+
const pathArg = extractWritePath(call.tool, call.args);
|
|
944
|
+
if (pathArg) {
|
|
945
|
+
const decision = writePathClass(pathArg);
|
|
946
|
+
if (decision.cls === "system_modify") {
|
|
947
|
+
return { actionClass: "system_modify", reasons: decision.reason ? [decision.reason] : [] };
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
return { actionClass: "write", reasons };
|
|
951
|
+
}
|
|
952
|
+
return { actionClass: base, reasons };
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
// src/domains/agents/spec.ts
|
|
956
|
+
var AGENT_AUTOMATION_AUTHORITIES = ["read-only", "verification", "artifact-write", "workspace-edit"];
|
|
957
|
+
var AGENT_PRODUCTS = ["orientation"];
|
|
958
|
+
var AGENT_CATEGORIES = [
|
|
959
|
+
"explore",
|
|
960
|
+
"plan",
|
|
961
|
+
"research",
|
|
962
|
+
"implement",
|
|
963
|
+
"quality",
|
|
964
|
+
"science",
|
|
965
|
+
"evolution",
|
|
966
|
+
"operations",
|
|
967
|
+
"internal"
|
|
968
|
+
];
|
|
969
|
+
var AGENT_CAPABILITY_CLASSES = [
|
|
970
|
+
"read-only",
|
|
971
|
+
"artifact-write",
|
|
972
|
+
"workspace-edit",
|
|
973
|
+
"verification",
|
|
974
|
+
"orchestration",
|
|
975
|
+
"internal"
|
|
976
|
+
];
|
|
977
|
+
var AGENT_LATENCY_CLASSES = ["fast", "balanced", "deep"];
|
|
978
|
+
var AGENT_AUDIENCES = ["base", "shadow", "custom", "internal"];
|
|
979
|
+
var AGENT_PROJECT_CONTEXT_TIERS = ["none", "bounded"];
|
|
980
|
+
function includes(values, value) {
|
|
981
|
+
return typeof value === "string" && values.includes(value);
|
|
982
|
+
}
|
|
983
|
+
function isAgentCategory(value) {
|
|
984
|
+
return includes(AGENT_CATEGORIES, value);
|
|
985
|
+
}
|
|
986
|
+
function isAgentCapabilityClass(value) {
|
|
987
|
+
return includes(AGENT_CAPABILITY_CLASSES, value);
|
|
988
|
+
}
|
|
989
|
+
function isAgentLatencyClass(value) {
|
|
990
|
+
return includes(AGENT_LATENCY_CLASSES, value);
|
|
991
|
+
}
|
|
992
|
+
function isAgentAudience(value) {
|
|
993
|
+
return includes(AGENT_AUDIENCES, value);
|
|
994
|
+
}
|
|
995
|
+
function isAgentProjectContextTier(value) {
|
|
996
|
+
return includes(AGENT_PROJECT_CONTEXT_TIERS, value);
|
|
997
|
+
}
|
|
998
|
+
function isAgentProduct(value) {
|
|
999
|
+
return includes(AGENT_PRODUCTS, value);
|
|
1000
|
+
}
|
|
1001
|
+
function actionClassesForTools(tools) {
|
|
1002
|
+
const actions = /* @__PURE__ */ new Set();
|
|
1003
|
+
for (const tool of tools) actions.add(classify({ tool }).actionClass);
|
|
1004
|
+
return actions;
|
|
1005
|
+
}
|
|
1006
|
+
function normalizeTools(recipe) {
|
|
1007
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1008
|
+
const tools = [];
|
|
1009
|
+
for (const tool of recipe.tools) {
|
|
1010
|
+
if (typeof tool !== "string" || tool.trim().length === 0) {
|
|
1011
|
+
throw new Error(`agent recipe '${recipe.id}' declares an invalid tool name`);
|
|
1012
|
+
}
|
|
1013
|
+
if (seen.has(tool)) continue;
|
|
1014
|
+
seen.add(tool);
|
|
1015
|
+
tools.push(isBuiltinToolName(tool) ? tool : tool);
|
|
1016
|
+
}
|
|
1017
|
+
return tools;
|
|
1018
|
+
}
|
|
1019
|
+
function asToolName(tool) {
|
|
1020
|
+
return isBuiltinToolName(tool) ? tool : tool;
|
|
1021
|
+
}
|
|
1022
|
+
function normalizeToolRequirements(recipe, tools) {
|
|
1023
|
+
if (!recipe.toolRequirements) {
|
|
1024
|
+
throw new Error(`agent recipe '${recipe.id}' must declare tools.required and tools.optional`);
|
|
1025
|
+
}
|
|
1026
|
+
const declared = new Set(tools);
|
|
1027
|
+
const required = recipe.toolRequirements.required.map((requirement, index) => {
|
|
1028
|
+
if (typeof requirement === "string") return asToolName(requirement);
|
|
1029
|
+
if (!Array.isArray(requirement.anyOf) || requirement.anyOf.length === 0) {
|
|
1030
|
+
throw new Error(`agent recipe '${recipe.id}' tools.required[${index}].anyOf must be non-empty`);
|
|
1031
|
+
}
|
|
1032
|
+
return { anyOf: requirement.anyOf.map(asToolName) };
|
|
1033
|
+
});
|
|
1034
|
+
const optional = recipe.toolRequirements.optional.map(asToolName);
|
|
1035
|
+
const mentioned = required.flatMap(
|
|
1036
|
+
(requirement) => typeof requirement === "string" ? [requirement] : requirement.anyOf
|
|
1037
|
+
);
|
|
1038
|
+
for (const tool of [...mentioned, ...optional]) {
|
|
1039
|
+
if (!declared.has(tool)) throw new Error(`agent recipe '${recipe.id}' requirement '${tool}' is not declared`);
|
|
1040
|
+
}
|
|
1041
|
+
if ((/* @__PURE__ */ new Set([...mentioned, ...optional])).size !== mentioned.length + optional.length) {
|
|
1042
|
+
throw new Error(`agent recipe '${recipe.id}' contains duplicate tool requirements`);
|
|
1043
|
+
}
|
|
1044
|
+
if ((/* @__PURE__ */ new Set([...mentioned, ...optional])).size !== tools.length) {
|
|
1045
|
+
throw new Error(`agent recipe '${recipe.id}' has declared tools without required/optional semantics`);
|
|
1046
|
+
}
|
|
1047
|
+
return { required, optional };
|
|
1048
|
+
}
|
|
1049
|
+
function resolveAgentToolCompatibility(spec, effectiveTools, options) {
|
|
1050
|
+
const available = new Set(effectiveTools);
|
|
1051
|
+
const missingRequired = [];
|
|
1052
|
+
for (const requirement of spec.toolRequirements.required) {
|
|
1053
|
+
if (typeof requirement === "string") {
|
|
1054
|
+
if (!available.has(requirement)) missingRequired.push(requirement);
|
|
1055
|
+
continue;
|
|
1056
|
+
}
|
|
1057
|
+
if (!requirement.anyOf.some((tool) => available.has(tool))) {
|
|
1058
|
+
missingRequired.push(`anyOf(${requirement.anyOf.join("|")})`);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
if (available.has(ToolNames.Dispatch) && !options.mediatesDispatch) {
|
|
1062
|
+
missingRequired.push("dispatch(runtime mediation unavailable)");
|
|
1063
|
+
}
|
|
1064
|
+
return {
|
|
1065
|
+
compatible: missingRequired.length === 0,
|
|
1066
|
+
missingRequired,
|
|
1067
|
+
lostOptional: spec.toolRequirements.optional.filter((tool) => !available.has(tool))
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
function normalizeAgentSpec(recipe) {
|
|
1071
|
+
const tools = normalizeTools(recipe);
|
|
1072
|
+
const toolRequirements = normalizeToolRequirements(recipe, tools);
|
|
1073
|
+
return {
|
|
1074
|
+
version: recipe.version,
|
|
1075
|
+
id: recipe.id,
|
|
1076
|
+
name: recipe.name,
|
|
1077
|
+
description: recipe.description,
|
|
1078
|
+
source: recipe.source,
|
|
1079
|
+
filepath: recipe.filepath,
|
|
1080
|
+
tools,
|
|
1081
|
+
toolRequirements,
|
|
1082
|
+
category: recipe.category,
|
|
1083
|
+
capabilityClass: recipe.capabilityClass,
|
|
1084
|
+
latencyClass: recipe.latencyClass,
|
|
1085
|
+
projectContextTier: recipe.projectContextTier,
|
|
1086
|
+
audience: recipe.audience,
|
|
1087
|
+
tags: recipe.tags,
|
|
1088
|
+
skills: recipe.skills,
|
|
1089
|
+
resultContract: recipe.resultContract,
|
|
1090
|
+
...recipe.product !== void 0 ? { product: recipe.product } : {},
|
|
1091
|
+
budget: recipe.budget,
|
|
1092
|
+
body: recipe.body
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
function agentSpecFingerprint(spec) {
|
|
1096
|
+
const requirement = (entry) => typeof entry === "string" ? entry : `anyOf(${[...entry.anyOf].sort().join("|")})`;
|
|
1097
|
+
const payload = JSON.stringify({
|
|
1098
|
+
id: spec.id,
|
|
1099
|
+
tools: [...spec.tools].sort(),
|
|
1100
|
+
required: spec.toolRequirements.required.map(requirement).sort(),
|
|
1101
|
+
optional: [...spec.toolRequirements.optional].sort(),
|
|
1102
|
+
capabilityClass: spec.capabilityClass,
|
|
1103
|
+
latencyClass: spec.latencyClass,
|
|
1104
|
+
projectContextTier: spec.projectContextTier,
|
|
1105
|
+
audience: spec.audience,
|
|
1106
|
+
skills: [...spec.skills].sort(),
|
|
1107
|
+
resultContract: spec.resultContract,
|
|
1108
|
+
budget: spec.budget,
|
|
1109
|
+
body: createHash("sha256").update(spec.body, "utf8").digest("hex")
|
|
1110
|
+
});
|
|
1111
|
+
return createHash("sha256").update(payload, "utf8").digest("hex");
|
|
1112
|
+
}
|
|
1113
|
+
function isUserVisibleAgent(spec) {
|
|
1114
|
+
return spec.audience === "base" || spec.audience === "custom";
|
|
1115
|
+
}
|
|
1116
|
+
function isShadowAgent(spec) {
|
|
1117
|
+
return spec.audience === "shadow" || spec.audience === "internal";
|
|
1118
|
+
}
|
|
1119
|
+
function agentSpecPolicyErrors(spec) {
|
|
1120
|
+
const errors = [];
|
|
1121
|
+
const actions = actionClassesForTools(spec.tools);
|
|
1122
|
+
const requiredGroups = spec.toolRequirements.required.map(
|
|
1123
|
+
(requirement) => typeof requirement === "string" ? [requirement] : requirement.anyOf
|
|
1124
|
+
);
|
|
1125
|
+
const requiresTool = (tool) => requiredGroups.some((group) => group.length > 0 && group.every((candidate) => candidate === tool));
|
|
1126
|
+
const requiresMutation = requiredGroups.some(
|
|
1127
|
+
(group) => group.length > 0 && group.every((tool) => tool === ToolNames.Write || tool === ToolNames.Edit)
|
|
1128
|
+
);
|
|
1129
|
+
if (spec.capabilityClass === "workspace-edit" && (!requiresTool(ToolNames.Read) || !requiresMutation)) {
|
|
1130
|
+
errors.push(`workspace-edit agent '${spec.id}' must require read and write|edit`);
|
|
1131
|
+
}
|
|
1132
|
+
if (spec.capabilityClass === "verification" && !requiresTool(ToolNames.Verify)) {
|
|
1133
|
+
errors.push(`verification agent '${spec.id}' must require verify`);
|
|
1134
|
+
}
|
|
1135
|
+
if (spec.capabilityClass === "artifact-write" && !requiresTool(ToolNames.Artifact)) {
|
|
1136
|
+
errors.push(`artifact-write agent '${spec.id}' must require artifact`);
|
|
1137
|
+
}
|
|
1138
|
+
if (spec.skills.length > 0 && !requiresTool(ToolNames.Context)) {
|
|
1139
|
+
errors.push(`agent '${spec.id}' must require context for bound skills`);
|
|
1140
|
+
}
|
|
1141
|
+
if (spec.capabilityClass === "orchestration" && spec.tools.includes(ToolNames.Dispatch) && !requiresTool(ToolNames.Dispatch)) {
|
|
1142
|
+
errors.push(`orchestration agent '${spec.id}' must require dispatch when it is declared`);
|
|
1143
|
+
}
|
|
1144
|
+
const toolList = spec.tools.join(", ");
|
|
1145
|
+
if (spec.capabilityClass === "read-only") {
|
|
1146
|
+
for (const action of actions) {
|
|
1147
|
+
if (action !== "read") errors.push(`read-only agent '${spec.id}' requests ${action} tools (${toolList})`);
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
if (spec.capabilityClass === "artifact-write") {
|
|
1151
|
+
for (const tool of spec.tools) {
|
|
1152
|
+
const action = classify({ tool }).actionClass;
|
|
1153
|
+
if (action === "execute" || action === "dispatch" || action === "system_modify" || action === "git_destructive") {
|
|
1154
|
+
errors.push(`artifact-write agent '${spec.id}' requests ${action} tool '${tool}'`);
|
|
1155
|
+
}
|
|
1156
|
+
if (action === "write" && tool !== ToolNames.Artifact) {
|
|
1157
|
+
errors.push(`artifact-write agent '${spec.id}' can only write terminal artifacts; got '${tool}'`);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
if (spec.capabilityClass === "verification") {
|
|
1162
|
+
for (const tool of spec.tools) {
|
|
1163
|
+
const action = classify({ tool }).actionClass;
|
|
1164
|
+
if (action === "write" || action === "dispatch" || action === "system_modify" || action === "git_destructive") {
|
|
1165
|
+
errors.push(`verification agent '${spec.id}' must not request ${action} tool '${tool}'`);
|
|
1166
|
+
}
|
|
1167
|
+
if (tool === ToolNames.Bash)
|
|
1168
|
+
errors.push(`verification agent '${spec.id}' must use typed validation tools, not bash`);
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
if (spec.capabilityClass !== "orchestration" && spec.tools.includes(ToolNames.Dispatch)) {
|
|
1172
|
+
errors.push(`agent '${spec.id}' exposes dispatch without orchestration capability`);
|
|
1173
|
+
}
|
|
1174
|
+
if (spec.tools.includes(ToolNames.AskUser)) {
|
|
1175
|
+
errors.push(`agent '${spec.id}' exposes ask_user, which is only available to the orchestrator`);
|
|
1176
|
+
}
|
|
1177
|
+
if (spec.skills.length > 0 && !spec.tools.includes(ToolNames.Context)) {
|
|
1178
|
+
errors.push(`agent '${spec.id}' declares skills but does not expose context`);
|
|
1179
|
+
}
|
|
1180
|
+
return errors;
|
|
1181
|
+
}
|
|
1182
|
+
function assertAgentSpecPolicy(spec) {
|
|
1183
|
+
const errors = agentSpecPolicyErrors(spec);
|
|
1184
|
+
if (errors.length > 0) throw new Error(`agent policy violation:
|
|
1185
|
+
${errors.map((error) => `- ${error}`).join("\n")}`);
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
export {
|
|
1189
|
+
canonicalizeExistingPath,
|
|
1190
|
+
VERIFICATION_SCRIPT_FAMILY_HINT,
|
|
1191
|
+
isVerificationScriptName,
|
|
1192
|
+
declaredVerificationScripts,
|
|
1193
|
+
toolMutationPaths,
|
|
1194
|
+
protectedArtifactMutationBlockReason,
|
|
1195
|
+
protectArtifact,
|
|
1196
|
+
detectValidationCommand,
|
|
1197
|
+
extractCommandWriteTargets,
|
|
1198
|
+
inlineShellScript,
|
|
1199
|
+
extractCommandDeleteTargets,
|
|
1200
|
+
tokenizeShellLike,
|
|
1201
|
+
classify,
|
|
1202
|
+
AGENT_AUTOMATION_AUTHORITIES,
|
|
1203
|
+
isAgentCategory,
|
|
1204
|
+
isAgentCapabilityClass,
|
|
1205
|
+
isAgentLatencyClass,
|
|
1206
|
+
isAgentAudience,
|
|
1207
|
+
isAgentProjectContextTier,
|
|
1208
|
+
isAgentProduct,
|
|
1209
|
+
resolveAgentToolCompatibility,
|
|
1210
|
+
normalizeAgentSpec,
|
|
1211
|
+
agentSpecFingerprint,
|
|
1212
|
+
isUserVisibleAgent,
|
|
1213
|
+
isShadowAgent,
|
|
1214
|
+
assertAgentSpecPolicy
|
|
1215
|
+
};
|
|
1216
|
+
//# sourceMappingURL=chunk-WD6AJM35.js.map
|