@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.
Files changed (226) hide show
  1. package/CHANGELOG.md +407 -0
  2. package/CODE_OF_CONDUCT.md +21 -0
  3. package/CONTRIBUTING.md +224 -0
  4. package/LICENSE +202 -0
  5. package/NOTICE +9 -0
  6. package/README.md +798 -0
  7. package/SECURITY.md +72 -0
  8. package/assets/clio-coder-logo-128.webp +0 -0
  9. package/damage-control-rules.yaml +419 -0
  10. package/dist/acp-UMLFVA3F.js +92 -0
  11. package/dist/agents-Q4MYPMUW.js +91 -0
  12. package/dist/auth-O6HYIJ6J.js +521 -0
  13. package/dist/chunk-262G75JS.js +35 -0
  14. package/dist/chunk-26BZQOAD.js +1281 -0
  15. package/dist/chunk-2J63S4SF.js +508 -0
  16. package/dist/chunk-3DANZDGR.js +717 -0
  17. package/dist/chunk-4UQA7NCT.js +29 -0
  18. package/dist/chunk-527KG6XR.js +497 -0
  19. package/dist/chunk-5LDRNKX2.js +1063 -0
  20. package/dist/chunk-5N2FG33Q.js +25 -0
  21. package/dist/chunk-67MTHP2E.js +135 -0
  22. package/dist/chunk-6CWDTGUC.js +20 -0
  23. package/dist/chunk-7BHLZB3A.js +2115 -0
  24. package/dist/chunk-7RBKDI66.js +348 -0
  25. package/dist/chunk-AMFR5YA3.js +541 -0
  26. package/dist/chunk-BBUH4VAA.js +1224 -0
  27. package/dist/chunk-BYEU76JP.js +899 -0
  28. package/dist/chunk-CLJ5HLUD.js +458 -0
  29. package/dist/chunk-D5YD55AR.js +116 -0
  30. package/dist/chunk-DXQNI4PC.js +61 -0
  31. package/dist/chunk-E3NYWENM.js +1004 -0
  32. package/dist/chunk-GNGDQYDU.js +34688 -0
  33. package/dist/chunk-GOTUR54M.js +9 -0
  34. package/dist/chunk-HBU5MTAM.js +41 -0
  35. package/dist/chunk-HMYNFFY4.js +28 -0
  36. package/dist/chunk-JPOWPFCU.js +1010 -0
  37. package/dist/chunk-JWHCJDCI.js +1215 -0
  38. package/dist/chunk-KBR4MZZR.js +41 -0
  39. package/dist/chunk-KKKPTZLM.js +93 -0
  40. package/dist/chunk-ME6DNWIU.js +66 -0
  41. package/dist/chunk-NI4DEJMC.js +88 -0
  42. package/dist/chunk-O4EJEDHO.js +659 -0
  43. package/dist/chunk-PIDUD6M2.js +31 -0
  44. package/dist/chunk-PS4PFJQP.js +29459 -0
  45. package/dist/chunk-QV47YRF4.js +48 -0
  46. package/dist/chunk-RQDWMVRB.js +279 -0
  47. package/dist/chunk-TFSSEXL6.js +136 -0
  48. package/dist/chunk-TKHQ4DGZ.js +8290 -0
  49. package/dist/chunk-TPOCL34A.js +2876 -0
  50. package/dist/chunk-UGYAX5YI.js +565 -0
  51. package/dist/chunk-UHTSULZS.js +461 -0
  52. package/dist/chunk-UU3R62TT.js +128 -0
  53. package/dist/chunk-UWIJNAOB.js +3906 -0
  54. package/dist/chunk-VOO7NYPP.js +914 -0
  55. package/dist/chunk-VPAWTYLY.js +117 -0
  56. package/dist/chunk-WD6AJM35.js +1216 -0
  57. package/dist/chunk-X3BR7HWV.js +115 -0
  58. package/dist/chunk-X3NE4WVW.js +120 -0
  59. package/dist/chunk-XNISANGE.js +1395 -0
  60. package/dist/chunk-XV4ZJ6ZM.js +3177 -0
  61. package/dist/cli/index.js +236 -0
  62. package/dist/clio-KIQ5SNDS.js +53 -0
  63. package/dist/components-JVHMUBEB.js +653 -0
  64. package/dist/config-ZFCDBMDC.js +372 -0
  65. package/dist/configure-G4E3A2PG.js +27 -0
  66. package/dist/context-CDXTP2MP.js +293 -0
  67. package/dist/context-E3KIFVXI.js +185 -0
  68. package/dist/context-clear-3F4PLXOS.js +102 -0
  69. package/dist/context-index-Q7YSYTR3.js +106 -0
  70. package/dist/docs-YIETIWZI.js +280 -0
  71. package/dist/doctor-M5HJJZOL.js +61 -0
  72. package/dist/domains/agents/builtins/architect.md +33 -0
  73. package/dist/domains/agents/builtins/coder.md +31 -0
  74. package/dist/domains/agents/builtins/context-bootstrap.md +38 -0
  75. package/dist/domains/agents/builtins/debugger.md +30 -0
  76. package/dist/domains/agents/builtins/documenter.md +31 -0
  77. package/dist/domains/agents/builtins/git-master.md +30 -0
  78. package/dist/domains/agents/builtins/provenance.md +30 -0
  79. package/dist/domains/agents/builtins/researcher.md +71 -0
  80. package/dist/domains/agents/builtins/scout.md +42 -0
  81. package/dist/domains/agents/builtins/tester.md +31 -0
  82. package/dist/domains/agents/builtins/verifier.md +30 -0
  83. package/dist/domains/agents/builtins/wiki-writer.md +41 -0
  84. package/dist/eval-B3KZZESM.js +2674 -0
  85. package/dist/evidence-V67CHM35.js +233 -0
  86. package/dist/evolve-YDZSUQYA.js +518 -0
  87. package/dist/extensions-SRG7XCAH.js +207 -0
  88. package/dist/fleet-CA2CRTVG.js +760 -0
  89. package/dist/fleet-preflight-CLIAX7YR.js +21 -0
  90. package/dist/init-2OZDJE2D.js +227 -0
  91. package/dist/memory-3PIQQAKX.js +207 -0
  92. package/dist/models-DY35XI7Y.js +237 -0
  93. package/dist/paths-5OMXW7Z4.js +57 -0
  94. package/dist/preload-KZVHET2B.js +11 -0
  95. package/dist/reset-PIFYNOS3.js +216 -0
  96. package/dist/run-3VSPP24F.js +735 -0
  97. package/dist/share-D36RQCXM.js +241 -0
  98. package/dist/skills-F2MRLELY.js +445 -0
  99. package/dist/skills-eval-E2ZTW4PL.js +932 -0
  100. package/dist/targets-DZMEZAH4.js +977 -0
  101. package/dist/trace-7NYCUI2J.js +250 -0
  102. package/dist/uninstall-AD3JWHBB.js +322 -0
  103. package/dist/upgrade-WYYBKGDY.js +301 -0
  104. package/dist/usage-ULIDAGFF.js +755 -0
  105. package/dist/version-ROZ6CZKH.js +16 -0
  106. package/dist/wiki-generate-PKFIX6OB.js +377 -0
  107. package/dist/worker/entry.js +1739 -0
  108. package/docs/README.md +93 -0
  109. package/docs/acp.md +120 -0
  110. package/docs/alcf-provider.md +72 -0
  111. package/docs/architecture.md +172 -0
  112. package/docs/artifact-versions.md +54 -0
  113. package/docs/built-in-agents.md +265 -0
  114. package/docs/capacity-and-scheduling.md +97 -0
  115. package/docs/commands-and-modes.md +554 -0
  116. package/docs/config-knobs-audit.md +115 -0
  117. package/docs/configuration-and-targets.md +812 -0
  118. package/docs/context-engine.md +236 -0
  119. package/docs/dispatch-architecture-rationale.md +126 -0
  120. package/docs/documentation-coverage.md +46 -0
  121. package/docs/documentation-guide.md +166 -0
  122. package/docs/environment-variables.md +105 -0
  123. package/docs/eval-runner.md +205 -0
  124. package/docs/evals-internal.md +298 -0
  125. package/docs/evidence-and-memory.md +243 -0
  126. package/docs/evolution.md +143 -0
  127. package/docs/exit-codes-and-output.md +74 -0
  128. package/docs/extensions-and-sharing.md +306 -0
  129. package/docs/fleet-demo-runbook.md +179 -0
  130. package/docs/fleet-dispatch.md +591 -0
  131. package/docs/glossary.md +75 -0
  132. package/docs/html/agents_blueprint.html +936 -0
  133. package/docs/html/alcf_blueprint.html +324 -0
  134. package/docs/html/architecture_blueprint.html +850 -0
  135. package/docs/html/commands_blueprint.html +794 -0
  136. package/docs/html/config_knobs_audit_blueprint.html +178 -0
  137. package/docs/html/configuration_blueprint.html +1080 -0
  138. package/docs/html/context_blueprint.html +603 -0
  139. package/docs/html/documentation_blueprint.html +832 -0
  140. package/docs/html/environment_blueprint.html +404 -0
  141. package/docs/html/eval_blueprint.html +743 -0
  142. package/docs/html/evals_internal_blueprint.html +190 -0
  143. package/docs/html/evolution_blueprint.html +674 -0
  144. package/docs/html/extensions_blueprint.html +2065 -0
  145. package/docs/html/fleet_dispatch_blueprint.html +286 -0
  146. package/docs/html/index.html +919 -0
  147. package/docs/html/lifecycle_blueprint.html +723 -0
  148. package/docs/html/memory_blueprint.html +699 -0
  149. package/docs/html/middleware_blueprint.html +664 -0
  150. package/docs/html/models_blueprint.html +2366 -0
  151. package/docs/html/observability_blueprint.html +683 -0
  152. package/docs/html/provider_adapter_blueprint.html +245 -0
  153. package/docs/html/safety_blueprint.html +1386 -0
  154. package/docs/html/shared.css +571 -0
  155. package/docs/html/shared.js +143 -0
  156. package/docs/html/skills_blueprint.html +671 -0
  157. package/docs/html/soak_blueprint.html +182 -0
  158. package/docs/html/tool_usage_blueprint.html +350 -0
  159. package/docs/html/tools_blueprint.html +2249 -0
  160. package/docs/html/trace_blueprint.html +235 -0
  161. package/docs/html/tui_design_blueprint.html +314 -0
  162. package/docs/html/validation_blueprint.html +961 -0
  163. package/docs/html/worker_dispatch_blueprint.html +231 -0
  164. package/docs/installation-and-lifecycle.md +308 -0
  165. package/docs/middleware-and-components.md +148 -0
  166. package/docs/model-catalog.md +189 -0
  167. package/docs/observability.md +233 -0
  168. package/docs/proactive-memory.md +452 -0
  169. package/docs/prompt-envelope-and-tools.md +142 -0
  170. package/docs/provider-adapter-cookbook.md +148 -0
  171. package/docs/release-cut-checklist.md +138 -0
  172. package/docs/safety-model.md +357 -0
  173. package/docs/scientific-validation.md +105 -0
  174. package/docs/session-lifecycle.md +156 -0
  175. package/docs/skills-marketplace.md +46 -0
  176. package/docs/tool-usage.md +527 -0
  177. package/docs/trace-store.md +132 -0
  178. package/docs/troubleshooting.md +33 -0
  179. package/docs/tui-design.md +239 -0
  180. package/docs/worker-dispatch-mechanics.md +242 -0
  181. package/package.json +132 -0
  182. package/skills/README.md +408 -0
  183. package/skills/git/commit-crafting/SKILL.md +79 -0
  184. package/skills/git/commit-crafting/evals.md +92 -0
  185. package/skills/git/create-pr/SKILL.md +116 -0
  186. package/skills/git/create-pr/evals.md +114 -0
  187. package/skills/git/investigate-issue/SKILL.md +139 -0
  188. package/skills/git/investigate-issue/evals.md +94 -0
  189. package/skills/git/resolve-merge-conflicts/SKILL.md +96 -0
  190. package/skills/git/resolve-merge-conflicts/evals.md +58 -0
  191. package/skills/git/review-changes/SKILL.md +103 -0
  192. package/skills/git/review-changes/evals.md +85 -0
  193. package/skills/git/worktree-create/SKILL.md +92 -0
  194. package/skills/git/worktree-create/evals.md +97 -0
  195. package/skills/git/worktree-create/references/worktree-setup.md +66 -0
  196. package/skills/git/worktree-merge/SKILL.md +95 -0
  197. package/skills/git/worktree-merge/evals.md +114 -0
  198. package/skills/skill-marketplace.json +261 -0
  199. package/skills/workflow/cut-it/SKILL.md +86 -0
  200. package/skills/workflow/cut-it/evals.md +42 -0
  201. package/src/domains/agents/builtins/architect.md +33 -0
  202. package/src/domains/agents/builtins/coder.md +31 -0
  203. package/src/domains/agents/builtins/context-bootstrap.md +38 -0
  204. package/src/domains/agents/builtins/debugger.md +30 -0
  205. package/src/domains/agents/builtins/documenter.md +31 -0
  206. package/src/domains/agents/builtins/git-master.md +30 -0
  207. package/src/domains/agents/builtins/provenance.md +30 -0
  208. package/src/domains/agents/builtins/researcher.md +71 -0
  209. package/src/domains/agents/builtins/scout.md +42 -0
  210. package/src/domains/agents/builtins/tester.md +31 -0
  211. package/src/domains/agents/builtins/verifier.md +30 -0
  212. package/src/domains/agents/builtins/wiki-writer.md +41 -0
  213. package/src/domains/agents/fleets/build-review.md +34 -0
  214. package/src/domains/agents/fleets/build-test.md +35 -0
  215. package/src/domains/agents/fleets/sdlc.md +86 -0
  216. package/src/domains/prompts/fragments/identity/clio-worker.md +11 -0
  217. package/src/domains/prompts/fragments/identity/clio.md +26 -0
  218. package/src/domains/prompts/fragments/operating/contract.md +64 -0
  219. package/src/domains/prompts/fragments/safety/auto-edit.md +14 -0
  220. package/src/domains/prompts/fragments/safety/full-auto.md +14 -0
  221. package/src/domains/prompts/fragments/safety/read-only.md +13 -0
  222. package/src/domains/prompts/fragments/safety/suggest.md +13 -0
  223. package/src/domains/prompts/fragments/wiki/page.md +75 -0
  224. package/src/domains/prompts/fragments/wiki/plan.md +48 -0
  225. package/src/domains/providers/models/cloud-models/alcf.yaml +40 -0
  226. package/src/domains/providers/models/local-models/clio-local-coding-targets.yaml +993 -0
@@ -0,0 +1,3177 @@
1
+ import {
2
+ AGENT_AUTOMATION_AUTHORITIES,
3
+ canonicalizeExistingPath,
4
+ classify,
5
+ detectValidationCommand,
6
+ extractCommandDeleteTargets,
7
+ extractCommandWriteTargets,
8
+ inlineShellScript,
9
+ isVerificationScriptName,
10
+ tokenizeShellLike,
11
+ toolMutationPaths
12
+ } from "./chunk-WD6AJM35.js";
13
+ import {
14
+ ToolNames
15
+ } from "./chunk-QV47YRF4.js";
16
+ import {
17
+ clioConfigDir
18
+ } from "./chunk-X3NE4WVW.js";
19
+ import {
20
+ resolvePackageRoot
21
+ } from "./chunk-DXQNI4PC.js";
22
+
23
+ // src/domains/agents/result-contract.ts
24
+ import { createHash } from "node:crypto";
25
+ import path from "node:path";
26
+ function canonical(value) {
27
+ if (value === null) return "null";
28
+ if (typeof value === "string" || typeof value === "boolean") return JSON.stringify(value);
29
+ if (typeof value === "number") {
30
+ if (!Number.isFinite(value)) throw new Error("result contract cannot digest non-finite numbers");
31
+ return JSON.stringify(value);
32
+ }
33
+ if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
34
+ if (typeof value === "object") {
35
+ const record = value;
36
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${canonical(record[key])}`).join(",")}}`;
37
+ }
38
+ throw new Error(`result contract cannot digest ${typeof value}`);
39
+ }
40
+ function digest(value) {
41
+ return createHash("sha256").update(canonical(value), "utf8").digest("hex");
42
+ }
43
+ function resultContractDigest(contract) {
44
+ return digest(contract);
45
+ }
46
+ function resultContractSourceId(contract) {
47
+ return `agent-result-contract:${contract.kind}:${resultContractDigest(contract)}`;
48
+ }
49
+ function failure(contract, quality, reason) {
50
+ return {
51
+ conformance: "fail",
52
+ quality,
53
+ sourceId: resultContractSourceId(contract),
54
+ validatorDigest: digest({ contract, reason }),
55
+ reason
56
+ };
57
+ }
58
+ function success(contract, quality, value, extra = {}) {
59
+ return {
60
+ conformance: "pass",
61
+ quality,
62
+ sourceId: resultContractSourceId(contract),
63
+ validatorDigest: digest({ contract, value }),
64
+ ...extra
65
+ };
66
+ }
67
+ function parseJson(output) {
68
+ if (output === null || output.trim().length === 0) return { ok: false, reason: "missing final result" };
69
+ try {
70
+ const value = JSON.parse(output);
71
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
72
+ return { ok: false, reason: "result must be a JSON object" };
73
+ }
74
+ return { ok: true, value };
75
+ } catch {
76
+ return { ok: false, reason: "result must be valid JSON" };
77
+ }
78
+ }
79
+ function hasOnlyKeys(value, keys) {
80
+ const allowed = new Set(keys);
81
+ return Object.keys(value).every((key) => allowed.has(key));
82
+ }
83
+ function string(value) {
84
+ return typeof value === "string" && value.trim().length > 0;
85
+ }
86
+ function parseFindings(value) {
87
+ if (!Array.isArray(value)) return null;
88
+ const findings = [];
89
+ for (const entry of value) {
90
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) return null;
91
+ const finding = entry;
92
+ const line = finding.line;
93
+ if (!hasOnlyKeys(finding, ["claim", "path", "line"]) || !string(finding.claim) || !string(finding.path) || typeof line !== "number" || !Number.isSafeInteger(line) || line < 1) {
94
+ return null;
95
+ }
96
+ findings.push({ claim: finding.claim, path: finding.path, line });
97
+ }
98
+ return findings;
99
+ }
100
+ var RESULT_CONTRACT_KINDS = [
101
+ "architect-plan",
102
+ "scout-report",
103
+ "verifier-report",
104
+ "debugger-report",
105
+ "research-report",
106
+ "mutation-report",
107
+ "provenance-report",
108
+ "external-delegation"
109
+ ];
110
+ function parseSubtasks(value) {
111
+ if (!Array.isArray(value) || value.length > 4) return null;
112
+ const subtasks = [];
113
+ const ids = /* @__PURE__ */ new Set();
114
+ for (const entry of value) {
115
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) return null;
116
+ const subtask = entry;
117
+ if (!hasOnlyKeys(subtask, ["id", "task", "dependencies", "expectedResultContract", "requestedAuthority"]) || !string(subtask.id) || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u.test(subtask.id) || ids.has(subtask.id) || !string(subtask.task) || Buffer.byteLength(subtask.task, "utf8") > 4096 || !Array.isArray(subtask.dependencies) || subtask.dependencies.length > 4 || subtask.dependencies.some((dependency) => !string(dependency)) || new Set(subtask.dependencies).size !== subtask.dependencies.length || !RESULT_CONTRACT_KINDS.includes(subtask.expectedResultContract) || !AGENT_AUTOMATION_AUTHORITIES.includes(subtask.requestedAuthority)) {
118
+ return null;
119
+ }
120
+ ids.add(subtask.id);
121
+ subtasks.push({
122
+ id: subtask.id,
123
+ task: subtask.task,
124
+ dependencies: [...subtask.dependencies],
125
+ expectedResultContract: subtask.expectedResultContract,
126
+ requestedAuthority: subtask.requestedAuthority
127
+ });
128
+ }
129
+ for (const subtask of subtasks) {
130
+ if (subtask.dependencies.some((dependency) => dependency === subtask.id || !ids.has(dependency))) return null;
131
+ }
132
+ const remaining = new Set(ids);
133
+ const completed = /* @__PURE__ */ new Set();
134
+ while (remaining.size > 0) {
135
+ const ready = subtasks.filter(
136
+ (subtask) => remaining.has(subtask.id) && subtask.dependencies.every((dependency) => completed.has(dependency))
137
+ );
138
+ if (ready.length === 0) return null;
139
+ for (const subtask of ready) {
140
+ remaining.delete(subtask.id);
141
+ completed.add(subtask.id);
142
+ }
143
+ }
144
+ return subtasks;
145
+ }
146
+ function isContextHandbookResult(value) {
147
+ if (!hasOnlyKeys(value, ["projectName", "identity", "conventions", "invariants", "sections"])) return false;
148
+ if (!string(value.projectName) || !string(value.identity)) return false;
149
+ if (!Array.isArray(value.conventions) || value.conventions.some((entry) => !string(entry))) return false;
150
+ if (!Array.isArray(value.invariants) || value.invariants.some((entry) => !string(entry))) return false;
151
+ if (!Array.isArray(value.sections)) return false;
152
+ return value.sections.every((entry) => {
153
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) return false;
154
+ const section = entry;
155
+ return hasOnlyKeys(section, ["title", "body"]) && string(section.title) && string(section.body);
156
+ });
157
+ }
158
+ function validateContextHandbook(contract, output) {
159
+ const parsed = parseJson(output);
160
+ if (!parsed.ok) return failure(contract, "fail", parsed.reason);
161
+ if (!isContextHandbookResult(parsed.value)) {
162
+ return failure(
163
+ contract,
164
+ "fail",
165
+ "context handbook result must carry projectName, identity, conventions[], invariants[], and sections[{title, body}] and nothing else"
166
+ );
167
+ }
168
+ return success(contract, "unmeasured", parsed.value);
169
+ }
170
+ function salvageScoutClaims(value) {
171
+ const grounded = [];
172
+ const ungrounded = [];
173
+ if (!Array.isArray(value)) return { grounded, ungrounded };
174
+ for (const entry of value) {
175
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) continue;
176
+ const finding = entry;
177
+ if (!string(finding.claim)) continue;
178
+ const line = finding.line;
179
+ if (string(finding.path) && typeof line === "number" && Number.isSafeInteger(line) && line >= 1) {
180
+ grounded.push({ claim: finding.claim, path: finding.path, line });
181
+ continue;
182
+ }
183
+ ungrounded.push(finding.claim);
184
+ }
185
+ return { grounded, ungrounded };
186
+ }
187
+ function degradedScout(contract, value, reason) {
188
+ const salvaged = salvageScoutClaims(value.findings);
189
+ if (salvaged.grounded.length === 0 && salvaged.ungrounded.length === 0) return null;
190
+ const degradedReason = `${reason} (accepted as a degraded reconnaissance result; findings are leads, not validation evidence)`;
191
+ const scout = {
192
+ findings: salvaged.grounded,
193
+ citations: salvaged.grounded.map(({ path: citedPath, line }) => ({ path: citedPath, line })),
194
+ needsSplit: false,
195
+ proposedSubtasks: [],
196
+ ungroundedClaims: salvaged.ungrounded,
197
+ degradedReason
198
+ };
199
+ return success(contract, "unmeasured", scout, { scout, reason: degradedReason });
200
+ }
201
+ function validateScout(contract, output) {
202
+ const parsed = parseJson(output);
203
+ if (!parsed.ok) return failure(contract, "fail", parsed.reason);
204
+ const value = parsed.value;
205
+ const degrade = (reason) => degradedScout(contract, value, reason) ?? failure(contract, "fail", reason);
206
+ if (!hasOnlyKeys(value, ["findings", "needsSplit", "proposedSubtasks"])) {
207
+ return degrade("Scout result has unknown fields");
208
+ }
209
+ const findings = parseFindings(value.findings);
210
+ const proposedSubtasks = parseSubtasks(value.proposedSubtasks);
211
+ if (findings === null || typeof value.needsSplit !== "boolean" || proposedSubtasks === null) {
212
+ return degrade(
213
+ "Scout result must carry findings as {claim, path, line} objects, needsSplit, and 0..4 typed proposed subtasks"
214
+ );
215
+ }
216
+ if (value.needsSplit !== proposedSubtasks.length > 0) {
217
+ return degrade("Scout needsSplit must agree with proposedSubtasks");
218
+ }
219
+ if (findings.length === 0 && !value.needsSplit) {
220
+ return degrade("Scout result must carry at least one cited finding, or set needsSplit");
221
+ }
222
+ if (value.needsSplit && findings.length > 0) {
223
+ return degrade("Scout split recommendations carry typed subtasks instead of findings");
224
+ }
225
+ const scout = {
226
+ findings,
227
+ citations: findings.map(({ path: citedPath, line }) => ({ path: citedPath, line })),
228
+ needsSplit: value.needsSplit,
229
+ proposedSubtasks
230
+ };
231
+ return success(contract, "pass", scout, { scout });
232
+ }
233
+ function parseChecks(value) {
234
+ if (!Array.isArray(value) || value.length === 0) return null;
235
+ const checks = [];
236
+ for (const entry of value) {
237
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) return null;
238
+ const check = entry;
239
+ if (!hasOnlyKeys(check, ["name", "passed", "evidence"]) || !string(check.name) || typeof check.passed !== "boolean" || !string(check.evidence)) {
240
+ return null;
241
+ }
242
+ checks.push({ name: check.name, passed: check.passed, evidence: check.evidence });
243
+ }
244
+ return checks;
245
+ }
246
+ function validateScoutCitation(input, citation) {
247
+ const cwd = path.resolve(input.cwd);
248
+ const citedPath = path.resolve(cwd, citation.path);
249
+ const relative = path.relative(cwd, citedPath);
250
+ if (relative === "" || relative.startsWith(`..${path.sep}`) || relative === ".." || path.isAbsolute(relative)) {
251
+ return { ok: false, reason: `Scout citation path escapes the workspace: ${citation.path}` };
252
+ }
253
+ const content = input.filesystem.readFile(citedPath);
254
+ if (content === null) return { ok: false, reason: `Scout citation cannot be read: ${citation.path}` };
255
+ const lineCount = content.split(/\r\n|\r|\n/u).length;
256
+ if (citation.line > lineCount) {
257
+ return { ok: false, reason: `Scout citation is past end of file: ${citation.path}:${citation.line}` };
258
+ }
259
+ if (input.observedReadRanges !== void 0) {
260
+ const spans = input.observedReadRanges.get(citedPath) ?? [];
261
+ if (!spans.some(([start, end]) => citation.line >= start && citation.line <= end)) {
262
+ const observed = spans.length === 0 ? "this run never read that file" : `this run read only ${spans.map(([start, end]) => `${start}-${end}`).join(", ")}`;
263
+ return {
264
+ ok: false,
265
+ reason: `Scout citation is not grounded in a live read: ${citation.path}:${citation.line} (${observed})`
266
+ };
267
+ }
268
+ }
269
+ return {
270
+ ok: true,
271
+ evidence: { ...citation, contentDigest: createHash("sha256").update(content, "utf8").digest("hex") }
272
+ };
273
+ }
274
+ function validateScoutCitations(input, citations) {
275
+ const evidence = [];
276
+ for (const citation of citations) {
277
+ const checked = validateScoutCitation(input, citation);
278
+ if (!checked.ok) return checked;
279
+ evidence.push(checked.evidence);
280
+ }
281
+ return { ok: true, evidence };
282
+ }
283
+ function groundDegradedScout(contract, input, scout, degradedReason) {
284
+ const grounded = [];
285
+ const evidence = [];
286
+ const demoted = [];
287
+ for (const finding of scout.findings) {
288
+ const checked = validateScoutCitation(input, { path: finding.path, line: finding.line });
289
+ if (checked.ok) {
290
+ grounded.push(finding);
291
+ evidence.push(checked.evidence);
292
+ continue;
293
+ }
294
+ demoted.push(finding.claim);
295
+ }
296
+ const degraded = {
297
+ findings: grounded,
298
+ citations: grounded.map(({ path: citedPath, line }) => ({ path: citedPath, line })),
299
+ needsSplit: false,
300
+ proposedSubtasks: [],
301
+ ungroundedClaims: [...scout.ungroundedClaims ?? [], ...demoted],
302
+ degradedReason
303
+ };
304
+ return success(contract, "unmeasured", { scout: degraded, evidence }, { scout: degraded, reason: degradedReason });
305
+ }
306
+ function validateVerifier(contract, output) {
307
+ const parsed = parseJson(output);
308
+ if (!parsed.ok) return failure(contract, "fail", parsed.reason);
309
+ const value = parsed.value;
310
+ if (!hasOnlyKeys(value, ["verdict", "checks"]) || value.verdict !== "pass" && value.verdict !== "fail") {
311
+ return failure(contract, "fail", "Verifier result must carry pass or fail verdict and checks");
312
+ }
313
+ const checks = parseChecks(value.checks);
314
+ if (checks === null || value.verdict === "pass" !== checks.every((check) => check.passed)) {
315
+ return failure(contract, "fail", "Verifier verdict must agree with every typed check");
316
+ }
317
+ const verifier = { verdict: value.verdict, checks };
318
+ return success(contract, value.verdict, verifier, { verifier });
319
+ }
320
+ function parseVerifierResult(output) {
321
+ const validation = validateVerifier({ kind: "verifier-report" }, output);
322
+ return validation.verifier ?? null;
323
+ }
324
+ function validateCodeReport(contract, output) {
325
+ const parsed = parseJson(output);
326
+ if (!parsed.ok) return failure(contract, "unmeasured", parsed.reason);
327
+ const value = parsed.value;
328
+ if (!hasOnlyKeys(value, ["passed", "exitCode", "checks", "artifactPaths", "outputExcerpt"]) || typeof value.passed !== "boolean" || typeof value.exitCode !== "number" || !Number.isSafeInteger(value.exitCode) || typeof value.outputExcerpt !== "string" || !Array.isArray(value.artifactPaths) || value.artifactPaths.some((entry) => !string(entry))) {
329
+ return failure(
330
+ contract,
331
+ "unmeasured",
332
+ "Code result must carry passed, integer exitCode, checks, artifactPaths, and outputExcerpt"
333
+ );
334
+ }
335
+ const checks = parseChecks(value.checks);
336
+ if (checks === null) return failure(contract, "unmeasured", "Code result must carry typed checks");
337
+ if (value.passed !== (value.exitCode === 0) || value.passed !== checks.every((check) => check.passed)) {
338
+ return failure(contract, "unmeasured", "Code result passed must agree with its exit code and every check");
339
+ }
340
+ return success(contract, "unmeasured", value);
341
+ }
342
+ var RESULT_COMMIT_MESSAGE_MAX_BYTES = 1e3;
343
+ var AUTHORSHIP_FIELDS = ["commitMessage", "summary"];
344
+ function hasControlCharacters(value) {
345
+ for (const character of value) {
346
+ const code = character.codePointAt(0) ?? 0;
347
+ if (code === 127) return true;
348
+ if (code < 32 && character !== "\n" && character !== "\r" && character !== " ") return true;
349
+ }
350
+ return false;
351
+ }
352
+ function normalizeAuthored(value) {
353
+ return value.replace(/\r\n/gu, "\n").replace(/\r/gu, "\n").trim();
354
+ }
355
+ function validateAuthorship(value) {
356
+ for (const field of AUTHORSHIP_FIELDS) {
357
+ const raw = value[field];
358
+ if (raw === void 0 || raw === null) continue;
359
+ if (typeof raw !== "string") return `${field} must be a string when present`;
360
+ if (raw.trim().length === 0) continue;
361
+ if (Buffer.byteLength(raw, "utf8") > RESULT_COMMIT_MESSAGE_MAX_BYTES) {
362
+ return `${field} must be at most ${RESULT_COMMIT_MESSAGE_MAX_BYTES} bytes`;
363
+ }
364
+ if (hasControlCharacters(raw)) return `${field} must not contain control characters`;
365
+ }
366
+ return null;
367
+ }
368
+ function resultContractAuthorship(contract, output) {
369
+ const empty = { commitMessage: null, summary: null };
370
+ if (contract.kind !== "mutation-report" && contract.kind !== "architect-plan") return empty;
371
+ const parsed = parseJson(output);
372
+ if (!parsed.ok || validateAuthorship(parsed.value) !== null) return empty;
373
+ const read = (field) => {
374
+ const raw = parsed.value[field];
375
+ if (typeof raw !== "string") return null;
376
+ const normalized = normalizeAuthored(raw);
377
+ return normalized.length === 0 ? null : normalized;
378
+ };
379
+ return { commitMessage: read("commitMessage"), summary: read("summary") };
380
+ }
381
+ function validateDebugger(contract, output) {
382
+ const parsed = parseJson(output);
383
+ if (!parsed.ok) return failure(contract, "unmeasured", parsed.reason);
384
+ const value = parsed.value;
385
+ if (!hasOnlyKeys(value, ["diagnosis", "reproduction", "evidence"]) || !string(value.diagnosis) || value.reproduction !== "reproduced" && value.reproduction !== "not-reproduced" && value.reproduction !== "unknown" || !Array.isArray(value.evidence) || value.evidence.some((entry) => !string(entry))) {
386
+ return failure(
387
+ contract,
388
+ "unmeasured",
389
+ "Debugger result must carry diagnosis, reproduction status, and evidence without a verdict"
390
+ );
391
+ }
392
+ return success(contract, "unmeasured", value);
393
+ }
394
+ function validateResearch(contract, output, networkAllowed) {
395
+ const parsed = parseJson(output);
396
+ if (!parsed.ok) return failure(contract, "unmeasured", parsed.reason);
397
+ const value = parsed.value;
398
+ if (!hasOnlyKeys(value, ["source", "findings"]) || value.source !== "local" && value.source !== "external" || !Array.isArray(value.findings)) {
399
+ return failure(
400
+ contract,
401
+ "unmeasured",
402
+ "Research result must distinguish local or external sources and carry findings"
403
+ );
404
+ }
405
+ if (value.source === "external" && !networkAllowed) {
406
+ return failure(contract, "unmeasured", "external research requires an allowed network posture");
407
+ }
408
+ for (const finding of value.findings) {
409
+ if (finding === null || typeof finding !== "object" || Array.isArray(finding)) {
410
+ return failure(contract, "unmeasured", "Research findings must be objects");
411
+ }
412
+ const record = finding;
413
+ if (!hasOnlyKeys(record, ["claim", "evidence"]) || !string(record.claim) || !string(record.evidence)) {
414
+ return failure(contract, "unmeasured", "Research findings must carry claim and evidence");
415
+ }
416
+ }
417
+ return success(contract, "unmeasured", value);
418
+ }
419
+ function describeWriteSet(effects, cwd) {
420
+ if (effects.mutatedPaths.size === 0) return "this run wrote nothing";
421
+ const written = [...effects.mutatedPaths].map((target) => path.relative(cwd, target) || target).sort().slice(0, MUTATION_WRITE_SET_REASON_LIMIT);
422
+ const more = effects.mutatedPaths.size - written.length;
423
+ return `this run wrote ${written.join(", ")}${more > 0 ? ` (+${more} more)` : ""}`;
424
+ }
425
+ var MUTATION_WRITE_SET_REASON_LIMIT = 8;
426
+ function pathIsPresent(filesystem, target) {
427
+ if (filesystem.pathExists === void 0) return true;
428
+ return filesystem.pathExists(target);
429
+ }
430
+ function validateMutation(contract, input) {
431
+ const parsed = parseJson(input.output);
432
+ if (!parsed.ok) return failure(contract, "unmeasured", parsed.reason);
433
+ const value = parsed.value;
434
+ if (!hasOnlyKeys(value, ["mutatedPaths", "validations", "commitMessage", "summary"]) || !Array.isArray(value.mutatedPaths) || value.mutatedPaths.some((entry) => !string(entry))) {
435
+ return failure(contract, "unmeasured", "Mutation result must carry mutatedPaths and validations");
436
+ }
437
+ const authorship = validateAuthorship(value);
438
+ if (authorship !== null) return failure(contract, "unmeasured", authorship);
439
+ const validations = parseChecks(value.validations);
440
+ if (validations === null)
441
+ return failure(contract, "unmeasured", "Mutation result must carry typed validation results");
442
+ const reportedFailure = !validations.every((check) => check.passed);
443
+ const effects = input.observedRunEffects;
444
+ if (effects === void 0) return success(contract, reportedFailure ? "fail" : "pass", value);
445
+ const cwd = path.resolve(input.cwd);
446
+ let unverifiedMutation = false;
447
+ for (const reported of value.mutatedPaths) {
448
+ const target = path.resolve(cwd, reported);
449
+ if (effects.mutatedPaths.has(target)) continue;
450
+ if (effects.failedMutationPaths.has(target)) {
451
+ return failure(
452
+ contract,
453
+ "unmeasured",
454
+ `Mutation result names a path whose only write this run attempted was refused: ${reported} (${describeWriteSet(effects, cwd)})`
455
+ );
456
+ }
457
+ if (pathIsPresent(input.filesystem, target)) {
458
+ unverifiedMutation = true;
459
+ continue;
460
+ }
461
+ return failure(
462
+ contract,
463
+ "unmeasured",
464
+ `Mutation result names a path this run never wrote and that does not exist: ${reported} (${describeWriteSet(effects, cwd)})`
465
+ );
466
+ }
467
+ if (reportedFailure) return success(contract, "fail", value);
468
+ const measured = !unverifiedMutation && effects.validationCommands.size > 0;
469
+ return success(contract, measured ? "pass" : "unmeasured", value);
470
+ }
471
+ function validateProvenance(contract, output) {
472
+ const parsed = parseJson(output);
473
+ if (!parsed.ok) return failure(contract, "unmeasured", parsed.reason);
474
+ const value = parsed.value;
475
+ if (!hasOnlyKeys(value, ["confirmedFacts", "missingEvidence", "nextInspections"]) || ![value.confirmedFacts, value.missingEvidence, value.nextInspections].every(
476
+ (entry) => Array.isArray(entry) && entry.every((item) => string(item))
477
+ )) {
478
+ return failure(
479
+ contract,
480
+ "unmeasured",
481
+ "Provenance result must carry confirmedFacts, missingEvidence, and nextInspections"
482
+ );
483
+ }
484
+ return success(contract, "unmeasured", value);
485
+ }
486
+ function validateArchitect(contract, input) {
487
+ const artifactPath = path.resolve(input.cwd, contract.path);
488
+ const content = input.filesystem.readFile(artifactPath);
489
+ if (content === null || content.trim().length === 0) {
490
+ return failure(contract, "unmeasured", `required plan artifact is missing or empty: ${contract.path}`);
491
+ }
492
+ return success(contract, "unmeasured", {
493
+ path: contract.path,
494
+ artifactDigest: createHash("sha256").update(content, "utf8").digest("hex")
495
+ });
496
+ }
497
+ function parseScoutResult(output) {
498
+ const validation = validateScout({ kind: "scout-report" }, output);
499
+ if (validation.conformance !== "pass" || validation.scout === void 0) return null;
500
+ return validation.scout.degradedReason === void 0 ? validation.scout : null;
501
+ }
502
+ function validateResultContract(input) {
503
+ switch (input.contract.kind) {
504
+ case "architect-plan":
505
+ return validateArchitect(input.contract, input);
506
+ case "scout-report": {
507
+ const validation = validateScout(input.contract, input.output);
508
+ if (validation.scout === void 0) return validation;
509
+ const degradedReason = validation.scout.degradedReason;
510
+ if (degradedReason !== void 0) {
511
+ return groundDegradedScout(input.contract, input, validation.scout, degradedReason);
512
+ }
513
+ const citations = validateScoutCitations(input, validation.scout.citations);
514
+ return citations.ok ? success(
515
+ input.contract,
516
+ "pass",
517
+ { scout: validation.scout, evidence: citations.evidence },
518
+ { scout: validation.scout }
519
+ ) : failure(input.contract, "fail", citations.reason);
520
+ }
521
+ case "verifier-report":
522
+ return validateVerifier(input.contract, input.output);
523
+ case "debugger-report":
524
+ return validateDebugger(input.contract, input.output);
525
+ case "research-report":
526
+ return validateResearch(input.contract, input.output, input.networkAllowed);
527
+ case "mutation-report":
528
+ return validateMutation(input.contract, input);
529
+ case "provenance-report":
530
+ return validateProvenance(input.contract, input.output);
531
+ case "external-delegation":
532
+ return success(input.contract, "unmeasured", { external: true });
533
+ case "artifact-report":
534
+ return success(input.contract, "unmeasured", { artifact: true });
535
+ case "context-handbook":
536
+ return validateContextHandbook(input.contract, input.output);
537
+ case "code-report":
538
+ return validateCodeReport(input.contract, input.output);
539
+ }
540
+ }
541
+ function validateRecipeResult(input) {
542
+ if (input.contract === null) return null;
543
+ if (!input.reachedTerminalResult) {
544
+ return {
545
+ applicable: false,
546
+ fact: {
547
+ sourceId: resultContractSourceId(input.contract),
548
+ validatorDigest: digest({ contract: input.contract, reached: false }),
549
+ conformance: "not-reached",
550
+ quality: "unmeasured"
551
+ }
552
+ };
553
+ }
554
+ const validation = validateResultContract({
555
+ contract: input.contract,
556
+ output: input.output,
557
+ cwd: input.cwd,
558
+ networkAllowed: input.networkAllowed,
559
+ filesystem: input.filesystem,
560
+ ...input.observedRunEffects !== void 0 ? { observedRunEffects: input.observedRunEffects } : {}
561
+ });
562
+ return {
563
+ applicable: true,
564
+ validation,
565
+ fact: {
566
+ sourceId: validation.sourceId,
567
+ validatorDigest: validation.validatorDigest,
568
+ conformance: validation.conformance,
569
+ quality: validation.quality
570
+ }
571
+ };
572
+ }
573
+ var RESULT_CONTRACT_REPAIR_LIMIT = 2;
574
+ var RESULT_CONTRACT_ANCHOR_LIMIT = 12;
575
+ function resultContractShape(contract) {
576
+ switch (contract.kind) {
577
+ case "architect-plan":
578
+ return `a plan artifact written to ${contract.path}, then optionally {"commitMessage":"...","summary":"..."} describing it`;
579
+ case "scout-report":
580
+ return '{"findings":[{"claim":"what you observed","path":"src/file.ts","line":1}],"needsSplit":false,"proposedSubtasks":[]} or {"findings":[],"needsSplit":true,"proposedSubtasks":[{"id":"inspect","task":"Inspect the boundary","dependencies":[],"expectedResultContract":"scout-report","requestedAuthority":"read-only"}]} or, if you cannot produce a citation you are sure of, at least {"findings":[{"claim":"what you observed"}]}, which is accepted as an ungrounded lead rather than losing the run';
581
+ case "verifier-report":
582
+ return '{"verdict":"pass","checks":[{"name":"npm run typecheck","passed":true,"evidence":"exit 0"}]}';
583
+ case "debugger-report":
584
+ return '{"diagnosis":"...","reproduction":"reproduced","evidence":["..."]}';
585
+ case "research-report":
586
+ return '{"source":"local","findings":[{"claim":"...","evidence":"..."}]}';
587
+ case "mutation-report":
588
+ return '{"mutatedPaths":["src/file.ts"],"validations":[{"name":"npm test","passed":true,"evidence":"exit 0"}],"commitMessage":"optional: the commit message for this change","summary":"optional: one line"}';
589
+ case "provenance-report":
590
+ return '{"confirmedFacts":["..."],"missingEvidence":["..."],"nextInspections":["..."]}';
591
+ case "external-delegation":
592
+ return "any final text";
593
+ case "artifact-report":
594
+ return "any final text; the artifact you wrote at the location the task named is the result";
595
+ case "context-handbook":
596
+ return '{"projectName":"string","identity":"one sentence","conventions":[],"invariants":[],"sections":[{"title":"Architecture","body":"markdown that cites `real/path.ts` tokens"}]}';
597
+ case "code-report":
598
+ return '{"passed":false,"exitCode":1,"checks":[{"name":"test","passed":false,"evidence":"exit 1"}],"artifactPaths":["/path/command.log"],"outputExcerpt":"..."}';
599
+ }
600
+ }
601
+ function resultContractRepairMessage(input) {
602
+ const last = input.attempt >= RESULT_CONTRACT_REPAIR_LIMIT;
603
+ const lines = [
604
+ last ? "FINAL RESULT REQUIRED IN THIS RESPONSE. Your previous response still did not satisfy this run's result contract." : "Your previous response did not satisfy this run's result contract.",
605
+ `Validator reason: ${input.reason}`,
606
+ "Tool use is over. Answer from the evidence you already gathered.",
607
+ `Emit exactly this shape and nothing else: ${resultContractShape(input.contract)}`,
608
+ "Do not add prose, code fences, or commentary around it. Do not describe work you intend to do next."
609
+ ];
610
+ if (input.anchors.length > 0) {
611
+ lines.push(
612
+ "",
613
+ "Locations you read successfully in this run (use these exact path:line values):",
614
+ ...input.anchors.slice(-RESULT_CONTRACT_ANCHOR_LIMIT).map((anchor) => `- ${anchor}`)
615
+ );
616
+ }
617
+ return lines.join("\n");
618
+ }
619
+ function parseResultContract(value, sourcePath) {
620
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
621
+ throw new Error(`agent recipe: ${sourcePath}: resultContract must be an object`);
622
+ }
623
+ const record = value;
624
+ if (typeof record.kind !== "string") throw new Error(`agent recipe: ${sourcePath}: resultContract.kind is required`);
625
+ const keys = Object.keys(record);
626
+ const only = (...allowed) => keys.every((key) => allowed.includes(key));
627
+ if (record.kind === "architect-plan") {
628
+ if (!only("kind", "path") || !string(record.path)) {
629
+ throw new Error(`agent recipe: ${sourcePath}: architect-plan requires only non-empty path`);
630
+ }
631
+ if (path.isAbsolute(record.path) || record.path.split(/[\\/]/u).includes("..")) {
632
+ throw new Error(`agent recipe: ${sourcePath}: architect-plan path must be workspace-relative`);
633
+ }
634
+ return { kind: "architect-plan", path: record.path };
635
+ }
636
+ const kinds = [
637
+ "scout-report",
638
+ "verifier-report",
639
+ "debugger-report",
640
+ "research-report",
641
+ "mutation-report",
642
+ "provenance-report",
643
+ "external-delegation",
644
+ "artifact-report",
645
+ "context-handbook"
646
+ ];
647
+ if (!kinds.includes(record.kind) || !only("kind")) {
648
+ throw new Error(`agent recipe: ${sourcePath}: resultContract.kind is unsupported or has unknown keys`);
649
+ }
650
+ return { kind: record.kind };
651
+ }
652
+
653
+ // src/domains/agents/fleet-commands.ts
654
+ import { existsSync, readFileSync } from "node:fs";
655
+ import { isAbsolute, join, normalize } from "node:path";
656
+ import { Type } from "typebox";
657
+ import { Value } from "typebox/value";
658
+ import yaml from "yaml";
659
+ var FLEET_COMMAND_MAX_TIMEOUT_MS = 36e5;
660
+ var FLEET_COMMAND_MIN_TIMEOUT_MS = 1e3;
661
+ var FLEET_COMMAND_DEFAULT_TIMEOUT_MS = 6e5;
662
+ var FLEET_COMMAND_BASE_ENV = ["PATH", "HOME", "LANG", "LC_ALL", "TZ", "TMPDIR"];
663
+ var CommandSchema = Type.Object(
664
+ {
665
+ argv: Type.Array(Type.String({ minLength: 1 }), { minItems: 1 }),
666
+ cwd: Type.Optional(Type.String({ minLength: 1 })),
667
+ timeoutMs: Type.Optional(
668
+ Type.Integer({ minimum: FLEET_COMMAND_MIN_TIMEOUT_MS, maximum: FLEET_COMMAND_MAX_TIMEOUT_MS })
669
+ ),
670
+ env: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
671
+ description: Type.Optional(Type.String())
672
+ },
673
+ { additionalProperties: false }
674
+ );
675
+ var RegistrySchema = Type.Object(
676
+ {
677
+ version: Type.Literal(1),
678
+ commands: Type.Record(Type.String({ minLength: 1 }), CommandSchema, { minProperties: 1 })
679
+ },
680
+ { additionalProperties: false }
681
+ );
682
+ var COMMAND_ID_RE = /^[a-z0-9][a-z0-9._-]{0,63}$/u;
683
+ var ENV_NAME_RE = /^[A-Z_][A-Z0-9_]*$/u;
684
+ function fleetCommandsPath(cwd) {
685
+ return join(cwd, ".clio-coder", "fleets", "commands.yaml");
686
+ }
687
+ function firstSchemaError(value) {
688
+ if (Value.Check(RegistrySchema, value)) return null;
689
+ const first = [...Value.Errors(RegistrySchema, value)][0];
690
+ return first ? `${first.instancePath || "(root)"}: ${first.message}` : "command registry failed validation";
691
+ }
692
+ function checkCwd(id, value, sourcePath) {
693
+ if (isAbsolute(value)) {
694
+ throw new Error(`fleet commands ${sourcePath}: command '${id}' cwd must be workspace-relative`);
695
+ }
696
+ const normalized = normalize(value);
697
+ if (normalized === ".." || normalized.startsWith(`..${"/"}`) || normalized.split(/[\\/]/u).includes("..")) {
698
+ throw new Error(`fleet commands ${sourcePath}: command '${id}' cwd escapes the workspace`);
699
+ }
700
+ return normalized === "." ? "" : normalized;
701
+ }
702
+ function parseFleetCommands(raw, sourcePath) {
703
+ let parsed;
704
+ try {
705
+ parsed = yaml.parse(raw);
706
+ } catch (err) {
707
+ const reason = err instanceof Error ? err.message : String(err);
708
+ throw new Error(`fleet commands ${sourcePath}: invalid YAML (${reason})`);
709
+ }
710
+ const schemaError = firstSchemaError(parsed);
711
+ if (schemaError !== null) throw new Error(`fleet commands ${sourcePath}: ${schemaError}`);
712
+ const registry = parsed;
713
+ const commands = /* @__PURE__ */ new Map();
714
+ for (const [id, entry] of Object.entries(registry.commands)) {
715
+ if (!COMMAND_ID_RE.test(id)) {
716
+ throw new Error(`fleet commands ${sourcePath}: command id '${id}' must match ${COMMAND_ID_RE.source}`);
717
+ }
718
+ const executable = entry.argv[0] ?? "";
719
+ if (executable.trim().length === 0) {
720
+ throw new Error(`fleet commands ${sourcePath}: command '${id}' has an empty executable`);
721
+ }
722
+ for (const name of entry.env ?? []) {
723
+ if (!ENV_NAME_RE.test(name)) {
724
+ throw new Error(`fleet commands ${sourcePath}: command '${id}' env name '${name}' is not a variable name`);
725
+ }
726
+ }
727
+ commands.set(id, {
728
+ id,
729
+ argv: [...entry.argv],
730
+ cwd: entry.cwd === void 0 ? "" : checkCwd(id, entry.cwd, sourcePath),
731
+ timeoutMs: entry.timeoutMs ?? FLEET_COMMAND_DEFAULT_TIMEOUT_MS,
732
+ env: [...entry.env ?? []],
733
+ description: entry.description ?? ""
734
+ });
735
+ }
736
+ return { version: 1, commands, path: sourcePath };
737
+ }
738
+ function loadFleetCommands(cwd) {
739
+ const path7 = fleetCommandsPath(cwd);
740
+ if (!existsSync(path7)) return null;
741
+ return parseFleetCommands(readFileSync(path7, "utf8"), path7);
742
+ }
743
+
744
+ // src/domains/agents/fleet-contract.ts
745
+ import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2 } from "node:fs";
746
+ import { basename, join as join2 } from "node:path";
747
+ import { Type as Type2 } from "typebox";
748
+ import { Value as Value2 } from "typebox/value";
749
+
750
+ // src/domains/agents/frontmatter.ts
751
+ import yaml2 from "yaml";
752
+ function parseFrontmatter(raw, sourcePath) {
753
+ const opening = raw.match(/^---\r?\n/);
754
+ if (!opening) {
755
+ throw new Error(`frontmatter: ${sourcePath}: missing YAML frontmatter opening delimiter`);
756
+ }
757
+ const closeRegex = /\r?\n---(?:\r?\n|$)/g;
758
+ closeRegex.lastIndex = opening[0].length;
759
+ const closing = closeRegex.exec(raw);
760
+ if (!closing) {
761
+ throw new Error(`frontmatter: ${sourcePath}: missing YAML frontmatter closing delimiter`);
762
+ }
763
+ const frontmatterText = raw.slice(opening[0].length, closing.index);
764
+ const body = raw.slice(closing.index + closing[0].length);
765
+ let parsed;
766
+ try {
767
+ parsed = yaml2.parse(frontmatterText);
768
+ } catch (err) {
769
+ const reason = err instanceof Error ? err.message : String(err);
770
+ throw new Error(`frontmatter: ${sourcePath}: invalid YAML frontmatter (${reason})`);
771
+ }
772
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
773
+ throw new Error(`frontmatter: ${sourcePath}: frontmatter must be a YAML object`);
774
+ }
775
+ return {
776
+ frontmatter: parsed,
777
+ body
778
+ };
779
+ }
780
+
781
+ // src/domains/agents/write-boundary.ts
782
+ var WRITE_BOUNDARY_MAX_ENTRIES = 32;
783
+ var GLOB_CHARACTERS = /[*?[\]{}]/u;
784
+ function normalizeWriteBoundaryEntry(entry) {
785
+ const raw = entry.trim();
786
+ if (raw.length === 0) throw new Error("write boundary: entry must be a non-empty path");
787
+ if (raw.includes("\\")) {
788
+ throw new Error(`write boundary: entry '${entry}' must use '/' separators`);
789
+ }
790
+ if (GLOB_CHARACTERS.test(raw)) {
791
+ throw new Error(`write boundary: entry '${entry}' looks like a glob, which v1 does not support`);
792
+ }
793
+ if (raw.startsWith("/") || /^[A-Za-z]:/u.test(raw)) {
794
+ throw new Error(`write boundary: entry '${entry}' must be repository-relative, not absolute`);
795
+ }
796
+ const subtree = raw.endsWith("/");
797
+ const segments = raw.split("/").filter((segment) => segment.length > 0);
798
+ if (segments.length === 0) {
799
+ throw new Error(`write boundary: entry '${entry}' must name a path inside the repository`);
800
+ }
801
+ for (const segment of segments) {
802
+ if (segment === "..") throw new Error(`write boundary: entry '${entry}' must not contain '..'`);
803
+ if (segment === ".") throw new Error(`write boundary: entry '${entry}' must not contain '.' segments`);
804
+ }
805
+ const joined = segments.join("/");
806
+ return subtree ? `${joined}/` : joined;
807
+ }
808
+ function normalizeWriteBoundary(entries) {
809
+ if (entries.length > WRITE_BOUNDARY_MAX_ENTRIES) {
810
+ throw new Error(`write boundary: at most ${WRITE_BOUNDARY_MAX_ENTRIES} entries may be declared`);
811
+ }
812
+ const normalized = new Set(entries.map(normalizeWriteBoundaryEntry));
813
+ return [...normalized].sort();
814
+ }
815
+ function toRepoRelativePosix(path7) {
816
+ return path7.split("\\").join("/");
817
+ }
818
+ function writeBoundaryCovers(boundary, path7) {
819
+ const candidate = toRepoRelativePosix(path7);
820
+ return boundary.some((entry) => entry.endsWith("/") ? candidate.startsWith(entry) : candidate === entry);
821
+ }
822
+ function describeWriteBoundary(boundary) {
823
+ return boundary.length === 0 ? "(nothing: readonly)" : boundary.join(", ");
824
+ }
825
+
826
+ // src/domains/agents/fleet-contract.ts
827
+ var FLEET_WRITE_BOUNDARY_VERSION = 4;
828
+ var FLEET_LOOP_MAX_ATTEMPTS = 5;
829
+ var FleetScopeSchema = Type2.Union([Type2.Literal("readonly"), Type2.Literal("workspace")]);
830
+ function writesSchema(version) {
831
+ return version >= FLEET_WRITE_BOUNDARY_VERSION ? { writes: Type2.Optional(Type2.Array(Type2.String({ minLength: 1 }), { maxItems: WRITE_BOUNDARY_MAX_ENTRIES })) } : {};
832
+ }
833
+ function agentStepSchema(version) {
834
+ return Type2.Object(
835
+ {
836
+ kind: Type2.Optional(Type2.Literal("agent")),
837
+ id: Type2.String({ minLength: 1 }),
838
+ agent: Type2.String({ minLength: 1 }),
839
+ scope: FleetScopeSchema,
840
+ dependencies: Type2.Array(Type2.String({ minLength: 1 })),
841
+ ...writesSchema(version)
842
+ },
843
+ { additionalProperties: false }
844
+ );
845
+ }
846
+ function codeStepSchema(version) {
847
+ return Type2.Object(
848
+ {
849
+ kind: Type2.Literal("code"),
850
+ id: Type2.String({ minLength: 1 }),
851
+ command: Type2.String({ minLength: 1 }),
852
+ scope: FleetScopeSchema,
853
+ dependencies: Type2.Array(Type2.String({ minLength: 1 })),
854
+ ...version >= 3 ? { commitFrom: Type2.Optional(Type2.Array(Type2.String({ minLength: 1 }), { minItems: 1 })) } : {},
855
+ ...writesSchema(version)
856
+ },
857
+ { additionalProperties: false }
858
+ );
859
+ }
860
+ function loopStepSchema(version) {
861
+ return Type2.Object(
862
+ {
863
+ kind: Type2.Literal("loop"),
864
+ id: Type2.String({ minLength: 1 }),
865
+ // Required and finite: this is the whole point of the construct.
866
+ maxAttempts: Type2.Integer({ minimum: 1, maximum: FLEET_LOOP_MAX_ATTEMPTS }),
867
+ dependencies: Type2.Array(Type2.String({ minLength: 1 })),
868
+ check: Type2.Union([
869
+ Type2.Object(
870
+ {
871
+ kind: Type2.Literal("code"),
872
+ command: Type2.String({ minLength: 1 }),
873
+ scope: FleetScopeSchema,
874
+ ...writesSchema(version)
875
+ },
876
+ { additionalProperties: false }
877
+ ),
878
+ Type2.Object(
879
+ {
880
+ kind: Type2.Literal("agent"),
881
+ agent: Type2.String({ minLength: 1 }),
882
+ scope: FleetScopeSchema,
883
+ ...writesSchema(version)
884
+ },
885
+ { additionalProperties: false }
886
+ )
887
+ ]),
888
+ repair: Type2.Object(
889
+ {
890
+ kind: Type2.Optional(Type2.Literal("agent")),
891
+ agent: Type2.String({ minLength: 1 }),
892
+ scope: FleetScopeSchema,
893
+ ...writesSchema(version)
894
+ },
895
+ { additionalProperties: false }
896
+ )
897
+ },
898
+ { additionalProperties: false }
899
+ );
900
+ }
901
+ function stepSchema(version) {
902
+ if (version === 1) return agentStepSchema(1);
903
+ if (version === 2) return Type2.Union([agentStepSchema(2), codeStepSchema(2)]);
904
+ return Type2.Union([agentStepSchema(version), codeStepSchema(version), loopStepSchema(version)]);
905
+ }
906
+ function frontmatterSchema(version) {
907
+ return Type2.Object(
908
+ {
909
+ version: Type2.Literal(version),
910
+ name: Type2.String({ minLength: 1 }),
911
+ description: Type2.Optional(Type2.String()),
912
+ steps: Type2.Array(stepSchema(version), { minItems: 1 }),
913
+ maxWorkers: Type2.Integer({ minimum: 1 }),
914
+ budgetUsd: Type2.Optional(Type2.Number()),
915
+ onFailure: Type2.Union([Type2.Literal("stop"), Type2.Literal("continue")])
916
+ },
917
+ { additionalProperties: false }
918
+ );
919
+ }
920
+ var SCHEMAS = {
921
+ 1: frontmatterSchema(1),
922
+ 2: frontmatterSchema(2),
923
+ 3: frontmatterSchema(3),
924
+ 4: frontmatterSchema(4)
925
+ };
926
+ function contractVersion(frontmatter) {
927
+ const value = frontmatter.version;
928
+ return value === 1 || value === 2 || value === 3 || value === 4 ? value : null;
929
+ }
930
+ function firstSchemaError2(frontmatter, version) {
931
+ const schema = SCHEMAS[version];
932
+ if (Value2.Check(schema, frontmatter)) return null;
933
+ const first = [...Value2.Errors(schema, frontmatter)][0];
934
+ return first ? `${first.instancePath || "(root)"}: ${first.message}` : "front matter failed validation";
935
+ }
936
+ function fleetsDir(cwd) {
937
+ return join2(cwd, ".clio-coder", "fleets");
938
+ }
939
+ function normalizedWrites(writes) {
940
+ return writes === void 0 ? {} : { writes: normalizeWriteBoundary(writes) };
941
+ }
942
+ function normalizeStep(step) {
943
+ if (step.kind === "loop") {
944
+ const check = step.check;
945
+ const repair = step.repair;
946
+ return {
947
+ kind: "loop",
948
+ id: step.id,
949
+ maxAttempts: step.maxAttempts ?? 0,
950
+ dependencies: [...step.dependencies],
951
+ check: check.kind === "code" ? { kind: "code", command: check.command ?? "", scope: check.scope, ...normalizedWrites(check.writes) } : { kind: "agent", agent: check.agent ?? "", scope: check.scope, ...normalizedWrites(check.writes) },
952
+ repair: { kind: "agent", agent: repair.agent, scope: repair.scope, ...normalizedWrites(repair.writes) }
953
+ };
954
+ }
955
+ if (step.kind === "code") {
956
+ return {
957
+ kind: "code",
958
+ id: step.id,
959
+ command: step.command ?? "",
960
+ scope: step.scope,
961
+ dependencies: [...step.dependencies],
962
+ ...step.commitFrom !== void 0 ? { commitFrom: [...step.commitFrom] } : {},
963
+ ...normalizedWrites(step.writes)
964
+ };
965
+ }
966
+ return {
967
+ kind: "agent",
968
+ id: step.id,
969
+ agent: step.agent ?? "",
970
+ scope: step.scope,
971
+ dependencies: [...step.dependencies],
972
+ ...normalizedWrites(step.writes)
973
+ };
974
+ }
975
+ function fleetLoopCheckStepId(loopId, attempt) {
976
+ return `${loopId}.check.${attempt}`;
977
+ }
978
+ function fleetLoopRepairStepId(loopId, attempt) {
979
+ return `${loopId}.repair.${attempt}`;
980
+ }
981
+ function occupiedIds(contract) {
982
+ const owners = /* @__PURE__ */ new Map();
983
+ for (const step of contract.steps) {
984
+ owners.set(step.id, step.id);
985
+ if (step.kind !== "loop") continue;
986
+ for (let attempt = 1; attempt <= step.maxAttempts; attempt++) {
987
+ owners.set(fleetLoopCheckStepId(step.id, attempt), step.id);
988
+ if (attempt < step.maxAttempts) owners.set(fleetLoopRepairStepId(step.id, attempt), step.id);
989
+ }
990
+ }
991
+ return owners;
992
+ }
993
+ function validateFleetGraph(contract) {
994
+ const declared = /* @__PURE__ */ new Set();
995
+ for (const step of contract.steps) {
996
+ if (declared.has(step.id)) throw new Error(`fleet contract ${contract.path}: duplicate step id '${step.id}'`);
997
+ declared.add(step.id);
998
+ }
999
+ const owners = /* @__PURE__ */ new Map();
1000
+ for (const step of contract.steps) {
1001
+ if (step.kind !== "loop") continue;
1002
+ for (const [generated, owner] of occupiedIds({ steps: [step] })) {
1003
+ if (generated === step.id) continue;
1004
+ if (declared.has(generated)) {
1005
+ throw new Error(
1006
+ `fleet contract ${contract.path}: step id '${generated}' collides with an id loop '${owner}' generates`
1007
+ );
1008
+ }
1009
+ owners.set(generated, owner);
1010
+ }
1011
+ }
1012
+ for (const step of contract.steps) {
1013
+ for (const dependency of step.dependencies) {
1014
+ if (dependency === step.id) throw new Error(`fleet contract ${contract.path}: step '${step.id}' depends on itself`);
1015
+ if (!declared.has(dependency)) {
1016
+ throw new Error(`fleet contract ${contract.path}: step '${step.id}' has unknown dependency '${dependency}'`);
1017
+ }
1018
+ }
1019
+ }
1020
+ const remaining = new Set(declared);
1021
+ const settled = /* @__PURE__ */ new Set();
1022
+ while (remaining.size > 0) {
1023
+ const ready = contract.steps.filter(
1024
+ (step) => remaining.has(step.id) && step.dependencies.every((dependency) => settled.has(dependency))
1025
+ );
1026
+ if (ready.length === 0) {
1027
+ throw new Error(`fleet contract ${contract.path}: dependency cycle among steps ${[...remaining].sort().join(", ")}`);
1028
+ }
1029
+ for (const step of ready) {
1030
+ remaining.delete(step.id);
1031
+ settled.add(step.id);
1032
+ }
1033
+ }
1034
+ const ancestors = fleetStepAncestors(contract.steps);
1035
+ for (const step of contract.steps) {
1036
+ if (step.kind !== "code" || step.commitFrom === void 0) continue;
1037
+ for (const source of step.commitFrom) {
1038
+ const target = contract.steps.find((candidate) => candidate.id === source);
1039
+ if (target === void 0) {
1040
+ throw new Error(`fleet contract ${contract.path}: commit step '${step.id}' names unknown source '${source}'`);
1041
+ }
1042
+ if (target.kind === "code") {
1043
+ throw new Error(
1044
+ `fleet contract ${contract.path}: commit step '${step.id}' source '${source}' is a code step and authors no commit message`
1045
+ );
1046
+ }
1047
+ if (!(ancestors.get(step.id)?.has(source) ?? false)) {
1048
+ throw new Error(
1049
+ `fleet contract ${contract.path}: commit step '${step.id}' must depend on its message source '${source}'`
1050
+ );
1051
+ }
1052
+ }
1053
+ }
1054
+ }
1055
+ function fleetStepWriteBoundary(version, scope, writes) {
1056
+ if (version < FLEET_WRITE_BOUNDARY_VERSION) return void 0;
1057
+ return scope === "readonly" ? [] : [...writes ?? []];
1058
+ }
1059
+ function fleetStepBoundaries(contract) {
1060
+ const boundaries = [];
1061
+ const add = (id, scope, writes) => {
1062
+ boundaries.push({ id, scope, writes: fleetStepWriteBoundary(contract.version, scope, writes) });
1063
+ };
1064
+ for (const step of contract.steps) {
1065
+ if (step.kind === "loop") {
1066
+ add(`${step.id}.check`, step.check.scope, step.check.writes);
1067
+ add(`${step.id}.repair`, step.repair.scope, step.repair.writes);
1068
+ continue;
1069
+ }
1070
+ add(step.id, step.scope, step.writes);
1071
+ }
1072
+ return boundaries;
1073
+ }
1074
+ function validateWriteBoundaries(contract) {
1075
+ if (contract.version < FLEET_WRITE_BOUNDARY_VERSION) return;
1076
+ for (const position of fleetStepBoundaries(contract)) {
1077
+ if (position.scope === "workspace" && (position.writes?.length ?? 0) === 0) {
1078
+ throw new Error(
1079
+ `fleet contract ${contract.path}: step '${position.id}' has scope 'workspace' and must declare a non-empty 'writes' allowlist at version ${FLEET_WRITE_BOUNDARY_VERSION}`
1080
+ );
1081
+ }
1082
+ }
1083
+ for (const step of contract.steps) {
1084
+ const declared = step.kind === "loop" ? [
1085
+ { id: `${step.id}.check`, scope: step.check.scope, writes: step.check.writes },
1086
+ { id: `${step.id}.repair`, scope: step.repair.scope, writes: step.repair.writes }
1087
+ ] : [{ id: step.id, scope: step.scope, writes: step.writes }];
1088
+ for (const position of declared) {
1089
+ if (position.scope === "readonly" && position.writes !== void 0) {
1090
+ throw new Error(
1091
+ `fleet contract ${contract.path}: step '${position.id}' is 'readonly', which is the empty allowlist; remove its 'writes' or give it scope 'workspace'`
1092
+ );
1093
+ }
1094
+ }
1095
+ }
1096
+ }
1097
+ function fleetStepAncestors(steps) {
1098
+ const direct = new Map(steps.map((step) => [step.id, step.dependencies]));
1099
+ const closure = /* @__PURE__ */ new Map();
1100
+ const resolve = (id, seen) => {
1101
+ const cached = closure.get(id);
1102
+ if (cached !== void 0) return cached;
1103
+ const result = /* @__PURE__ */ new Set();
1104
+ if (seen.has(id)) return result;
1105
+ seen.add(id);
1106
+ for (const dependency of direct.get(id) ?? []) {
1107
+ result.add(dependency);
1108
+ for (const inherited of resolve(dependency, seen)) result.add(inherited);
1109
+ }
1110
+ closure.set(id, result);
1111
+ return result;
1112
+ };
1113
+ for (const step of steps) resolve(step.id, /* @__PURE__ */ new Set());
1114
+ return closure;
1115
+ }
1116
+ function parseFleetContract(raw, sourcePath) {
1117
+ const { frontmatter, body } = parseFrontmatter(raw, sourcePath);
1118
+ const version = contractVersion(frontmatter);
1119
+ if (version === null) {
1120
+ throw new Error(
1121
+ `fleet contract ${sourcePath}: version must be 1 (agent steps), 2 (agent and code steps), 3 (adds bounded loops and commit steps), or 4 (adds enforced write boundaries)`
1122
+ );
1123
+ }
1124
+ if (version < FLEET_WRITE_BOUNDARY_VERSION) assertNoWritesBefore(frontmatter, sourcePath, version);
1125
+ const schemaError = firstSchemaError2(frontmatter, version);
1126
+ if (schemaError !== null) {
1127
+ throw new Error(`fleet contract ${sourcePath}: ${schemaError}`);
1128
+ }
1129
+ const fm = frontmatter;
1130
+ if (fm.budgetUsd !== void 0 && !(fm.budgetUsd > 0)) {
1131
+ throw new Error(`fleet contract ${sourcePath}: budgetUsd must be a positive number`);
1132
+ }
1133
+ const trimmedBody = body.trim();
1134
+ if (trimmedBody.length === 0) {
1135
+ throw new Error(`fleet contract ${sourcePath}: prompt body is empty`);
1136
+ }
1137
+ const contract = {
1138
+ version,
1139
+ name: fm.name,
1140
+ description: fm.description ?? "",
1141
+ steps: fm.steps.map(normalizeStep),
1142
+ maxWorkers: fm.maxWorkers,
1143
+ budgetUsd: fm.budgetUsd ?? null,
1144
+ onFailure: fm.onFailure,
1145
+ body: trimmedBody,
1146
+ path: sourcePath
1147
+ };
1148
+ validateFleetGraph(contract);
1149
+ validateWriteBoundaries(contract);
1150
+ return contract;
1151
+ }
1152
+ function assertNoWritesBefore(frontmatter, sourcePath, version) {
1153
+ const steps = Array.isArray(frontmatter.steps) ? frontmatter.steps : [];
1154
+ const declaresWrites = (value) => typeof value === "object" && value !== null && "writes" in value;
1155
+ for (const step of steps) {
1156
+ const raw = step;
1157
+ if (declaresWrites(raw) || declaresWrites(raw.check) || declaresWrites(raw.repair)) {
1158
+ throw new Error(
1159
+ `fleet contract ${sourcePath}: 'writes' requires contract version ${FLEET_WRITE_BOUNDARY_VERSION}; this contract declares version ${version}, where the declaration would not be enforced`
1160
+ );
1161
+ }
1162
+ }
1163
+ }
1164
+ function fleetCodeSteps(contract) {
1165
+ const steps = [];
1166
+ for (const step of contract.steps) {
1167
+ if (step.kind === "code") steps.push({ id: step.id, command: step.command });
1168
+ else if (step.kind === "loop" && step.check.kind === "code") {
1169
+ steps.push({ id: fleetLoopCheckStepId(step.id, 1), command: step.check.command });
1170
+ }
1171
+ }
1172
+ return steps;
1173
+ }
1174
+ var FLEET_COMMANDS_REPO_PATH = ".clio-coder/fleets/commands.yaml";
1175
+ var FLEET_COMMANDS_REMEDY = "declare each id there under `commands:` with an `argv` list; `clio-coder docs fleet_dispatch` has the schema";
1176
+ var FleetCommandRegistryMissingError = class extends Error {
1177
+ constructor(contractPath, commands) {
1178
+ super(
1179
+ `fleet contract ${contractPath}: code steps require a command registry at ${FLEET_COMMANDS_REPO_PATH} declaring ${commands.join(", ")}`
1180
+ );
1181
+ this.contractPath = contractPath;
1182
+ this.commands = commands;
1183
+ this.name = "FleetCommandRegistryMissingError";
1184
+ }
1185
+ contractPath;
1186
+ commands;
1187
+ };
1188
+ function validateFleetCommands(contract, registry) {
1189
+ const codeSteps = fleetCodeSteps(contract);
1190
+ if (codeSteps.length === 0) return;
1191
+ if (registry === null) {
1192
+ throw new FleetCommandRegistryMissingError(contract.path, [...new Set(codeSteps.map((step) => step.command))].sort());
1193
+ }
1194
+ for (const step of codeSteps) {
1195
+ if (!registry.commands.has(step.command)) {
1196
+ const known = [...registry.commands.keys()].sort().join(", ");
1197
+ throw new Error(
1198
+ `fleet contract ${contract.path}: step '${step.id}' names unknown command '${step.command}' (registered: ${known || "none"})`
1199
+ );
1200
+ }
1201
+ }
1202
+ }
1203
+ function builtinFleetsDir() {
1204
+ return join2(resolvePackageRoot(), "src", "domains", "agents", "fleets");
1205
+ }
1206
+ function fleetContractPath(cwd, name) {
1207
+ const projectPath = join2(fleetsDir(cwd), `${name}.md`);
1208
+ if (existsSync2(projectPath)) return { path: projectPath, source: "project" };
1209
+ const builtinPath = join2(builtinFleetsDir(), `${name}.md`);
1210
+ if (existsSync2(builtinPath)) return { path: builtinPath, source: "builtin" };
1211
+ return null;
1212
+ }
1213
+ function loadFleetContract(cwd, name) {
1214
+ const located = fleetContractPath(cwd, name);
1215
+ if (located === null) {
1216
+ throw new Error(`fleet contract not found: ${join2(fleetsDir(cwd), `${name}.md`)} (and no builtin named '${name}')`);
1217
+ }
1218
+ const contract = parseFleetContract(readFileSync2(located.path, "utf8"), located.path);
1219
+ validateFleetCommands(contract, loadFleetCommands(cwd));
1220
+ return contract;
1221
+ }
1222
+ function listDirectory(dir) {
1223
+ if (!existsSync2(dir)) return [];
1224
+ try {
1225
+ return readdirSync(dir).filter((name) => name.endsWith(".md")).sort();
1226
+ } catch {
1227
+ return [];
1228
+ }
1229
+ }
1230
+ function listFleetContracts(cwd) {
1231
+ let registry = null;
1232
+ let registryError = null;
1233
+ try {
1234
+ registry = loadFleetCommands(cwd);
1235
+ } catch (err) {
1236
+ registryError = err instanceof Error ? err.message : String(err);
1237
+ }
1238
+ const listings = /* @__PURE__ */ new Map();
1239
+ const sources = [
1240
+ { dir: builtinFleetsDir(), source: "builtin" },
1241
+ { dir: fleetsDir(cwd), source: "project" }
1242
+ ];
1243
+ for (const { dir, source } of sources) {
1244
+ for (const file of listDirectory(dir)) {
1245
+ const path7 = join2(dir, file);
1246
+ const name = basename(file, ".md");
1247
+ try {
1248
+ const contract = parseFleetContract(readFileSync2(path7, "utf8"), path7);
1249
+ if (registryError !== null && fleetCodeSteps(contract).length > 0) throw new Error(registryError);
1250
+ validateFleetCommands(contract, registry);
1251
+ listings.set(name, { name, path: path7, source, contract, error: null, needsCommands: null });
1252
+ } catch (err) {
1253
+ listings.set(name, {
1254
+ name,
1255
+ path: path7,
1256
+ source,
1257
+ contract: null,
1258
+ error: err instanceof Error ? err.message : String(err),
1259
+ // A registry this repo never wrote is unfinished setup. A registry
1260
+ // that exists and does not parse, or one missing an id the contract
1261
+ // names, is a real error and stays one.
1262
+ needsCommands: err instanceof FleetCommandRegistryMissingError ? err.commands : null
1263
+ });
1264
+ }
1265
+ }
1266
+ }
1267
+ return [...listings.values()].sort((left, right) => left.name.localeCompare(right.name));
1268
+ }
1269
+ var PLACEHOLDER_RE = /\{\{\s*([A-Za-z_][A-Za-z0-9_-]*)\s*\}\}/g;
1270
+ function renderFleetPrompt(body, vars) {
1271
+ const missing = /* @__PURE__ */ new Set();
1272
+ const rendered = body.replace(PLACEHOLDER_RE, (_match, name) => {
1273
+ const value = vars[name];
1274
+ if (value === void 0) {
1275
+ missing.add(name);
1276
+ return "";
1277
+ }
1278
+ return value;
1279
+ });
1280
+ if (missing.size > 0) {
1281
+ throw new Error(`fleet prompt: unresolved template variables: ${[...missing].join(", ")} (pass --var name=value)`);
1282
+ }
1283
+ return rendered;
1284
+ }
1285
+
1286
+ // src/domains/safety/autonomy.ts
1287
+ var AUTONOMY_LEVELS = ["read-only", "suggest", "auto-edit", "full-auto"];
1288
+ var DEFAULT_AUTONOMY_LEVEL = "auto-edit";
1289
+ function isAutonomyLevel(value) {
1290
+ return typeof value === "string" && AUTONOMY_LEVELS.includes(value);
1291
+ }
1292
+ function mapAutonomy(level, actionClass, options = {}) {
1293
+ if (actionClass === "read") return "allow";
1294
+ if (actionClass === "git_destructive") return "deny";
1295
+ if (level === "read-only") return "deny";
1296
+ if (level === "suggest") return "ask";
1297
+ switch (actionClass) {
1298
+ case "write":
1299
+ return "allow";
1300
+ case "dispatch":
1301
+ if (options.dispatchPlanScale === true && level !== "full-auto") return "ask";
1302
+ return "allow";
1303
+ case "execute": {
1304
+ if (options.executeRecognized !== false) return "allow";
1305
+ return level === "full-auto" ? "allow" : "ask";
1306
+ }
1307
+ case "unknown":
1308
+ return "ask";
1309
+ default:
1310
+ return "ask";
1311
+ }
1312
+ }
1313
+ function autonomyDenyRejection(level, tool, actionClass) {
1314
+ return {
1315
+ short: `${tool} denied: autonomy level is ${level}`,
1316
+ detail: `Clio is at autonomy ${level}: ${actionClass} actions are denied without prompting. Describe the change you would make instead, so the operator can apply it or raise the autonomy level.`,
1317
+ hints: ["Propose the exact edit or command as text.", "The operator can change the level in /settings."]
1318
+ };
1319
+ }
1320
+ function autonomyAskRejection(level, tool, actionClass) {
1321
+ return {
1322
+ short: `${tool} needs approval (${actionClass}) at autonomy ${level}`,
1323
+ detail: `Autonomy ${level} routes ${actionClass} actions through operator approval. The call is parked until the operator approves it once or cancels it.`,
1324
+ hints: [
1325
+ "Approving resumes only this call.",
1326
+ "Recognized commands can be added to .clio-coder/safety.yaml.",
1327
+ // The sanctioned pivots for a gated shell command: typed verification
1328
+ // and read-class observe tools run without approval at this level,
1329
+ // and models otherwise stall retrying denied bash for checks or
1330
+ // directory listings.
1331
+ ...actionClass === "execute" ? [
1332
+ 'A declared package.json check runs without approval through the verify tool: verify(check="<script>").',
1333
+ "Read-only inspection runs without approval through the ls, read, grep, and find tools."
1334
+ ] : []
1335
+ ]
1336
+ };
1337
+ }
1338
+
1339
+ // src/domains/safety/loop-detector.ts
1340
+ var DEFAULT_WINDOW_MS = 3e4;
1341
+ var DEFAULT_MAX_REPEATS = 3;
1342
+ var DEFAULT_KEEP_LAST_ATTEMPTS = 4;
1343
+ function createLoopState(opts) {
1344
+ const windowMs = opts?.windowMs ?? DEFAULT_WINDOW_MS;
1345
+ const maxRepeats = opts?.maxRepeats ?? DEFAULT_MAX_REPEATS;
1346
+ const keepLastAttempts = opts?.keepLastAttempts ?? DEFAULT_KEEP_LAST_ATTEMPTS;
1347
+ return { recent: [], windowMs, maxRepeats, keepLastAttempts };
1348
+ }
1349
+ function hashToolCall(tool, args) {
1350
+ let argPart;
1351
+ try {
1352
+ argPart = canonicalSerialize(args ?? {}, /* @__PURE__ */ new WeakSet());
1353
+ } catch {
1354
+ argPart = "<unrepresentable>";
1355
+ }
1356
+ return `${tool}${argPart}`;
1357
+ }
1358
+ function canonicalSerialize(value, seen) {
1359
+ if (value === null) return "null";
1360
+ if (typeof value === "number") {
1361
+ if (!Number.isFinite(value)) {
1362
+ throw new Error(`canonicalSerialize: non-finite number ${String(value)} is not representable`);
1363
+ }
1364
+ return JSON.stringify(value);
1365
+ }
1366
+ if (typeof value === "string" || typeof value === "boolean") {
1367
+ return JSON.stringify(value);
1368
+ }
1369
+ if (typeof value === "bigint") {
1370
+ throw new Error("canonicalSerialize: bigint is not representable");
1371
+ }
1372
+ if (typeof value === "symbol" || typeof value === "function" || value === void 0) {
1373
+ throw new Error(`canonicalSerialize: ${typeof value} is not representable`);
1374
+ }
1375
+ if (Array.isArray(value)) {
1376
+ if (seen.has(value)) throw new Error("canonicalSerialize: circular reference is not representable");
1377
+ seen.add(value);
1378
+ try {
1379
+ const parts = [];
1380
+ for (let i = 0; i < value.length; i++) {
1381
+ if (!(i in value) || value[i] === void 0) {
1382
+ parts.push("null");
1383
+ continue;
1384
+ }
1385
+ parts.push(canonicalSerialize(value[i], seen));
1386
+ }
1387
+ return `[${parts.join(",")}]`;
1388
+ } finally {
1389
+ seen.delete(value);
1390
+ }
1391
+ }
1392
+ if (typeof value === "object") {
1393
+ if (value instanceof Date) {
1394
+ const jsonValue = value.toJSON();
1395
+ return jsonValue === null ? "null" : JSON.stringify(jsonValue);
1396
+ }
1397
+ if (seen.has(value)) throw new Error("canonicalSerialize: circular reference is not representable");
1398
+ seen.add(value);
1399
+ const obj = value;
1400
+ try {
1401
+ const keys = Object.keys(obj).sort();
1402
+ const parts = [];
1403
+ for (const key of keys) {
1404
+ const child = obj[key];
1405
+ if (child === void 0) continue;
1406
+ parts.push(`${JSON.stringify(key)}:${canonicalSerialize(child, seen)}`);
1407
+ }
1408
+ return `{${parts.join(",")}}`;
1409
+ } finally {
1410
+ seen.delete(value);
1411
+ }
1412
+ }
1413
+ throw new Error(`canonicalSerialize: unsupported value of type ${typeof value}`);
1414
+ }
1415
+ function observe(state, key, now) {
1416
+ const cutoff = now - state.windowMs;
1417
+ const keepLast = state.keepLastAttempts ?? DEFAULT_KEEP_LAST_ATTEMPTS;
1418
+ const appended = [...state.recent, { key, at: now }];
1419
+ const floorStart = Math.max(0, appended.length - keepLast);
1420
+ const next = appended.filter((entry, index) => index >= floorStart || entry.at >= cutoff);
1421
+ let count = 0;
1422
+ for (const entry of next) {
1423
+ if (entry.key === key) count += 1;
1424
+ }
1425
+ const looping = count >= state.maxRepeats;
1426
+ const newState = {
1427
+ recent: next,
1428
+ windowMs: state.windowMs,
1429
+ maxRepeats: state.maxRepeats
1430
+ };
1431
+ return [newState, { looping, key, count }];
1432
+ }
1433
+
1434
+ // src/domains/safety/path-policy.ts
1435
+ import { homedir } from "node:os";
1436
+ import path2 from "node:path";
1437
+ var ORDERED_KINDS = ["zeroAccessPaths", "readOnlyPaths", "noDeletePaths"];
1438
+ function isSameOrDescendant(candidatePath, policyPath) {
1439
+ const candidate = path2.resolve(candidatePath);
1440
+ const policy = path2.resolve(policyPath);
1441
+ const relative = path2.relative(policy, candidate);
1442
+ return relative === "" || !relative.startsWith("..") && !path2.isAbsolute(relative);
1443
+ }
1444
+ function expandTilde(rawPath) {
1445
+ if (rawPath === "~") return homedir();
1446
+ if (rawPath.startsWith("~/")) return path2.join(homedir(), rawPath.slice(2));
1447
+ return rawPath;
1448
+ }
1449
+ function normalizePolicyEntry(rawPath, root) {
1450
+ const expanded = expandTilde(rawPath.trim());
1451
+ const resolved = path2.isAbsolute(expanded) ? path2.resolve(expanded) : path2.resolve(root, expanded);
1452
+ return canonicalizeExistingPath(resolved);
1453
+ }
1454
+ function blocksOperation(kind, operation) {
1455
+ if (kind === "zeroAccessPaths") return true;
1456
+ if (kind === "readOnlyPaths") return operation === "write" || operation === "delete";
1457
+ return operation === "delete";
1458
+ }
1459
+ function compilePathPolicy(input, root = process.cwd()) {
1460
+ const resolvedRoot = canonicalizeExistingPath(path2.resolve(root));
1461
+ const entries = [];
1462
+ const diagnostics = [];
1463
+ for (const kind of ORDERED_KINDS) {
1464
+ for (const rawPath of input[kind] ?? []) {
1465
+ const trimmed = rawPath.trim();
1466
+ if (trimmed.length === 0) {
1467
+ diagnostics.push(`${kind}: path must not be empty`);
1468
+ continue;
1469
+ }
1470
+ const entry = { kind, path: normalizePolicyEntry(trimmed, resolvedRoot), source: trimmed };
1471
+ const pattern = compilePathPattern(trimmed, resolvedRoot);
1472
+ if (pattern !== null) entry.pattern = pattern;
1473
+ entries.push(entry);
1474
+ }
1475
+ }
1476
+ return {
1477
+ root: resolvedRoot,
1478
+ entries: entries.sort((a, b) => a.kind.localeCompare(b.kind) || a.path.localeCompare(b.path)),
1479
+ diagnostics
1480
+ };
1481
+ }
1482
+ function evaluatePathPolicy(policy, operation, targetPath, cwd = policy.root) {
1483
+ const expanded = expandTilde(targetPath);
1484
+ const lexicalTarget = path2.isAbsolute(expanded) ? path2.resolve(expanded) : path2.resolve(cwd, expanded);
1485
+ const resolvedTarget = canonicalizeExistingPath(lexicalTarget);
1486
+ const normalizedResolvedTarget = normalizeSeparators(resolvedTarget);
1487
+ const normalizedRelativeTarget = normalizeSeparators(path2.relative(policy.root, resolvedTarget));
1488
+ const normalizedRawTarget = normalizeSeparators(targetPath);
1489
+ for (const entry of policy.entries) {
1490
+ if (!blocksOperation(entry.kind, operation)) continue;
1491
+ if (!matchesEntry(entry, resolvedTarget, normalizedResolvedTarget, normalizedRelativeTarget, normalizedRawTarget))
1492
+ continue;
1493
+ return {
1494
+ kind: "block",
1495
+ reasonCode: `path-policy:${entry.kind}`,
1496
+ reason: `${operation} denied by ${entry.kind} entry ${entry.source}`,
1497
+ matchedPath: entry.path,
1498
+ policyKind: entry.kind
1499
+ };
1500
+ }
1501
+ return { kind: "allow" };
1502
+ }
1503
+ function matchesEntry(entry, resolvedTarget, normalizedResolvedTarget, normalizedRelativeTarget, normalizedRawTarget) {
1504
+ if (isSameOrDescendant(resolvedTarget, entry.path)) return true;
1505
+ if (entry.pattern === void 0) return false;
1506
+ return entry.pattern.test(normalizedResolvedTarget) || entry.pattern.test(normalizedRelativeTarget) || entry.pattern.test(normalizedRawTarget);
1507
+ }
1508
+ function compilePathPattern(rawPath, root) {
1509
+ if (!rawPath.includes("*")) return null;
1510
+ const expanded = expandTilde(rawPath.trim());
1511
+ const source = path2.isAbsolute(expanded) ? expanded : path2.join(root, expanded);
1512
+ const normalized = normalizeSeparators(canonicalizePatternSource(source));
1513
+ const relative = normalizeSeparators(path2.isAbsolute(expanded) ? expanded : expanded);
1514
+ const absolutePattern = segmentPattern(normalized);
1515
+ const relativePattern = segmentPattern(relative);
1516
+ return new RegExp(`(?:${absolutePattern})|(?:${relativePattern})`);
1517
+ }
1518
+ function canonicalizePatternSource(patternPath) {
1519
+ const wildcardIndex = patternPath.indexOf("*");
1520
+ if (wildcardIndex === -1) return canonicalizeExistingPath(patternPath);
1521
+ const fixedPrefix = patternPath.slice(0, wildcardIndex);
1522
+ const separatorIndex = fixedPrefix.lastIndexOf(path2.sep);
1523
+ if (separatorIndex === -1) return patternPath;
1524
+ const prefix = patternPath.slice(0, separatorIndex + 1);
1525
+ const suffix = patternPath.slice(separatorIndex + 1);
1526
+ return path2.join(canonicalizeExistingPath(prefix), suffix);
1527
+ }
1528
+ function segmentPattern(rawPath) {
1529
+ const escaped = rawPath.split("*").map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("[^/]*");
1530
+ return `(?:^|/)${escaped}(?:$|/)`;
1531
+ }
1532
+ function normalizeSeparators(value) {
1533
+ return value.split(path2.sep).join("/");
1534
+ }
1535
+
1536
+ // src/domains/safety/policy-engine.ts
1537
+ import { createHash as createHash3 } from "node:crypto";
1538
+ import path4 from "node:path";
1539
+
1540
+ // src/domains/safety/default-path-policy.ts
1541
+ var DEFAULT_DAMAGE_CONTROL_PATH_POLICY = {
1542
+ zeroAccessPaths: [
1543
+ ".env",
1544
+ ".env.local",
1545
+ ".env.development",
1546
+ ".env.production",
1547
+ ".env.staging",
1548
+ ".env.test",
1549
+ ".env.*.local",
1550
+ "*.env",
1551
+ "~/.ssh/",
1552
+ "~/.gnupg/",
1553
+ "~/.aws/",
1554
+ "~/.config/gcloud/",
1555
+ "*-credentials.json",
1556
+ "*serviceAccount*.json",
1557
+ "*service-account*.json",
1558
+ "~/.azure/",
1559
+ "~/.kube/",
1560
+ "kubeconfig",
1561
+ "*-secret.yaml",
1562
+ "secrets.yaml",
1563
+ "~/.docker/",
1564
+ "*.pem",
1565
+ "*.key",
1566
+ "*.p12",
1567
+ "*.pfx",
1568
+ "*.tfstate",
1569
+ "*.tfstate.backup",
1570
+ ".terraform/",
1571
+ ".vercel/",
1572
+ ".netlify/",
1573
+ "firebase-adminsdk*.json",
1574
+ "serviceAccountKey.json",
1575
+ ".supabase/",
1576
+ "~/.netrc",
1577
+ "~/.npmrc",
1578
+ "~/.pypirc",
1579
+ "~/.git-credentials",
1580
+ ".git-credentials",
1581
+ // Remote URLs in the repo git config can embed access tokens, and .git
1582
+ // is hidden from grep/find but not from direct reads without this entry.
1583
+ ".git/config",
1584
+ // Clio's own provider secret store. The literal covers a repo-level file
1585
+ // of the same name on purpose; the expanded clioConfigDir() form is
1586
+ // appended at policy construction (policy-engine.ts), since this static
1587
+ // list cannot call config helpers.
1588
+ "credentials.yaml",
1589
+ "dump.sql",
1590
+ "backup.sql",
1591
+ "*.dump"
1592
+ ],
1593
+ readOnlyPaths: [
1594
+ "/etc/",
1595
+ "/usr/",
1596
+ "/bin/",
1597
+ "/sbin/",
1598
+ "/boot/",
1599
+ "/root/",
1600
+ "~/.bash_history",
1601
+ "~/.zsh_history",
1602
+ "~/.node_repl_history",
1603
+ "~/.bashrc",
1604
+ "~/.zshrc",
1605
+ "~/.profile",
1606
+ "~/.bash_profile",
1607
+ "*.min.js",
1608
+ "*.min.css",
1609
+ "*.bundle.js",
1610
+ "*.chunk.js",
1611
+ "dist/",
1612
+ "build/",
1613
+ ".next/",
1614
+ ".nuxt/",
1615
+ ".output/",
1616
+ "node_modules/",
1617
+ "__pycache__/",
1618
+ ".venv/",
1619
+ "venv/",
1620
+ "target/"
1621
+ ],
1622
+ noDeletePaths: [
1623
+ "~/.claude/",
1624
+ "CLAUDE.md",
1625
+ "LICENSE",
1626
+ "LICENSE.*",
1627
+ "COPYING",
1628
+ "COPYING.*",
1629
+ "NOTICE",
1630
+ "PATENTS",
1631
+ "README.md",
1632
+ "README.*"
1633
+ ]
1634
+ };
1635
+ function mergePathPolicyInputs(base, override) {
1636
+ return {
1637
+ zeroAccessPaths: [...base.zeroAccessPaths ?? [], ...override.zeroAccessPaths ?? []],
1638
+ readOnlyPaths: [...base.readOnlyPaths ?? [], ...override.readOnlyPaths ?? []],
1639
+ noDeletePaths: [...base.noDeletePaths ?? [], ...override.noDeletePaths ?? []]
1640
+ };
1641
+ }
1642
+
1643
+ // src/domains/safety/project-policy.ts
1644
+ import { createHash as createHash2 } from "node:crypto";
1645
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
1646
+ import path3 from "node:path";
1647
+ import { parse as parseYaml } from "yaml";
1648
+ var POLICY_RELATIVE_PATH = path3.join(".clio-coder", "safety.yaml");
1649
+ var ACTION_CLASSES = /* @__PURE__ */ new Set([
1650
+ "read",
1651
+ "write",
1652
+ "execute",
1653
+ "dispatch",
1654
+ "system_modify",
1655
+ "git_destructive",
1656
+ "unknown"
1657
+ ]);
1658
+ var PATH_POLICY_KEYS = ["zeroAccessPaths", "readOnlyPaths", "noDeletePaths"];
1659
+ var ROOT_KEYS = /* @__PURE__ */ new Set(["version", "commands", "tasks", "disableDefaultPathPolicy", ...PATH_POLICY_KEYS]);
1660
+ var COMMAND_KEYS = /* @__PURE__ */ new Set([
1661
+ "id",
1662
+ "command",
1663
+ "cwd",
1664
+ "timeoutMs",
1665
+ "maxOutputBytes",
1666
+ "actionClass",
1667
+ "shellOperators",
1668
+ "env",
1669
+ "requireConfirmation",
1670
+ "rationale",
1671
+ "owner",
1672
+ "comment"
1673
+ ]);
1674
+ var ENV_KEYS = /* @__PURE__ */ new Set(["mode", "allow"]);
1675
+ var ID_RE = /^[a-zA-Z0-9][a-zA-Z0-9_.:-]*$/;
1676
+ function projectSafetyPolicyPath(cwd = process.cwd()) {
1677
+ let cursor = path3.resolve(cwd);
1678
+ while (true) {
1679
+ const candidate = path3.join(cursor, POLICY_RELATIVE_PATH);
1680
+ if (existsSync3(candidate)) return candidate;
1681
+ const parent = path3.dirname(cursor);
1682
+ if (parent === cursor) return null;
1683
+ cursor = parent;
1684
+ }
1685
+ }
1686
+ function loadProjectSafetyPolicy(cwd = process.cwd()) {
1687
+ const policyPath = projectSafetyPolicyPath(cwd);
1688
+ if (policyPath === null) {
1689
+ return {
1690
+ path: null,
1691
+ hash: null,
1692
+ valid: true,
1693
+ errors: [],
1694
+ commands: [],
1695
+ pathPolicy: {},
1696
+ disableDefaultPathPolicy: false
1697
+ };
1698
+ }
1699
+ let raw;
1700
+ try {
1701
+ raw = readFileSync3(policyPath, "utf8");
1702
+ } catch (err) {
1703
+ return {
1704
+ path: policyPath,
1705
+ hash: null,
1706
+ valid: false,
1707
+ errors: [`cannot read project safety policy: ${err instanceof Error ? err.message : String(err)}`],
1708
+ commands: [],
1709
+ pathPolicy: {},
1710
+ disableDefaultPathPolicy: false
1711
+ };
1712
+ }
1713
+ const hash = sha256(raw);
1714
+ try {
1715
+ const parsed = parseYaml(raw);
1716
+ return validateProjectSafetyPolicy(parsed, policyPath, hash);
1717
+ } catch (err) {
1718
+ return {
1719
+ path: policyPath,
1720
+ hash,
1721
+ valid: false,
1722
+ errors: [`cannot parse project safety policy: ${err instanceof Error ? err.message : String(err)}`],
1723
+ commands: [],
1724
+ pathPolicy: {},
1725
+ disableDefaultPathPolicy: false
1726
+ };
1727
+ }
1728
+ }
1729
+ function validateProjectSafetyPolicy(value, policyPath, hash) {
1730
+ const errors = [];
1731
+ const commands = [];
1732
+ if (!isPlainRecord(value)) {
1733
+ return {
1734
+ path: policyPath,
1735
+ hash,
1736
+ valid: false,
1737
+ errors: ["policy root must be a mapping"],
1738
+ commands: [],
1739
+ pathPolicy: {},
1740
+ disableDefaultPathPolicy: false
1741
+ };
1742
+ }
1743
+ for (const key of Object.keys(value)) {
1744
+ if (!ROOT_KEYS.has(key)) errors.push(`unknown root key '${key}'`);
1745
+ }
1746
+ if (value.version !== 1) errors.push("version must be 1");
1747
+ appendCommandPolicies(commands, errors, value.commands, "commands");
1748
+ appendCommandPolicies(commands, errors, value.tasks, "tasks");
1749
+ const pathPolicy = parsePathPolicy(value, errors);
1750
+ const disableDefaultPathPolicy = value.disableDefaultPathPolicy === void 0 ? false : booleanField(value, "disableDefaultPathPolicy", "policy", errors);
1751
+ const ids = /* @__PURE__ */ new Set();
1752
+ for (const command of commands) {
1753
+ if (ids.has(command.id)) errors.push(`duplicate command id '${command.id}'`);
1754
+ ids.add(command.id);
1755
+ }
1756
+ return {
1757
+ path: policyPath,
1758
+ hash,
1759
+ valid: errors.length === 0,
1760
+ errors,
1761
+ commands: errors.length === 0 ? commands : [],
1762
+ pathPolicy: errors.length === 0 ? pathPolicy : {},
1763
+ disableDefaultPathPolicy: errors.length === 0 ? disableDefaultPathPolicy : false
1764
+ };
1765
+ }
1766
+ function parsePathPolicy(value, errors) {
1767
+ const out = {};
1768
+ for (const key of PATH_POLICY_KEYS) {
1769
+ const parsed = parsePathList(value[key], key, errors);
1770
+ if (parsed !== void 0) out[key] = parsed;
1771
+ }
1772
+ return out;
1773
+ }
1774
+ function parsePathList(value, key, errors) {
1775
+ if (value === void 0) return void 0;
1776
+ if (!Array.isArray(value)) {
1777
+ errors.push(`${key} must be an array`);
1778
+ return void 0;
1779
+ }
1780
+ const out = [];
1781
+ for (let index = 0; index < value.length; index += 1) {
1782
+ const item = value[index];
1783
+ const label = `${key}[${index}]`;
1784
+ if (typeof item !== "string" || item.trim().length === 0) {
1785
+ errors.push(`${label} must be a non-empty string`);
1786
+ continue;
1787
+ }
1788
+ const trimmed = item.trim();
1789
+ if (path3.isAbsolute(trimmed)) {
1790
+ errors.push(`${label} must be relative to the policy root`);
1791
+ continue;
1792
+ }
1793
+ const normalized = path3.normalize(trimmed);
1794
+ const segments = normalized.split(path3.sep).filter((segment) => segment.length > 0);
1795
+ if (segments.some((segment) => segment === "..")) {
1796
+ errors.push(`${label} must not escape the policy root with '..'`);
1797
+ continue;
1798
+ }
1799
+ out.push(trimmed);
1800
+ }
1801
+ return out;
1802
+ }
1803
+ function appendCommandPolicies(out, errors, value, field) {
1804
+ if (value === void 0) return;
1805
+ if (!Array.isArray(value)) {
1806
+ errors.push(`${field} must be an array`);
1807
+ return;
1808
+ }
1809
+ for (let index = 0; index < value.length; index += 1) {
1810
+ const parsed = parseCommandPolicy(value[index], `${field}[${index}]`);
1811
+ if (parsed.errors.length > 0) {
1812
+ errors.push(...parsed.errors);
1813
+ continue;
1814
+ }
1815
+ if (parsed.command !== void 0) out.push(parsed.command);
1816
+ }
1817
+ }
1818
+ function parseCommandPolicy(value, label) {
1819
+ const errors = [];
1820
+ if (!isPlainRecord(value)) return { errors: [`${label} must be a mapping`] };
1821
+ for (const key of Object.keys(value)) {
1822
+ if (!COMMAND_KEYS.has(key)) errors.push(`${label}: unknown key '${key}'`);
1823
+ }
1824
+ const id = stringField(value, "id", label, errors);
1825
+ const command = stringField(value, "command", label, errors);
1826
+ const cwd = optionalStringField(value, "cwd", label, errors);
1827
+ const timeoutMs = optionalPositiveInt(value, "timeoutMs", label, errors);
1828
+ const maxOutputBytes = optionalPositiveInt(value, "maxOutputBytes", label, errors);
1829
+ const actionClass = parseActionClass(value.actionClass, label, errors);
1830
+ const shellOperators = parseShellOperators(value.shellOperators, label, errors);
1831
+ const env = parseEnvPolicy(value.env, label, errors);
1832
+ const requireConfirmation = value.requireConfirmation === void 0 ? false : booleanField(value, "requireConfirmation", label, errors);
1833
+ const rationale = optionalStringField(value, "rationale", label, errors);
1834
+ const owner = optionalStringField(value, "owner", label, errors);
1835
+ const comment = optionalStringField(value, "comment", label, errors);
1836
+ if (id && !ID_RE.test(id)) errors.push(`${label}.id must match ${ID_RE}`);
1837
+ if (command && command.trim().length === 0) errors.push(`${label}.command must not be empty`);
1838
+ if (cwd !== void 0) {
1839
+ if (path3.isAbsolute(cwd)) {
1840
+ errors.push(`${label}.cwd must be relative to the policy root`);
1841
+ } else {
1842
+ const normalized = path3.normalize(cwd);
1843
+ const segments = normalized.split(path3.sep).filter((segment) => segment.length > 0);
1844
+ if (segments.some((segment) => segment === "..")) {
1845
+ errors.push(`${label}.cwd must not escape the policy root with '..'`);
1846
+ }
1847
+ }
1848
+ }
1849
+ if (errors.length > 0 || id === void 0 || command === void 0 || actionClass === void 0 || env === void 0) {
1850
+ return { errors };
1851
+ }
1852
+ const parsed = {
1853
+ id,
1854
+ command,
1855
+ actionClass,
1856
+ shellOperators,
1857
+ env,
1858
+ requireConfirmation
1859
+ };
1860
+ if (cwd !== void 0) parsed.cwd = cwd;
1861
+ if (timeoutMs !== void 0) parsed.timeoutMs = timeoutMs;
1862
+ if (maxOutputBytes !== void 0) parsed.maxOutputBytes = maxOutputBytes;
1863
+ if (rationale !== void 0) parsed.rationale = rationale;
1864
+ if (owner !== void 0) parsed.owner = owner;
1865
+ if (comment !== void 0) parsed.comment = comment;
1866
+ return { command: parsed, errors: [] };
1867
+ }
1868
+ function parseActionClass(value, label, errors) {
1869
+ if (typeof value !== "string") {
1870
+ errors.push(`${label}.actionClass must be a string`);
1871
+ return void 0;
1872
+ }
1873
+ if (!ACTION_CLASSES.has(value)) {
1874
+ errors.push(`${label}.actionClass is not supported: ${value}`);
1875
+ return void 0;
1876
+ }
1877
+ return value;
1878
+ }
1879
+ function parseShellOperators(value, label, errors) {
1880
+ if (value === void 0) return "deny";
1881
+ if (value === "deny" || value === "allow") return value;
1882
+ errors.push(`${label}.shellOperators must be 'deny' or 'allow'`);
1883
+ return "deny";
1884
+ }
1885
+ function parseEnvPolicy(value, label, errors) {
1886
+ if (value === void 0) return { mode: "none", allow: [] };
1887
+ if (!isPlainRecord(value)) {
1888
+ errors.push(`${label}.env must be a mapping`);
1889
+ return void 0;
1890
+ }
1891
+ for (const key of Object.keys(value)) {
1892
+ if (!ENV_KEYS.has(key)) errors.push(`${label}.env: unknown key '${key}'`);
1893
+ }
1894
+ const mode = value.mode;
1895
+ if (mode !== "none" && mode !== "allowlist") {
1896
+ errors.push(`${label}.env.mode must be 'none' or 'allowlist'`);
1897
+ return void 0;
1898
+ }
1899
+ const allow = value.allow;
1900
+ if (allow === void 0) return { mode, allow: [] };
1901
+ if (!Array.isArray(allow) || allow.some((entry) => typeof entry !== "string" || entry.length === 0)) {
1902
+ errors.push(`${label}.env.allow must be an array of non-empty strings`);
1903
+ return void 0;
1904
+ }
1905
+ if (mode === "none" && allow.length > 0) {
1906
+ errors.push(`${label}.env.allow requires env.mode='allowlist'`);
1907
+ return void 0;
1908
+ }
1909
+ return { mode, allow: [...allow] };
1910
+ }
1911
+ function stringField(record, key, label, errors) {
1912
+ const value = record[key];
1913
+ if (typeof value !== "string" || value.length === 0) {
1914
+ errors.push(`${label}.${key} must be a non-empty string`);
1915
+ return void 0;
1916
+ }
1917
+ return value;
1918
+ }
1919
+ function optionalStringField(record, key, label, errors) {
1920
+ const value = record[key];
1921
+ if (value === void 0) return void 0;
1922
+ if (typeof value !== "string" || value.length === 0) {
1923
+ errors.push(`${label}.${key} must be a non-empty string when present`);
1924
+ return void 0;
1925
+ }
1926
+ return value;
1927
+ }
1928
+ function optionalPositiveInt(record, key, label, errors) {
1929
+ const value = record[key];
1930
+ if (value === void 0) return void 0;
1931
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) {
1932
+ errors.push(`${label}.${key} must be a positive integer`);
1933
+ return void 0;
1934
+ }
1935
+ return value;
1936
+ }
1937
+ function booleanField(record, key, label, errors) {
1938
+ const value = record[key];
1939
+ if (typeof value === "boolean") return value;
1940
+ errors.push(`${label}.${key} must be a boolean`);
1941
+ return false;
1942
+ }
1943
+ function isPlainRecord(value) {
1944
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1945
+ }
1946
+ function sha256(raw) {
1947
+ return createHash2("sha256").update(raw, "utf8").digest("hex");
1948
+ }
1949
+
1950
+ // src/domains/safety/rejection-feedback.ts
1951
+ var HARD_BLOCK_CLASSES = ["git_destructive"];
1952
+ function buildShort(ctx) {
1953
+ const postureSuffix = ctx.posture ? ` in ${ctx.posture}` : "";
1954
+ return `${ctx.tool} blocked: ${ctx.actionClass}${postureSuffix}`;
1955
+ }
1956
+ function buildDetail(ctx) {
1957
+ const lines = [`Clio refused to run ${ctx.tool}.`];
1958
+ for (const reason of ctx.reasons) {
1959
+ lines.push(`- ${reason}`);
1960
+ }
1961
+ if (ctx.ruleId) {
1962
+ lines.push(`rule: ${ctx.ruleId}`);
1963
+ }
1964
+ return lines.join("\n");
1965
+ }
1966
+ function buildHints(ctx) {
1967
+ const hints = [];
1968
+ const hardBlock = HARD_BLOCK_CLASSES.includes(ctx.actionClass) || ctx.ruleId !== void 0;
1969
+ if (hardBlock) {
1970
+ hints.push("This is a hard block; confirmation cannot override it.");
1971
+ return hints;
1972
+ }
1973
+ if (ctx.actionClass === "system_modify") {
1974
+ hints.push("Operator confirmation is required for this action.");
1975
+ }
1976
+ return hints;
1977
+ }
1978
+ function formatRejection(ctx) {
1979
+ return {
1980
+ short: buildShort(ctx),
1981
+ detail: buildDetail(ctx),
1982
+ hints: buildHints(ctx)
1983
+ };
1984
+ }
1985
+ var MODEL_REJECTION_PIVOT = "Do not retry this action through another tool unless a hint above names one; pivot or report the blocker.";
1986
+ var MODEL_REJECTION_MAX_LINES = 16;
1987
+ var MODEL_REJECTION_MAX_LINE_CHARS = 300;
1988
+ function formatModelRejection(reason, rejection) {
1989
+ const lines = [];
1990
+ const seen = /* @__PURE__ */ new Set();
1991
+ const push = (line) => {
1992
+ const bounded = line.length > MODEL_REJECTION_MAX_LINE_CHARS ? `${line.slice(0, MODEL_REJECTION_MAX_LINE_CHARS)}\u2026` : line;
1993
+ const trimmed = bounded.trim();
1994
+ if (trimmed.length === 0) return;
1995
+ const normalized = trimmed.replace(/^-\s+/, "");
1996
+ if (seen.has(normalized) || normalized === MODEL_REJECTION_PIVOT) return;
1997
+ seen.add(normalized);
1998
+ lines.push(trimmed);
1999
+ };
2000
+ for (const line of reason.split("\n")) push(line);
2001
+ if (rejection) {
2002
+ for (const line of rejection.detail.split("\n")) push(line);
2003
+ for (const hint of rejection.hints) push(hint);
2004
+ }
2005
+ const bodyBudget = MODEL_REJECTION_MAX_LINES - 1;
2006
+ const body = lines.length > bodyBudget ? [...lines.slice(0, bodyBudget - 1), "(further detail elided)"] : lines;
2007
+ return [...body, MODEL_REJECTION_PIVOT].join("\n");
2008
+ }
2009
+
2010
+ // src/domains/safety/rule-pack-loader.ts
2011
+ import { readFileSync as readFileSync4 } from "node:fs";
2012
+ import { join as join3 } from "node:path";
2013
+ import { parse as parseYaml2 } from "yaml";
2014
+
2015
+ // src/domains/safety/rule-compiler.ts
2016
+ function asString(value, ruleId, field) {
2017
+ if (typeof value !== "string" || value.length === 0) {
2018
+ throw new Error(`damage-control rule '${ruleId}': expected string for ${field}`);
2019
+ }
2020
+ return value;
2021
+ }
2022
+ function compileDamageControlRule(raw, index) {
2023
+ if (typeof raw.id !== "string" || raw.id.length === 0) {
2024
+ throw new Error(`damage-control rule at index ${index}: missing or non-string 'id'`);
2025
+ }
2026
+ const id = raw.id;
2027
+ const description = asString(raw.description, id, "description");
2028
+ const patternString = asString(raw.pattern, id, "pattern");
2029
+ const klass = asString(raw.class, id, "class");
2030
+ if (typeof raw.block !== "boolean") {
2031
+ throw new Error(`damage-control rule '${id}': expected boolean for block`);
2032
+ }
2033
+ if (raw.ask !== void 0 && typeof raw.ask !== "boolean") {
2034
+ throw new Error(`damage-control rule '${id}': expected boolean for ask`);
2035
+ }
2036
+ let pattern;
2037
+ try {
2038
+ pattern = new RegExp(patternString, "i");
2039
+ } catch (err) {
2040
+ const msg = err instanceof Error ? err.message : String(err);
2041
+ throw new Error(`damage-control rule '${id}': invalid pattern: ${msg}`);
2042
+ }
2043
+ return {
2044
+ id,
2045
+ description,
2046
+ pattern,
2047
+ class: klass,
2048
+ block: raw.block,
2049
+ ...raw.ask !== void 0 ? { ask: raw.ask } : {}
2050
+ };
2051
+ }
2052
+
2053
+ // src/domains/safety/rule-pack-loader.ts
2054
+ function compilePackRules(rawRules, packId) {
2055
+ if (rawRules === void 0 || rawRules === null) return [];
2056
+ if (!Array.isArray(rawRules)) {
2057
+ throw new Error(`damage-control pack '${packId}': expected array at 'rules'`);
2058
+ }
2059
+ return rawRules.map((rule, index) => compileDamageControlRule(rule, index));
2060
+ }
2061
+ function emptyRuleset(version) {
2062
+ return { version, rules: [] };
2063
+ }
2064
+ function loadRulePacks(yamlPath) {
2065
+ const raw = readFileSync4(yamlPath, "utf8");
2066
+ const parsed = parseYaml2(raw);
2067
+ if (!parsed || typeof parsed !== "object") {
2068
+ throw new Error(`damage-control rules at ${yamlPath}: expected mapping at document root`);
2069
+ }
2070
+ const version = typeof parsed.version === "number" ? parsed.version : 0;
2071
+ if (version === 1) {
2072
+ const baseRules = compilePackRules(parsed.rules, "base");
2073
+ return {
2074
+ base: { version: 1, rules: baseRules }
2075
+ };
2076
+ }
2077
+ if (version !== 2) {
2078
+ throw new Error(`damage-control rules at ${yamlPath}: unsupported version ${String(parsed.version)}`);
2079
+ }
2080
+ if (!Array.isArray(parsed.packs)) {
2081
+ throw new Error(`damage-control rules at ${yamlPath}: expected array at 'packs'`);
2082
+ }
2083
+ const out = {
2084
+ base: emptyRuleset(2)
2085
+ };
2086
+ for (const rawPack of parsed.packs) {
2087
+ const packId = rawPack.id;
2088
+ if (packId !== "base") {
2089
+ throw new Error(`damage-control rules at ${yamlPath}: unknown pack id ${String(packId)}`);
2090
+ }
2091
+ const rules = compilePackRules(rawPack.rules, packId);
2092
+ out[packId] = { version: 2, rules };
2093
+ }
2094
+ return out;
2095
+ }
2096
+ function loadDefaultRulePacks() {
2097
+ return loadRulePacks(join3(resolvePackageRoot(), "damage-control-rules.yaml"));
2098
+ }
2099
+ var cachedPacks = null;
2100
+ function getCachedDefaultRulePacks() {
2101
+ if (cachedPacks === null) cachedPacks = loadDefaultRulePacks();
2102
+ return cachedPacks;
2103
+ }
2104
+
2105
+ // src/domains/safety/policy-engine.ts
2106
+ var BUILTIN_ALLOWLIST = [
2107
+ { id: "builtin:pwd", re: /^pwd$/ },
2108
+ { id: "builtin:ls", re: /^ls(?:\s+(-[A-Za-z0-9]+|\.[/\w.-]*|[/\w.-]+))*$/ },
2109
+ { id: "builtin:git-status", re: /^git\s+status(?:\s+--short|\s+--branch|\s+-sb)*$/ },
2110
+ { id: "builtin:git-diff", re: /^git\s+diff(?:\s+--cached|\s+--stat|\s+--name-only|\s+--\s+[\w./-]+)*$/ },
2111
+ { id: "builtin:git-log", re: /^git\s+log\s+--oneline(?:\s+-n\s+[1-9]\d{0,2})?(?:\s+--\s+[\w./-]+)?$/ },
2112
+ { id: "builtin:npm-test", re: /^npm\s+(?:test|run\s+test)(?:\s+--\s+[\w=./:-]+(?:\s+[\w=./:-]+)*)?$/ },
2113
+ { id: "builtin:npm-lint", re: /^npm\s+run\s+lint(?:\s+--\s+[\w=./:-]+(?:\s+[\w=./:-]+)*)?$/ },
2114
+ { id: "builtin:npm-build", re: /^npm\s+run\s+build(?:\s+--\s+[\w=./:-]+(?:\s+[\w=./:-]+)*)?$/ },
2115
+ { id: "builtin:npm-typecheck", re: /^npm\s+run\s+typecheck(?:\s+--\s+[\w=./:-]+(?:\s+[\w=./:-]+)*)?$/ },
2116
+ { id: "builtin:npm-ci-script", re: /^npm\s+run\s+ci(?:\s+--\s+[\w=./:-]+(?:\s+[\w=./:-]+)*)?$/ },
2117
+ { id: "builtin:pytest", re: /^pytest(?:\s+[\w=./:-]+)*$/ },
2118
+ { id: "builtin:python-pytest", re: /^python(?:3(?:\.\d+)?)?\s+-m\s+pytest(?:\s+[\w=./:-]+)*$/ },
2119
+ { id: "builtin:cargo-test", re: /^cargo\s+test(?:\s+[\w=./:-]+)*$/ },
2120
+ { id: "builtin:go-test", re: /^go\s+test(?:\s+[\w=./:-]+)*$/ },
2121
+ { id: "builtin:make-test", re: /^make\s+test(?:\s+[\w=./:-]+)*$/ }
2122
+ ];
2123
+ var BASH_RECOGNIZED_FORM_HINT = "Recognized forms run without asking: one command per bash call, or a && chain whose every step is recognized, with cwd passed as the cwd argument instead of a leading cd.";
2124
+ var EXECUTION_TOOLS = /* @__PURE__ */ new Set([ToolNames.Bash, ToolNames.Verify]);
2125
+ var WRITE_ROOT_REFUSED_TOOLS = /* @__PURE__ */ new Set([
2126
+ ToolNames.Bash,
2127
+ ToolNames.Verify,
2128
+ ToolNames.Dispatch
2129
+ ]);
2130
+ var WRITE_ROOT_TOOLS = /* @__PURE__ */ new Set([ToolNames.Write, ToolNames.Edit, ToolNames.Artifact]);
2131
+ function writeRootTargetPath(call) {
2132
+ if (!WRITE_ROOT_TOOLS.has(call.tool)) return null;
2133
+ const target = pathArg(call.args);
2134
+ if (target !== null) return target;
2135
+ if (call.tool === ToolNames.Artifact) {
2136
+ const kind = call.args?.kind;
2137
+ return kind === "review" ? "REVIEW.md" : kind === "report" ? "REPORT.md" : "PLAN.md";
2138
+ }
2139
+ return null;
2140
+ }
2141
+ function isWriteConfinementEscape(call, actionClass) {
2142
+ if (WRITE_ROOT_REFUSED_TOOLS.has(call.tool)) return true;
2143
+ return actionClass === "execute" || actionClass === "dispatch";
2144
+ }
2145
+ function evaluateWriteRoots(roots, writeRootCwd, call) {
2146
+ if (roots.length === 0) return null;
2147
+ const target = writeRootTargetPath(call);
2148
+ if (target === null) return null;
2149
+ const resolved = path4.resolve(writeRootCwd, target);
2150
+ for (const root of roots) {
2151
+ if (resolved === root || resolved.startsWith(`${root}${path4.sep}`)) return null;
2152
+ }
2153
+ return `write target '${target}' resolves to '${resolved}', which is outside the permitted write roots for this run: ${roots.join(", ")}`;
2154
+ }
2155
+ function createSafetyPolicyEngine(options = {}) {
2156
+ const cwd = canonicalizeExistingPath(path4.resolve(options.cwd ?? process.cwd()));
2157
+ const writeRootCwd = path4.resolve(options.cwd ?? process.cwd());
2158
+ const writeRoots = (options.writeRoots ?? []).map((root) => path4.resolve(root));
2159
+ const packs = options.rulePacks ?? getCachedDefaultRulePacks();
2160
+ const projectPolicy = options.projectPolicy ?? loadProjectSafetyPolicy(cwd);
2161
+ const projectPolicyRoot = projectPolicy.path === null ? cwd : path4.dirname(path4.dirname(canonicalizeExistingPath(projectPolicy.path)));
2162
+ const pathPolicyInput = projectPolicy.disableDefaultPathPolicy ? projectPolicy.pathPolicy : mergePathPolicyInputs(DEFAULT_DAMAGE_CONTROL_PATH_POLICY, projectPolicy.pathPolicy);
2163
+ const expandedDefaults = projectPolicy.disableDefaultPathPolicy ? pathPolicyInput : mergePathPolicyInputs(pathPolicyInput, { zeroAccessPaths: clioCredentialStorePaths() });
2164
+ const pathPolicy = compilePathPolicy(expandedDefaults, projectPolicyRoot);
2165
+ const zeroAccessPolicy = {
2166
+ root: pathPolicy.root,
2167
+ entries: pathPolicy.entries.filter((entry) => entry.kind === "zeroAccessPaths"),
2168
+ diagnostics: []
2169
+ };
2170
+ const sourcedRules = packs.base.rules.map((rule) => ({ rule, source: "damage-control:base" }));
2171
+ return {
2172
+ evaluate(call, posture) {
2173
+ const rawClassification = classify(call);
2174
+ const command = commandArg(call.args);
2175
+ const callCwd = cwdArg(call.args, cwd);
2176
+ const scan = damageControlScan(call);
2177
+ const hit = scan ? matchSourcedRule(scan, sourcedRules) : null;
2178
+ const classification = effectiveClassification(rawClassification, hit?.match);
2179
+ const base = baseDecision(call, classification, callCwd, posture, command);
2180
+ const writeRootReason = evaluateWriteRoots(writeRoots, writeRootCwd, call);
2181
+ if (writeRootReason !== null) {
2182
+ return blockDecision(base, {
2183
+ ruleId: "write-root",
2184
+ reasonCode: "write-root",
2185
+ reasons: [writeRootReason],
2186
+ policySource: "builtin-classifier"
2187
+ });
2188
+ }
2189
+ if (writeRoots.length > 0 && isWriteConfinementEscape(call, classification.actionClass)) {
2190
+ return blockDecision(base, {
2191
+ ruleId: "write-root",
2192
+ reasonCode: "write-root",
2193
+ reasons: [
2194
+ `tool '${call.tool}' (${classification.actionClass}) can mutate the filesystem outside the permitted write roots and is blocked under write-root confinement`
2195
+ ],
2196
+ policySource: "builtin-classifier"
2197
+ });
2198
+ }
2199
+ const askRule = hit?.match.ask === true && hit.match.block !== true;
2200
+ if (!askRule && (classification.actionClass === "git_destructive" || hit?.match.actionClass === "git_destructive" || hit?.match.block === true)) {
2201
+ const blockInput = {
2202
+ reasonCode: hit ? `damage-control:${hit.match.ruleId}` : "classification:git_destructive",
2203
+ reasons: [...classification.reasons, ...hit ? [hit.match.reason] : []],
2204
+ policySource: hit?.source ?? "damage-control:base"
2205
+ };
2206
+ if (hit?.match.ruleId !== void 0) blockInput.ruleId = hit.match.ruleId;
2207
+ if (hit?.match !== void 0) blockInput.match = hit.match;
2208
+ return blockDecision(base, blockInput);
2209
+ }
2210
+ if (!projectPolicy.valid && EXECUTION_TOOLS.has(call.tool)) {
2211
+ const blockInput = {
2212
+ ruleId: "project-policy-invalid",
2213
+ reasonCode: "project-policy-invalid",
2214
+ reasons: [`project safety policy is invalid and execution fails closed: ${projectPolicy.errors.join("; ")}`],
2215
+ policySource: "project-policy-invalid"
2216
+ };
2217
+ if (projectPolicy.hash !== null) blockInput.policyHash = projectPolicy.hash;
2218
+ if (projectPolicy.path !== null) blockInput.projectPolicyPath = projectPolicy.path;
2219
+ return blockDecision(base, blockInput);
2220
+ }
2221
+ const pathBlock = evaluateProjectPathPolicy(pathPolicy, call, callCwd);
2222
+ if (pathBlock !== null) {
2223
+ const blockInput = {
2224
+ ruleId: pathBlock.reasonCode,
2225
+ reasonCode: pathBlock.reasonCode,
2226
+ reasons: [pathBlock.reason],
2227
+ policySource: "project-policy"
2228
+ };
2229
+ if (projectPolicy.hash !== null) blockInput.policyHash = projectPolicy.hash;
2230
+ if (projectPolicy.path !== null) blockInput.projectPolicyPath = projectPolicy.path;
2231
+ return blockDecision(base, blockInput);
2232
+ }
2233
+ if (call.tool === ToolNames.Bash && command !== null) {
2234
+ const secretRead = evaluateBashZeroAccessRead(zeroAccessPolicy, command, callCwd);
2235
+ if (secretRead !== null) {
2236
+ const blockInput = {
2237
+ ruleId: "secret_path_bash",
2238
+ reasonCode: "secret_path_bash",
2239
+ reasons: [
2240
+ `bash read of zero-access path blocked: '${secretRead.token}' matches ${secretRead.entrySource}. Check presence with exit codes only (grep -sq "^NAME=" <file>); have the user supply values through their own terminal (read -s), never through chat or command output.`
2241
+ ],
2242
+ policySource: "project-policy"
2243
+ };
2244
+ if (projectPolicy.hash !== null) blockInput.policyHash = projectPolicy.hash;
2245
+ if (projectPolicy.path !== null) blockInput.projectPolicyPath = projectPolicy.path;
2246
+ return blockDecision(base, blockInput);
2247
+ }
2248
+ }
2249
+ if (askRule && hit !== null && posture !== "confirmed") {
2250
+ return askDecision(base, {
2251
+ ruleId: hit.match.ruleId,
2252
+ reasonCode: `damage-control:${hit.match.ruleId}`,
2253
+ reasons: [...classification.reasons, hit.match.reason, "damage-control rule requires confirmation"],
2254
+ policySource: hit.source,
2255
+ match: hit.match
2256
+ });
2257
+ }
2258
+ if (askRule && hit !== null && posture === "confirmed") {
2259
+ return allowDecision(base, {
2260
+ ruleId: hit.match.ruleId,
2261
+ reasonCode: `damage-control:${hit.match.ruleId}`,
2262
+ reasons: [...classification.reasons, hit.match.reason, "damage-control rule confirmed by operator"],
2263
+ policySource: hit.source,
2264
+ match: hit.match
2265
+ });
2266
+ }
2267
+ if (classification.actionClass === "system_modify") {
2268
+ const input = {
2269
+ ruleId: "system-modify-confirm",
2270
+ reasonCode: "system-modify-confirm",
2271
+ reasons: [...classification.reasons, "system-level changes require one-shot confirmation at every autonomy level"],
2272
+ policySource: "builtin-classifier"
2273
+ };
2274
+ return posture === "confirmed" ? allowDecision(base, input) : askDecision(base, input);
2275
+ }
2276
+ if (call.tool === ToolNames.Bash && classification.actionClass === "execute") {
2277
+ const bash = evaluateBashPolicy(command ?? "", callCwd, cwd, posture, projectPolicy);
2278
+ if (bash.kind === "block") return blockDecision(base, bash);
2279
+ if (bash.kind === "ask") return askDecision(base, bash);
2280
+ return allowDecision(base, bash);
2281
+ }
2282
+ const allowInput = {
2283
+ reasonCode: "allowed",
2284
+ reasons: classification.reasons,
2285
+ policySource: hit?.source ?? "none"
2286
+ };
2287
+ if (hit?.match.ruleId !== void 0) allowInput.ruleId = hit.match.ruleId;
2288
+ if (hit?.match !== void 0) allowInput.match = hit.match;
2289
+ if (classification.actionClass === "execute") allowInput.execRecognition = "recognized";
2290
+ return allowDecision(base, allowInput);
2291
+ },
2292
+ metadata() {
2293
+ return {
2294
+ version: 1,
2295
+ rulePackHash: rulePackHash(packs),
2296
+ rulePackVersion: packs.base.version,
2297
+ activeRuleIds: sourcedRules.map((entry) => entry.rule.id),
2298
+ projectPolicyPath: projectPolicy.path,
2299
+ projectPolicyHash: projectPolicy.hash,
2300
+ projectPolicyValid: projectPolicy.valid,
2301
+ projectPolicyErrors: [...projectPolicy.errors, ...pathPolicy.diagnostics],
2302
+ cwd
2303
+ };
2304
+ }
2305
+ };
2306
+ }
2307
+ function clioCredentialStorePaths() {
2308
+ try {
2309
+ return [path4.join(clioConfigDir(), "credentials.yaml")];
2310
+ } catch {
2311
+ return [];
2312
+ }
2313
+ }
2314
+ var SAFE_PRESENCE_RE = /^\s*grep\s+(?:-(?:q|sq|qs)\s+)('[^']*'|"[^"]*"|\S+)\s+\S+\s*$/;
2315
+ function isSafePresenceCheck(command) {
2316
+ const match = SAFE_PRESENCE_RE.exec(command);
2317
+ if (!match || match[1] === void 0) return false;
2318
+ const pattern = stripQuotes(match[1]);
2319
+ return /^\^[A-Za-z_][A-Za-z0-9_]*=/.test(pattern);
2320
+ }
2321
+ function stripQuotes(token) {
2322
+ if (token.length >= 2) {
2323
+ const first = token[0];
2324
+ const last = token[token.length - 1];
2325
+ if (first === '"' && last === '"' || first === "'" && last === "'") return token.slice(1, -1);
2326
+ }
2327
+ return token;
2328
+ }
2329
+ function bashPathTokenCandidates(command) {
2330
+ const candidates = [];
2331
+ for (const token of tokenizeShellLike(command)) {
2332
+ if (token.length === 0) continue;
2333
+ if (token === ";" || token === "&&" || token === "||" || token === "|" || token === ">" || token === ">>") continue;
2334
+ if (token === "<" || token === "<<") continue;
2335
+ if (/\s/.test(token)) continue;
2336
+ if (token.startsWith("-") && !token.includes("/") && !token.includes("=")) continue;
2337
+ candidates.push(token);
2338
+ const eq = token.indexOf("=");
2339
+ if (eq > 0 && eq < token.length - 1) candidates.push(token.slice(eq + 1));
2340
+ }
2341
+ return candidates;
2342
+ }
2343
+ function evaluateBashZeroAccessRead(zeroAccessPolicy, command, callCwd) {
2344
+ if (zeroAccessPolicy.entries.length === 0) return null;
2345
+ if (isSafePresenceCheck(command)) return null;
2346
+ for (const token of bashPathTokenCandidates(command)) {
2347
+ const decision = evaluatePathPolicy(zeroAccessPolicy, "read", token, callCwd);
2348
+ if (decision.kind === "block") {
2349
+ return { token, entrySource: `zeroAccessPaths entry ${decision.matchedPath}` };
2350
+ }
2351
+ }
2352
+ return null;
2353
+ }
2354
+ function evaluateProjectPathPolicy(policy, call, callCwd) {
2355
+ if (policy.entries.length === 0) return null;
2356
+ for (const target of pathPolicyTargets(call)) {
2357
+ const decision = evaluatePathPolicy(policy, target.operation, target.path, callCwd);
2358
+ if (decision.kind === "block") return decision;
2359
+ }
2360
+ return null;
2361
+ }
2362
+ function pathPolicyTargets(call) {
2363
+ const args = call.args;
2364
+ switch (call.tool) {
2365
+ case ToolNames.Read:
2366
+ case ToolNames.Ls:
2367
+ case ToolNames.Grep:
2368
+ case ToolNames.Find: {
2369
+ const target = pathArg(args) ?? ".";
2370
+ return [{ operation: "read", path: target }];
2371
+ }
2372
+ case ToolNames.Write:
2373
+ case ToolNames.Edit: {
2374
+ const target = pathArg(args);
2375
+ return target === null ? [] : [{ operation: "write", path: target }];
2376
+ }
2377
+ case ToolNames.Artifact: {
2378
+ const fallback = args?.kind === "review" ? "REVIEW.md" : args?.kind === "report" ? "REPORT.md" : "PLAN.md";
2379
+ return [{ operation: "write", path: pathArg(args) ?? fallback }];
2380
+ }
2381
+ case ToolNames.CredentialPresent:
2382
+ return [];
2383
+ case ToolNames.Bash: {
2384
+ const command = commandArg(args);
2385
+ if (command === null) return [];
2386
+ return [
2387
+ ...extractCommandWriteTargets(command).map((target) => ({ operation: "write", path: target })),
2388
+ ...extractCommandDeleteTargets(command).map((target) => ({ operation: "delete", path: target }))
2389
+ ];
2390
+ }
2391
+ default:
2392
+ return [];
2393
+ }
2394
+ }
2395
+ function evaluateBashPolicy(command, callCwd, workspaceRoot, posture, policy) {
2396
+ if (command.trim().length === 0) {
2397
+ return {
2398
+ kind: "block",
2399
+ ruleId: "bash-empty-command",
2400
+ reasonCode: "bash-empty-command",
2401
+ reasons: ["bash command must not be empty"],
2402
+ policySource: "builtin-command-allowlist"
2403
+ };
2404
+ }
2405
+ const projectMatch = matchingProjectCommand(policy, command, callCwd);
2406
+ if (projectMatch) {
2407
+ const base = {
2408
+ ruleId: projectMatch.id,
2409
+ reasonCode: `project-policy:${projectMatch.id}`,
2410
+ reasons: [`allowed by project safety policy command '${projectMatch.id}'`],
2411
+ policySource: "project-policy",
2412
+ execRecognition: "recognized"
2413
+ };
2414
+ if (policy.hash !== null) base.policyHash = policy.hash;
2415
+ if (policy.path !== null) base.projectPolicyPath = policy.path;
2416
+ if (projectMatch.requireConfirmation && posture !== "confirmed") {
2417
+ return {
2418
+ ...base,
2419
+ kind: "ask",
2420
+ reasons: [...base.reasons, "project policy requires confirmation"]
2421
+ };
2422
+ }
2423
+ return { ...base, kind: "allow" };
2424
+ }
2425
+ if (!isUnderOrSame(callCwd, workspaceRoot)) {
2426
+ return {
2427
+ kind: "block",
2428
+ ruleId: "bash-cwd-escape",
2429
+ reasonCode: "bash-cwd-escape",
2430
+ reasons: [
2431
+ `bash cwd '${callCwd}' escapes workspace root '${workspaceRoot}'; use a typed tool or a project policy entry with explicit cwd`
2432
+ ],
2433
+ policySource: "builtin-command-allowlist"
2434
+ };
2435
+ }
2436
+ const recognitionCommand = inlineShellScript(command) ?? command;
2437
+ if (hasCommandSubstitution(recognitionCommand) && posture !== "confirmed") {
2438
+ return {
2439
+ kind: "ask",
2440
+ ruleId: "bash-command-substitution",
2441
+ reasonCode: "bash-command-substitution",
2442
+ reasons: ["command substitution hides the executed content from the safety net and requires one-shot confirmation"],
2443
+ policySource: "builtin-command-allowlist",
2444
+ execRecognition: "unrecognized"
2445
+ };
2446
+ }
2447
+ const chain = recognizeCommandChain(recognitionCommand, callCwd, workspaceRoot, policy);
2448
+ if (chain !== null) {
2449
+ const chainReasons = [`every step of the && chain is recognized: ${chain.ruleIds.join(", ")}`];
2450
+ if (chain.requiresConfirmation && posture !== "confirmed") {
2451
+ return {
2452
+ kind: "ask",
2453
+ ruleId: "bash-recognized-chain",
2454
+ reasonCode: "bash-recognized-chain",
2455
+ reasons: [...chainReasons, "project policy requires confirmation for one step"],
2456
+ policySource: "builtin-command-allowlist",
2457
+ execRecognition: "recognized"
2458
+ };
2459
+ }
2460
+ return {
2461
+ kind: "allow",
2462
+ ruleId: "bash-recognized-chain",
2463
+ reasonCode: "bash-recognized-chain",
2464
+ reasons: chainReasons,
2465
+ policySource: "builtin-command-allowlist",
2466
+ execRecognition: "recognized"
2467
+ };
2468
+ }
2469
+ if (hasSequencingOperators(command)) {
2470
+ return {
2471
+ kind: "allow",
2472
+ ruleId: "bash-shell-operators",
2473
+ reasonCode: "bash-shell-operators",
2474
+ reasons: [
2475
+ "shell operators defeat per-command recognition; the autonomy level decides admission",
2476
+ BASH_RECOGNIZED_FORM_HINT
2477
+ ],
2478
+ policySource: "builtin-command-allowlist",
2479
+ execRecognition: "unrecognized"
2480
+ };
2481
+ }
2482
+ for (const entry of BUILTIN_ALLOWLIST) {
2483
+ if (entry.re.test(recognitionCommand)) {
2484
+ return {
2485
+ kind: "allow",
2486
+ ruleId: entry.id,
2487
+ reasonCode: entry.id,
2488
+ reasons: [`matched built-in no-prompt command allowlist '${entry.id}'`],
2489
+ policySource: "builtin-command-allowlist",
2490
+ execRecognition: "recognized"
2491
+ };
2492
+ }
2493
+ }
2494
+ return {
2495
+ kind: "allow",
2496
+ ruleId: "bash-unrecognized",
2497
+ reasonCode: "bash-unrecognized",
2498
+ reasons: [
2499
+ "bash command is outside the no-prompt set; the autonomy level decides admission",
2500
+ BASH_RECOGNIZED_FORM_HINT
2501
+ ],
2502
+ policySource: "builtin-command-allowlist",
2503
+ execRecognition: "unrecognized"
2504
+ };
2505
+ }
2506
+ function baseDecision(call, classification, cwd, posture, command) {
2507
+ const out = {
2508
+ classification,
2509
+ tool: call.tool,
2510
+ actionClass: classification.actionClass,
2511
+ cwd
2512
+ };
2513
+ if (posture !== void 0) out.posture = posture;
2514
+ if (command !== null) out.command = command;
2515
+ return out;
2516
+ }
2517
+ function allowDecision(base, input) {
2518
+ return { ...base, ...input, kind: "allow" };
2519
+ }
2520
+ function askDecision(base, input) {
2521
+ const rejectionInput = {
2522
+ tool: base.tool,
2523
+ actionClass: base.classification.actionClass,
2524
+ reasons: input.reasons
2525
+ };
2526
+ if (base.posture !== void 0) rejectionInput.posture = base.posture;
2527
+ if (input.ruleId !== void 0) rejectionInput.ruleId = input.ruleId;
2528
+ const rejection = formatRejection(rejectionInput);
2529
+ return { ...base, ...input, kind: "ask", rejection };
2530
+ }
2531
+ function blockDecision(base, input) {
2532
+ const rejectionInput = {
2533
+ tool: base.tool,
2534
+ actionClass: base.classification.actionClass,
2535
+ reasons: input.reasons
2536
+ };
2537
+ if (base.posture !== void 0) rejectionInput.posture = base.posture;
2538
+ if (input.ruleId !== void 0) rejectionInput.ruleId = input.ruleId;
2539
+ const rejection = formatRejection(rejectionInput);
2540
+ return { ...base, ...input, kind: "block", rejection };
2541
+ }
2542
+ function matchSourcedRule(commandString, rules) {
2543
+ for (const entry of rules) {
2544
+ if (entry.rule.pattern.test(commandString)) {
2545
+ const match = {
2546
+ ruleId: entry.rule.id,
2547
+ reason: `matched ${entry.rule.id}: ${entry.rule.description}`,
2548
+ actionClass: entry.rule.class,
2549
+ block: entry.rule.block
2550
+ };
2551
+ if (entry.rule.ask !== void 0) match.ask = entry.rule.ask;
2552
+ return { match, source: entry.source };
2553
+ }
2554
+ }
2555
+ return null;
2556
+ }
2557
+ var ACTION_CLASSES2 = /* @__PURE__ */ new Set([
2558
+ "read",
2559
+ "write",
2560
+ "execute",
2561
+ "system_modify",
2562
+ "git_destructive",
2563
+ "dispatch"
2564
+ ]);
2565
+ function effectiveClassification(classification, match) {
2566
+ if (!match || !ACTION_CLASSES2.has(match.actionClass)) return classification;
2567
+ const actionClass = match.actionClass;
2568
+ if (actionClass === classification.actionClass) return classification;
2569
+ return {
2570
+ actionClass,
2571
+ reasons: [...classification.reasons, `damage-control:${match.ruleId}`]
2572
+ };
2573
+ }
2574
+ function matchingProjectCommand(policy, command, cwd) {
2575
+ if (!policy.valid || policy.path === null) return null;
2576
+ const policyRoot = path4.dirname(path4.dirname(policy.path));
2577
+ for (const entry of policy.commands) {
2578
+ if (entry.command !== command) continue;
2579
+ if (entry.shellOperators === "deny" && hasShellOperators(command)) continue;
2580
+ const allowedCwd = entry.cwd !== void 0 ? path4.resolve(policyRoot, entry.cwd) : policyRoot;
2581
+ if (!isUnderOrSame(cwd, allowedCwd)) continue;
2582
+ return entry;
2583
+ }
2584
+ return null;
2585
+ }
2586
+ var CHAIN_BREAKING_TOKENS = /* @__PURE__ */ new Set([";", "|", "||", ">", ">>", "<", "<<"]);
2587
+ var CHAIN_MAX_SEGMENTS = 6;
2588
+ function recognizeCommandChain(command, callCwd, workspaceRoot, policy) {
2589
+ const segments = [];
2590
+ let current = [];
2591
+ for (const token of tokenizeShellLike(command)) {
2592
+ if (token === "&&") {
2593
+ segments.push(current);
2594
+ current = [];
2595
+ continue;
2596
+ }
2597
+ if (CHAIN_BREAKING_TOKENS.has(token)) return null;
2598
+ current.push(token);
2599
+ }
2600
+ segments.push(current);
2601
+ if (segments.length < 2 || segments.length > CHAIN_MAX_SEGMENTS) return null;
2602
+ const ruleIds = [];
2603
+ let requiresConfirmation = false;
2604
+ for (const segment of segments) {
2605
+ if (segment.length === 0) return null;
2606
+ if (segment[0] === "cd") {
2607
+ if (segment.length !== 2) return null;
2608
+ const target = segment[1] ?? "";
2609
+ if (target.startsWith("~") || !isUnderOrSame(path4.resolve(callCwd, target), workspaceRoot)) return null;
2610
+ ruleIds.push("builtin:cd-workspace");
2611
+ continue;
2612
+ }
2613
+ const rendered = segment.join(" ");
2614
+ const projectMatch = matchingProjectCommand(policy, rendered, callCwd);
2615
+ if (projectMatch) {
2616
+ ruleIds.push(projectMatch.id);
2617
+ if (projectMatch.requireConfirmation) requiresConfirmation = true;
2618
+ continue;
2619
+ }
2620
+ const builtin = BUILTIN_ALLOWLIST.find((entry) => entry.re.test(rendered));
2621
+ if (builtin === void 0) return null;
2622
+ ruleIds.push(builtin.id);
2623
+ }
2624
+ return { ruleIds, requiresConfirmation };
2625
+ }
2626
+ function hasSequencingOperators(command) {
2627
+ return /(\|\||&&|;|\||>>?|<|\n|\r)/.test(command);
2628
+ }
2629
+ function hasCommandSubstitution(command) {
2630
+ return /(`|\$\()/.test(command);
2631
+ }
2632
+ function hasShellOperators(command) {
2633
+ return hasSequencingOperators(command) || hasCommandSubstitution(command);
2634
+ }
2635
+ function isUnderOrSame(child, parent) {
2636
+ const rel = path4.relative(
2637
+ canonicalizeExistingPath(path4.resolve(parent)),
2638
+ canonicalizeExistingPath(path4.resolve(child))
2639
+ );
2640
+ return rel === "" || !rel.startsWith("..") && !path4.isAbsolute(rel);
2641
+ }
2642
+ function commandArg(args) {
2643
+ return typeof args?.command === "string" ? args.command : null;
2644
+ }
2645
+ function pathArg(args) {
2646
+ if (!args) return null;
2647
+ const candidate = args.path ?? args.file_path ?? args.filePath;
2648
+ return typeof candidate === "string" && candidate.length > 0 ? candidate : null;
2649
+ }
2650
+ function cwdArg(args, fallback) {
2651
+ const resolved = typeof args?.cwd === "string" && args.cwd.length > 0 ? path4.resolve(fallback, args.cwd) : fallback;
2652
+ return canonicalizeExistingPath(resolved);
2653
+ }
2654
+ var CONTENT_BEARING_TOOLS = /* @__PURE__ */ new Set([ToolNames.Write, ToolNames.Edit, ToolNames.Artifact]);
2655
+ function damageControlScan(call) {
2656
+ if (!CONTENT_BEARING_TOOLS.has(call.tool)) return serializeArgs(call.args);
2657
+ const pathArg2 = call.args?.path;
2658
+ return typeof pathArg2 === "string" ? pathArg2 : "";
2659
+ }
2660
+ function serializeArgs(args) {
2661
+ if (!args) return "";
2662
+ const parts = [];
2663
+ for (const v of Object.values(args)) {
2664
+ if (v == null) continue;
2665
+ if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") parts.push(String(v));
2666
+ else {
2667
+ try {
2668
+ parts.push(JSON.stringify(v));
2669
+ } catch {
2670
+ }
2671
+ }
2672
+ }
2673
+ return parts.join(" ");
2674
+ }
2675
+ function rulePackHash(packs) {
2676
+ const payload = {
2677
+ base: packPayload(packs.base.rules)
2678
+ };
2679
+ return createHash3("sha256").update(JSON.stringify(payload), "utf8").digest("hex");
2680
+ }
2681
+ function packPayload(rules) {
2682
+ return rules.map((rule) => ({
2683
+ id: rule.id,
2684
+ description: rule.description,
2685
+ pattern: rule.pattern.source,
2686
+ class: rule.class,
2687
+ block: rule.block,
2688
+ ...rule.ask !== void 0 ? { ask: rule.ask } : {}
2689
+ }));
2690
+ }
2691
+
2692
+ // src/domains/safety/scope.ts
2693
+ import path5 from "node:path";
2694
+ function normalizeRoot(root) {
2695
+ return path5.resolve(root);
2696
+ }
2697
+ function isUnder(child, parent) {
2698
+ const c = normalizeRoot(child);
2699
+ const p = normalizeRoot(parent);
2700
+ if (c === p) return true;
2701
+ const rel = path5.relative(p, c);
2702
+ return rel !== "" && !rel.startsWith("..") && !path5.isAbsolute(rel);
2703
+ }
2704
+ function isSubset(worker, orchestrator) {
2705
+ for (const action of worker.allowedActions) {
2706
+ if (!orchestrator.allowedActions.has(action)) return false;
2707
+ }
2708
+ for (const root of worker.allowedWriteRoots) {
2709
+ const covered = orchestrator.allowedWriteRoots.some((outer) => isUnder(root, outer));
2710
+ if (!covered) return false;
2711
+ }
2712
+ if (worker.allowNetwork && !orchestrator.allowNetwork) return false;
2713
+ if (worker.allowDispatch && !orchestrator.allowDispatch) return false;
2714
+ return true;
2715
+ }
2716
+ var READONLY_SCOPE = {
2717
+ allowedActions: /* @__PURE__ */ new Set(["read"]),
2718
+ allowedWriteRoots: [],
2719
+ allowNetwork: true,
2720
+ allowDispatch: false
2721
+ };
2722
+ var WORKSPACE_SCOPE = {
2723
+ allowedActions: /* @__PURE__ */ new Set(["read", "write", "execute", "dispatch"]),
2724
+ allowedWriteRoots: [process.cwd()],
2725
+ allowNetwork: true,
2726
+ allowDispatch: true
2727
+ };
2728
+ var CONFIRMED_SCOPE = {
2729
+ allowedActions: /* @__PURE__ */ new Set(["read", "write", "execute", "dispatch", "system_modify"]),
2730
+ allowedWriteRoots: [process.cwd()],
2731
+ allowNetwork: true,
2732
+ allowDispatch: true
2733
+ };
2734
+
2735
+ // src/domains/safety/finish-contract.ts
2736
+ var FINISH_CONTRACT_ADVISORY_MESSAGE = "[Clio Coder] finish-contract advisory: you changed files this turn without recording validation evidence or an explicit limitation. Run a verification command or state what could not be verified.";
2737
+ var DEFAULT_RECENT_ENTRY_LIMIT = 80;
2738
+ var LIMITATION_PATTERNS = [
2739
+ /\b(?:blocked by|blocker|blockers|unable to|not able to|could not|couldn't|cannot|can't)\b/i,
2740
+ /\b(?:did not|didn't|have not|haven't|has not|hasn't|was not able|wasn't able)\b/i,
2741
+ /\b(?:not complete|incomplete)\b/i,
2742
+ /\b(?:not|un)(?:\s|-)?(?:validated|verified|tested)\b/i,
2743
+ /^\s*Tests\s*:\s*(?:not run|not executed|not available|failed|blocked|skipped)\b/im,
2744
+ /^\s*Known gaps?\s*:\s*(?!\s*(?:none|no\b|n\/a|not applicable)\b).+/im,
2745
+ /\bremaining(?:\s+\w+){0,3}\s+(?:work|issue|issues|gap|gaps|blocker|blockers)\b/i
2746
+ ];
2747
+ function assessFinishContract(input) {
2748
+ const assistantText = input.assistantText.trim();
2749
+ const sessionEntries = input.sessionEntries ?? [];
2750
+ const assistantTurnId = input.assistantTurnId ?? null;
2751
+ const window = recentEntries(sessionEntries, assistantTurnId, input.recentEntryLimit ?? DEFAULT_RECENT_ENTRY_LIMIT);
2752
+ const mutatedPaths = mutatingReceipts(window);
2753
+ if (mutatedPaths.length === 0) {
2754
+ return { kind: "ok", reason: "no_mutation", evidence: [], mutatedPaths };
2755
+ }
2756
+ const evidence = collectValidationEvidence(window);
2757
+ if (evidence.length > 0) {
2758
+ return { kind: "ok", reason: "validation_evidence", evidence, mutatedPaths };
2759
+ }
2760
+ if (hasExplicitLimitation(assistantText)) {
2761
+ return { kind: "ok", reason: "explicit_limitation", evidence: [], mutatedPaths };
2762
+ }
2763
+ return {
2764
+ kind: "engage",
2765
+ reason: "unvalidated_mutation",
2766
+ message: FINISH_CONTRACT_ADVISORY_MESSAGE,
2767
+ evidence: [],
2768
+ mutatedPaths
2769
+ };
2770
+ }
2771
+ function hasExplicitLimitation(text) {
2772
+ const normalized = text.trim();
2773
+ if (normalized.length === 0) return false;
2774
+ return LIMITATION_PATTERNS.some((pattern) => pattern.test(normalized));
2775
+ }
2776
+ function mutatingReceipts(recent) {
2777
+ const mutationCalls = /* @__PURE__ */ new Map();
2778
+ const paths = [];
2779
+ const seen = /* @__PURE__ */ new Set();
2780
+ for (const entry of recent) {
2781
+ const bashMutation = bashExecutionMutationPaths(entry);
2782
+ if (bashMutation !== null) {
2783
+ for (const path7 of bashMutation) pushPath(paths, seen, path7);
2784
+ continue;
2785
+ }
2786
+ const call = mutatingToolCall(entry);
2787
+ if (call !== null) {
2788
+ mutationCalls.set(call.toolCallId, call);
2789
+ continue;
2790
+ }
2791
+ const resultId = successfulToolResultId(entry);
2792
+ if (resultId !== null) {
2793
+ const candidate = mutationCalls.get(resultId);
2794
+ if (candidate !== void 0) {
2795
+ for (const path7 of candidate.paths) pushPath(paths, seen, path7);
2796
+ }
2797
+ }
2798
+ }
2799
+ return paths;
2800
+ }
2801
+ function mutationPathsForTool(toolName, args) {
2802
+ if (toolName === ToolNames.Bash) {
2803
+ const command = typeof args?.command === "string" ? args.command : null;
2804
+ if (command === null) return [];
2805
+ return [...extractCommandWriteTargets(command), ...extractCommandDeleteTargets(command)];
2806
+ }
2807
+ return toolMutationPaths(toolName, args);
2808
+ }
2809
+ function mutatingToolCall(entry) {
2810
+ const record = asRecord(entry);
2811
+ if (record?.kind !== "message" || record.role !== "tool_call") return null;
2812
+ const payload = asRecord(record.payload);
2813
+ if (payload === null) return null;
2814
+ const toolName = stringFromFirst(payload, ["name", "toolName", "tool"]);
2815
+ if (toolName === null) return null;
2816
+ const args = asRecord(payload.args ?? payload.arguments ?? payload.input) ?? void 0;
2817
+ const paths = mutationPathsForTool(toolName, args);
2818
+ if (paths.length === 0) return null;
2819
+ const toolCallId = stringFromFirst(payload, ["toolCallId", "tool_call_id", "id"]) ?? turnIdOf(entry);
2820
+ if (toolCallId === null) return null;
2821
+ return { toolCallId, paths };
2822
+ }
2823
+ function bashExecutionMutationPaths(entry) {
2824
+ const record = asRecord(entry);
2825
+ if (record?.kind !== "bashExecution") return null;
2826
+ if (typeof record.command !== "string") return null;
2827
+ if (record.cancelled === true || record.exitCode !== 0) return null;
2828
+ return [...extractCommandWriteTargets(record.command), ...extractCommandDeleteTargets(record.command)];
2829
+ }
2830
+ function pushPath(paths, seen, path7) {
2831
+ if (path7.length === 0 || seen.has(path7)) return;
2832
+ seen.add(path7);
2833
+ paths.push(path7);
2834
+ }
2835
+ function collectValidationEvidence(recent) {
2836
+ const evidence = [];
2837
+ const toolCalls = /* @__PURE__ */ new Map();
2838
+ const dispatchCalls = /* @__PURE__ */ new Map();
2839
+ const seen = /* @__PURE__ */ new Set();
2840
+ for (const entry of recent) {
2841
+ const protectedArtifact = protectedArtifactEvidence(entry);
2842
+ if (protectedArtifact !== null) {
2843
+ pushEvidence(evidence, seen, protectedArtifact);
2844
+ continue;
2845
+ }
2846
+ const call = bashValidationCall(entry);
2847
+ if (call !== null) {
2848
+ toolCalls.set(call.toolCallId, call);
2849
+ continue;
2850
+ }
2851
+ const typedValidationCall = validationToolCall(entry);
2852
+ if (typedValidationCall !== null) {
2853
+ toolCalls.set(typedValidationCall.toolCallId, typedValidationCall);
2854
+ continue;
2855
+ }
2856
+ const dispatchCall = dispatchEvidenceCall(entry);
2857
+ if (dispatchCall !== null) {
2858
+ dispatchCalls.set(dispatchCall.toolCallId, dispatchCall);
2859
+ continue;
2860
+ }
2861
+ const resultId = successfulToolResultId(entry);
2862
+ if (resultId !== null) {
2863
+ const dispatchCandidate = dispatchCalls.get(resultId);
2864
+ const dispatchReceipt = dispatchReceiptEvidence(entry, dispatchCandidate);
2865
+ if (dispatchReceipt !== null) {
2866
+ pushEvidence(evidence, seen, dispatchReceipt);
2867
+ continue;
2868
+ }
2869
+ const candidate = toolCalls.get(resultId);
2870
+ if (candidate !== void 0) {
2871
+ pushEvidence(evidence, seen, validationEvidence(candidate));
2872
+ }
2873
+ continue;
2874
+ }
2875
+ const bashExecution = bashExecutionEvidence(entry);
2876
+ if (bashExecution !== null) pushEvidence(evidence, seen, bashExecution);
2877
+ }
2878
+ return evidence;
2879
+ }
2880
+ function recentEntries(entries, assistantTurnId, recentEntryLimit) {
2881
+ const boundedLimit = Number.isFinite(recentEntryLimit) && recentEntryLimit > 0 ? Math.floor(recentEntryLimit) : DEFAULT_RECENT_ENTRY_LIMIT;
2882
+ const assistantIndex = assistantTurnId === null ? -1 : entries.findIndex((entry) => turnIdOf(entry) === assistantTurnId);
2883
+ const endExclusive = assistantIndex >= 0 ? assistantIndex : entries.length;
2884
+ let startInclusive = Math.max(0, endExclusive - boundedLimit);
2885
+ for (let index = endExclusive - 1; index >= startInclusive; index -= 1) {
2886
+ if (isUserMessageEntry(entries[index])) {
2887
+ startInclusive = index + 1;
2888
+ break;
2889
+ }
2890
+ }
2891
+ return entries.slice(startInclusive, endExclusive);
2892
+ }
2893
+ function bashValidationCall(entry) {
2894
+ const record = asRecord(entry);
2895
+ if (record?.kind !== "message" || record.role !== "tool_call") return null;
2896
+ const payload = asRecord(record.payload);
2897
+ if (payload === null) return null;
2898
+ const toolName = stringFromFirst(payload, ["name", "toolName", "tool"]);
2899
+ if (toolName !== "bash") return null;
2900
+ const args = asRecord(payload.args ?? payload.arguments ?? payload.input);
2901
+ const command = typeof args?.command === "string" ? args.command : null;
2902
+ if (command === null) return null;
2903
+ const detected = detectValidationCommand(command);
2904
+ if (detected.kind !== "validation") return null;
2905
+ const toolCallId = stringFromFirst(payload, ["toolCallId", "tool_call_id", "id"]) ?? turnIdOf(entry);
2906
+ if (toolCallId === null) return null;
2907
+ const candidate = {
2908
+ toolCallId,
2909
+ command: detected.matched
2910
+ };
2911
+ const turnId = turnIdOf(entry);
2912
+ if (turnId !== null) candidate.turnId = turnId;
2913
+ return candidate;
2914
+ }
2915
+ function dispatchEvidenceCall(entry) {
2916
+ const record = asRecord(entry);
2917
+ if (record?.kind !== "message" || record.role !== "tool_call") return null;
2918
+ const payload = asRecord(record.payload);
2919
+ if (payload === null) return null;
2920
+ const toolName = stringFromFirst(payload, ["name", "toolName", "tool"]);
2921
+ if (toolName !== "dispatch") return null;
2922
+ const args = asRecord(payload.args ?? payload.arguments ?? payload.input);
2923
+ const { agentId, task } = dispatchCallDescriptor(args);
2924
+ const toolCallId = stringFromFirst(payload, ["toolCallId", "tool_call_id", "id"]) ?? turnIdOf(entry);
2925
+ if (toolCallId === null) return null;
2926
+ const candidate = {
2927
+ toolCallId,
2928
+ command: `agent=${agentId}${task.length > 0 ? ` task=${task}` : ""}`
2929
+ };
2930
+ const turnId = turnIdOf(entry);
2931
+ if (turnId !== null) candidate.turnId = turnId;
2932
+ return candidate;
2933
+ }
2934
+ function dispatchCallDescriptor(args) {
2935
+ const defaultAgent = stringFromFirst(args ?? {}, ["agent", "agent_id", "agentId"]);
2936
+ const tasks = Array.isArray(args?.tasks) ? args.tasks : null;
2937
+ if (tasks !== null && tasks.length > 0) {
2938
+ const first = tasks[0];
2939
+ if (typeof first === "string") {
2940
+ return { agentId: defaultAgent ?? "coder", task: first.trim() };
2941
+ }
2942
+ const firstRecord = asRecord(first);
2943
+ const itemAgent = firstRecord === null ? null : stringFromFirst(firstRecord, ["agent", "agent_id", "agentId"]);
2944
+ const itemTask = typeof firstRecord?.task === "string" ? firstRecord.task.trim() : "";
2945
+ return { agentId: itemAgent ?? defaultAgent ?? "coder", task: itemTask };
2946
+ }
2947
+ const singleTask = typeof args?.task === "string" ? args.task.trim() : "";
2948
+ return { agentId: defaultAgent ?? "coder", task: singleTask };
2949
+ }
2950
+ function validationToolCall(entry) {
2951
+ const record = asRecord(entry);
2952
+ if (record?.kind !== "message" || record.role !== "tool_call") return null;
2953
+ const payload = asRecord(record.payload);
2954
+ if (payload === null) return null;
2955
+ const toolName = stringFromFirst(payload, ["name", "toolName", "tool"]);
2956
+ if (toolName === null) return null;
2957
+ const summary = typedValidationSummary(toolName, payload);
2958
+ if (summary === null) return null;
2959
+ const toolCallId = stringFromFirst(payload, ["toolCallId", "tool_call_id", "id"]) ?? turnIdOf(entry);
2960
+ if (toolCallId === null) return null;
2961
+ const candidate = {
2962
+ toolCallId,
2963
+ command: summary
2964
+ };
2965
+ const turnId = turnIdOf(entry);
2966
+ if (turnId !== null) candidate.turnId = turnId;
2967
+ return candidate;
2968
+ }
2969
+ function typedValidationSummary(toolName, payload) {
2970
+ const args = asRecord(payload.args ?? payload.arguments ?? payload.input);
2971
+ if (toolName === "verify") {
2972
+ const check = typeof args?.check === "string" ? args.check.trim() : "";
2973
+ if (check === "frontend") {
2974
+ const path7 = typeof args?.path === "string" && args.path.trim().length > 0 ? args.path.trim() : "artifact";
2975
+ return `verify frontend ${path7}`;
2976
+ }
2977
+ if (!isVerificationScriptName(check)) return null;
2978
+ return `npm run ${check}`;
2979
+ }
2980
+ return null;
2981
+ }
2982
+ function successfulToolResultId(entry) {
2983
+ const record = asRecord(entry);
2984
+ if (record?.kind !== "message" || record.role !== "tool_result") return null;
2985
+ const payload = asRecord(record.payload);
2986
+ if (payload === null) return null;
2987
+ if (payload.isError === true || payload.error === true) return null;
2988
+ const result = asRecord(payload.result);
2989
+ const details = asRecord(result?.details);
2990
+ if (details?.kind === "error") return null;
2991
+ return stringFromFirst(payload, ["toolCallId", "tool_call_id", "id"]);
2992
+ }
2993
+ function dispatchReceiptEvidence(entry, candidate) {
2994
+ const record = asRecord(entry);
2995
+ if (record?.kind !== "message" || record.role !== "tool_result") return null;
2996
+ const payload = asRecord(record.payload);
2997
+ if (payload === null) return null;
2998
+ const toolName = stringFromFirst(payload, ["toolName", "name", "tool"]);
2999
+ if (toolName !== null && toolName !== "dispatch") return null;
3000
+ const result = asRecord(payload.result);
3001
+ const details = asRecord(result?.details);
3002
+ if (details === null) return null;
3003
+ const summary = passedDispatchReceiptSummary(details, candidate);
3004
+ if (summary === null) return null;
3005
+ const evidence = {
3006
+ kind: "dispatch_receipt",
3007
+ summary
3008
+ };
3009
+ const turnId = candidate?.turnId ?? turnIdOf(entry);
3010
+ if (turnId !== null && turnId !== void 0) evidence.turnId = turnId;
3011
+ return evidence;
3012
+ }
3013
+ function passedDispatchReceiptSummary(details, candidate) {
3014
+ if (Array.isArray(details.runs)) {
3015
+ const runs = details.runs.map(asRecord);
3016
+ if (runs.length === 0 || runs.some((run) => run === null || run.exitCode !== 0)) return null;
3017
+ if (typeof details.failedCount === "number" && details.failedCount !== 0) return null;
3018
+ const first = runs[0];
3019
+ const runId = typeof first?.runId === "string" && first.runId.length > 0 ? first.runId : "unknown";
3020
+ const agentId = dispatchReceiptAgentId(typeof first?.agentId === "string" ? first.agentId : void 0, candidate);
3021
+ const extra = runs.length > 1 ? ` (+${runs.length - 1} more)` : "";
3022
+ return `dispatch receipt passed: run ${runId} agent ${agentId}${extra}`;
3023
+ }
3024
+ if (details.exitCode === 0) {
3025
+ const runId = typeof details.runId === "string" && details.runId.length > 0 ? details.runId : "unknown";
3026
+ const agentId = dispatchReceiptAgentId(typeof details.agentId === "string" ? details.agentId : void 0, candidate);
3027
+ return `dispatch receipt passed: run ${runId} agent ${agentId}`;
3028
+ }
3029
+ return null;
3030
+ }
3031
+ function dispatchReceiptAgentId(receiptAgentId, candidate) {
3032
+ if (receiptAgentId !== void 0 && receiptAgentId.length > 0) return receiptAgentId;
3033
+ return candidate?.command.match(/^agent=([^\s]+)/)?.[1] ?? "unknown";
3034
+ }
3035
+ function bashExecutionEvidence(entry) {
3036
+ const record = asRecord(entry);
3037
+ if (record?.kind !== "bashExecution") return null;
3038
+ if (typeof record.command !== "string") return null;
3039
+ if (record.cancelled === true) return null;
3040
+ if (record.exitCode !== 0) return null;
3041
+ const detected = detectValidationCommand(record.command);
3042
+ if (detected.kind !== "validation") return null;
3043
+ const evidence = {
3044
+ kind: "validation_command",
3045
+ summary: `validation command passed: ${detected.matched}`
3046
+ };
3047
+ const turnId = turnIdOf(entry);
3048
+ if (turnId !== null) evidence.turnId = turnId;
3049
+ return evidence;
3050
+ }
3051
+ function protectedArtifactEvidence(entry) {
3052
+ const record = asRecord(entry);
3053
+ if (record?.kind !== "protectedArtifact" || record.action !== "protect") return null;
3054
+ const artifact = asRecord(record.artifact);
3055
+ const path7 = typeof artifact?.path === "string" && artifact.path.trim().length > 0 ? artifact.path.trim() : null;
3056
+ if (path7 === null) return null;
3057
+ const evidence = {
3058
+ kind: "protected_artifact",
3059
+ summary: `protected artifact recorded: ${path7}`
3060
+ };
3061
+ const turnId = turnIdOf(entry);
3062
+ if (turnId !== null) evidence.turnId = turnId;
3063
+ return evidence;
3064
+ }
3065
+ function validationEvidence(candidate) {
3066
+ const evidence = {
3067
+ kind: "validation_command",
3068
+ summary: `validation command passed: ${candidate.command}`
3069
+ };
3070
+ if (candidate.turnId !== void 0) evidence.turnId = candidate.turnId;
3071
+ return evidence;
3072
+ }
3073
+ function pushEvidence(evidence, seen, item) {
3074
+ const key = `${item.kind}\0${item.summary}\0${item.turnId ?? ""}`;
3075
+ if (seen.has(key)) return;
3076
+ seen.add(key);
3077
+ evidence.push(item);
3078
+ }
3079
+ function isUserMessageEntry(entry) {
3080
+ const record = asRecord(entry);
3081
+ if (record?.kind !== "message" || record.role !== "user") return false;
3082
+ const payload = asRecord(record.payload);
3083
+ return payload?.synthetic !== true;
3084
+ }
3085
+ function turnIdOf(entry) {
3086
+ const record = asRecord(entry);
3087
+ return typeof record?.turnId === "string" ? record.turnId : null;
3088
+ }
3089
+ function stringFromFirst(record, keys) {
3090
+ for (const key of keys) {
3091
+ const value = record[key];
3092
+ if (typeof value === "string" && value.length > 0) return value;
3093
+ }
3094
+ return null;
3095
+ }
3096
+ function asRecord(value) {
3097
+ return value !== null && typeof value === "object" ? value : null;
3098
+ }
3099
+
3100
+ // src/domains/safety/rigor.ts
3101
+ import { existsSync as existsSync4 } from "node:fs";
3102
+ import path6 from "node:path";
3103
+ var VALIDATION_CONTRACT_FILES = [
3104
+ path6.join(".clio-coder", "validation.yaml"),
3105
+ path6.join(".clio-coder", "validation.yml"),
3106
+ "validation.yaml",
3107
+ "validation.yml",
3108
+ "VALIDATION.md"
3109
+ ];
3110
+ function resolveRigor(options) {
3111
+ if (options.override === "high" || options.override === "normal") return options.override;
3112
+ const cwd = options.cwd ?? process.cwd();
3113
+ return hasValidationContract(cwd) ? "high" : "normal";
3114
+ }
3115
+ function parseRigorOverride(value) {
3116
+ if (typeof value !== "string") return null;
3117
+ const normalized = value.trim().toLowerCase();
3118
+ if (normalized === "high") return "high";
3119
+ if (normalized === "normal") return "normal";
3120
+ return null;
3121
+ }
3122
+ function hasValidationContract(cwd) {
3123
+ try {
3124
+ return VALIDATION_CONTRACT_FILES.some((relative) => existsSync4(path6.join(cwd, relative)));
3125
+ } catch {
3126
+ return false;
3127
+ }
3128
+ }
3129
+
3130
+ export {
3131
+ parseFrontmatter,
3132
+ resultContractSourceId,
3133
+ parseVerifierResult,
3134
+ resultContractAuthorship,
3135
+ parseScoutResult,
3136
+ validateResultContract,
3137
+ validateRecipeResult,
3138
+ RESULT_CONTRACT_REPAIR_LIMIT,
3139
+ resultContractRepairMessage,
3140
+ parseResultContract,
3141
+ FLEET_COMMAND_BASE_ENV,
3142
+ loadFleetCommands,
3143
+ normalizeWriteBoundary,
3144
+ writeBoundaryCovers,
3145
+ describeWriteBoundary,
3146
+ fleetLoopCheckStepId,
3147
+ fleetLoopRepairStepId,
3148
+ fleetStepWriteBoundary,
3149
+ FLEET_COMMANDS_REPO_PATH,
3150
+ FLEET_COMMANDS_REMEDY,
3151
+ FleetCommandRegistryMissingError,
3152
+ loadFleetContract,
3153
+ listFleetContracts,
3154
+ renderFleetPrompt,
3155
+ AUTONOMY_LEVELS,
3156
+ DEFAULT_AUTONOMY_LEVEL,
3157
+ isAutonomyLevel,
3158
+ mapAutonomy,
3159
+ autonomyDenyRejection,
3160
+ autonomyAskRejection,
3161
+ createLoopState,
3162
+ hashToolCall,
3163
+ observe,
3164
+ formatModelRejection,
3165
+ WRITE_ROOT_REFUSED_TOOLS,
3166
+ createSafetyPolicyEngine,
3167
+ isSubset,
3168
+ READONLY_SCOPE,
3169
+ WORKSPACE_SCOPE,
3170
+ CONFIRMED_SCOPE,
3171
+ DEFAULT_RECENT_ENTRY_LIMIT,
3172
+ assessFinishContract,
3173
+ typedValidationSummary,
3174
+ resolveRigor,
3175
+ parseRigorOverride
3176
+ };
3177
+ //# sourceMappingURL=chunk-XV4ZJ6ZM.js.map