cool-workflow 0.1.98 → 0.2.1

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 (315) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/README.md +18 -7
  4. package/apps/architecture-review/app.json +2 -2
  5. package/apps/architecture-review/workflow.js +12 -12
  6. package/apps/architecture-review-fast/app.json +1 -1
  7. package/apps/end-to-end-golden-path/app.json +1 -1
  8. package/apps/pr-review-fix-ci/app.json +1 -1
  9. package/apps/release-cut/app.json +1 -1
  10. package/apps/research-synthesis/app.json +1 -1
  11. package/dist/cli/dispatch.js +236 -0
  12. package/dist/cli/entry.js +120 -0
  13. package/dist/cli/io.js +21 -4
  14. package/dist/cli/parseargv.js +157 -0
  15. package/dist/cli.js +6 -33
  16. package/dist/core/capability-table.js +3518 -0
  17. package/dist/core/format/help.js +314 -0
  18. package/dist/{state-explosion/format.js → core/format/state-explosion-text.js} +10 -1
  19. package/dist/core/hash.js +137 -0
  20. package/dist/core/multi-agent/candidate-scoring.js +219 -0
  21. package/dist/core/multi-agent/collaboration.js +481 -0
  22. package/dist/core/multi-agent/coordinator.js +515 -0
  23. package/dist/core/multi-agent/eval-replay.js +306 -0
  24. package/dist/core/multi-agent/runtime.js +929 -0
  25. package/dist/core/multi-agent/topology.js +298 -0
  26. package/dist/core/multi-agent/trust-policy.js +197 -0
  27. package/dist/core/pipeline/commit-gate.js +320 -0
  28. package/dist/{pipeline-contract.js → core/pipeline/contract.js} +24 -37
  29. package/dist/core/pipeline/dispatch.js +103 -0
  30. package/dist/core/pipeline/drive-decide.js +227 -0
  31. package/dist/core/pipeline/error-feedback.js +161 -0
  32. package/dist/core/pipeline/loop-expansion.js +124 -0
  33. package/dist/{result-normalize.js → core/pipeline/result-normalize.js} +14 -37
  34. package/dist/core/pipeline/runner.js +230 -0
  35. package/dist/{contract-migration.js → core/state/contract-migration.js} +68 -92
  36. package/dist/{state-migrations.js → core/state/migrations.js} +103 -96
  37. package/dist/core/state/node-projection.js +95 -0
  38. package/dist/core/state/node-snapshot.js +230 -0
  39. package/dist/core/state/run-paths.js +93 -0
  40. package/dist/{schema-validate.js → core/state/schema-validate.js} +35 -28
  41. package/dist/core/state/schema.js +50 -0
  42. package/dist/core/state/state-explosion/digest.js +243 -0
  43. package/dist/core/state/state-explosion/graph.js +527 -0
  44. package/dist/{state-explosion → core/state/state-explosion}/helpers.js +34 -3
  45. package/dist/core/state/state-explosion/report.js +187 -0
  46. package/dist/{state-explosion → core/state/state-explosion}/size.js +25 -7
  47. package/dist/{state-node.js → core/state/state-node.js} +90 -113
  48. package/dist/core/state/types.js +19 -0
  49. package/dist/{validation.js → core/state/validation.js} +64 -131
  50. package/dist/core/trust/evidence-grounding.js +134 -0
  51. package/dist/core/trust/ledger.js +199 -0
  52. package/dist/{telemetry-attestation.js → core/trust/telemetry-attestation.js} +94 -68
  53. package/dist/core/trust/telemetry-ledger.js +127 -0
  54. package/dist/core/types.js +20 -0
  55. package/dist/core/version.js +16 -0
  56. package/dist/{workflow-app-framework.js → core/workflow-apps/app-schema.js} +337 -426
  57. package/dist/mcp/dispatch.js +104 -0
  58. package/dist/mcp/server.js +144 -0
  59. package/dist/mcp-server.js +6 -84
  60. package/dist/{agent-config.js → shell/agent-config.js} +118 -71
  61. package/dist/shell/app-run-cli.js +88 -0
  62. package/dist/shell/audit-cli.js +259 -0
  63. package/dist/shell/audit-provenance.js +83 -0
  64. package/dist/shell/candidate-scoring-io.js +459 -0
  65. package/dist/shell/collaboration-io.js +264 -0
  66. package/dist/shell/commit-summary.js +104 -0
  67. package/dist/shell/commit.js +290 -0
  68. package/dist/shell/coordinator-io.js +476 -0
  69. package/dist/shell/demo-cli.js +19 -0
  70. package/dist/shell/dispatch.js +162 -0
  71. package/dist/shell/doctor.js +292 -0
  72. package/dist/shell/drive.js +885 -0
  73. package/dist/shell/error-feedback-io.js +305 -0
  74. package/dist/shell/eval-io.js +473 -0
  75. package/dist/{multi-agent-eval/format.js → shell/eval-text.js} +78 -49
  76. package/dist/{evidence-reasoning.js → shell/evidence-reasoning.js} +124 -255
  77. package/dist/shell/exec-backend-cli.js +88 -0
  78. package/dist/shell/execution-backend/agent.js +507 -0
  79. package/dist/shell/execution-backend/ci.js +15 -0
  80. package/dist/shell/execution-backend/container.js +69 -0
  81. package/dist/shell/execution-backend/envelopes.js +55 -0
  82. package/dist/shell/execution-backend/local.js +113 -0
  83. package/dist/shell/execution-backend/probes.js +175 -0
  84. package/dist/shell/execution-backend/registry.js +402 -0
  85. package/dist/shell/execution-backend/remote.js +128 -0
  86. package/dist/shell/execution-backend/types.js +11 -0
  87. package/dist/shell/feedback-cli.js +81 -0
  88. package/dist/shell/feedback-operations.js +48 -0
  89. package/dist/shell/fs-atomic.js +276 -0
  90. package/dist/shell/harness.js +98 -0
  91. package/dist/shell/ledger-cli.js +212 -0
  92. package/dist/shell/ledger-io.js +169 -0
  93. package/dist/shell/man-cli.js +89 -0
  94. package/dist/shell/metrics-cli.js +100 -0
  95. package/dist/shell/multi-agent-cli.js +1002 -0
  96. package/dist/shell/multi-agent-host.js +563 -0
  97. package/dist/shell/multi-agent-io.js +387 -0
  98. package/dist/{multi-agent-operator-ux.js → shell/multi-agent-operator-ux.js} +234 -191
  99. package/dist/shell/node-store.js +124 -0
  100. package/dist/{observability/format.js → shell/observability-format.js} +7 -1
  101. package/dist/{observability/intake.js → shell/observability-intake.js} +79 -56
  102. package/dist/{observability.js → shell/observability.js} +159 -332
  103. package/dist/{onramp.js → shell/onramp.js} +11 -0
  104. package/dist/{operator-ux/format.js → shell/operator-ux-text.js} +250 -239
  105. package/dist/shell/operator-ux.js +431 -0
  106. package/dist/shell/orchestrator.js +231 -0
  107. package/dist/shell/pipeline-cli.js +667 -0
  108. package/dist/shell/pipeline.js +217 -0
  109. package/dist/shell/reclamation-io.js +1366 -0
  110. package/dist/shell/registry-cli.js +344 -0
  111. package/dist/{remote-source.js → shell/remote-source.js} +2 -2
  112. package/dist/shell/report-cli.js +101 -0
  113. package/dist/shell/report-view-cli.js +117 -0
  114. package/dist/{orchestrator → shell}/report.js +289 -282
  115. package/dist/shell/reporter.js +62 -0
  116. package/dist/shell/run-export-cli.js +106 -0
  117. package/dist/shell/run-export.js +680 -0
  118. package/dist/shell/run-registry-io.js +1014 -0
  119. package/dist/shell/run-store.js +164 -0
  120. package/dist/{sandbox-profile.js → shell/sandbox-profile.js} +134 -95
  121. package/dist/{scheduler.js → shell/scheduler-io.js} +257 -48
  122. package/dist/shell/scheduling-io.js +321 -0
  123. package/dist/shell/state-cli.js +181 -0
  124. package/dist/shell/state-explosion-cli.js +197 -0
  125. package/dist/shell/telemetry-cli.js +85 -0
  126. package/dist/{telemetry-demo.js → shell/telemetry-demo.js} +149 -119
  127. package/dist/shell/telemetry-ledger-io.js +132 -0
  128. package/dist/{term.js → shell/term.js} +36 -46
  129. package/dist/shell/topology-io.js +361 -0
  130. package/dist/shell/trust-audit.js +472 -0
  131. package/dist/{multi-agent-trust.js → shell/trust-policy-io.js} +67 -205
  132. package/dist/shell/verifier.js +48 -0
  133. package/dist/shell/workbench-host.js +258 -0
  134. package/dist/shell/workbench-text.js +18 -0
  135. package/dist/shell/workbench.js +170 -0
  136. package/dist/shell/worker-cli.js +124 -0
  137. package/dist/shell/worker-isolation.js +852 -0
  138. package/dist/shell/workflow-app-loader.js +650 -0
  139. package/docs/agent-delegation-drive.7.md +4 -0
  140. package/docs/cli-mcp-parity.7.md +282 -219
  141. package/docs/contract-migration-tooling.7.md +4 -0
  142. package/docs/control-plane-scheduling.7.md +4 -0
  143. package/docs/durable-state-and-locking.7.md +4 -0
  144. package/docs/evidence-adoption-reasoning-chain.7.md +4 -0
  145. package/docs/execution-backends.7.md +4 -0
  146. package/docs/multi-agent-cli-mcp-surface.7.md +4 -0
  147. package/docs/multi-agent-eval-replay-harness.7.md +4 -0
  148. package/docs/multi-agent-operator-ux.7.md +4 -0
  149. package/docs/node-snapshot-diff-replay.7.md +4 -0
  150. package/docs/observability-cost-accounting.7.md +6 -1
  151. package/docs/project-index.md +137 -71
  152. package/docs/real-execution-backends.7.md +4 -0
  153. package/docs/release-and-migration.7.md +4 -0
  154. package/docs/release-tooling.7.md +4 -0
  155. package/docs/remote-source-review.7.md +4 -4
  156. package/docs/report-verifiable-bundle.7.md +1 -1
  157. package/docs/run-registry-control-plane.7.md +4 -0
  158. package/docs/run-retention-reclamation.7.md +25 -0
  159. package/docs/security-trust-hardening.7.md +3 -1
  160. package/docs/state-explosion-management.7.md +4 -0
  161. package/docs/team-collaboration.7.md +4 -0
  162. package/docs/web-desktop-workbench.7.md +15 -1
  163. package/manifest/plugin.manifest.json +1 -1
  164. package/manifest/source-context-profiles.json +9 -13
  165. package/package.json +1 -1
  166. package/scripts/agents/agent-adapter-core.js +22 -2
  167. package/scripts/agents/claude-p-agent.js +8 -3
  168. package/scripts/agents/cw-attest-wrap.js +2 -2
  169. package/scripts/agents/gemini-agent.js +5 -1
  170. package/scripts/agents/opencode-agent.js +5 -1
  171. package/scripts/bump-version.js +4 -3
  172. package/scripts/canonical-apps.js +4 -4
  173. package/scripts/dogfood-architecture-review.js +2 -3
  174. package/scripts/dogfood-release.js +3 -3
  175. package/scripts/gen-parity-doc.js +15 -2
  176. package/scripts/golden-path.js +4 -4
  177. package/scripts/onramp-check.js +1 -1
  178. package/scripts/parity-check.js +38 -21
  179. package/scripts/release-flow.js +2 -2
  180. package/scripts/sync-project-index.js +51 -27
  181. package/scripts/validate-run-state-schema.js +2 -2
  182. package/scripts/version-sync-check.js +30 -30
  183. package/dist/candidate-scoring.js +0 -729
  184. package/dist/capability-core.js +0 -1189
  185. package/dist/capability-registry.js +0 -885
  186. package/dist/cli/command-surface.js +0 -494
  187. package/dist/cli/format.js +0 -56
  188. package/dist/cli/handlers/audit.js +0 -82
  189. package/dist/cli/handlers/blackboard.js +0 -81
  190. package/dist/cli/handlers/candidate.js +0 -40
  191. package/dist/cli/handlers/clones.js +0 -34
  192. package/dist/cli/handlers/collaboration.js +0 -61
  193. package/dist/cli/handlers/eval.js +0 -40
  194. package/dist/cli/handlers/ledger.js +0 -169
  195. package/dist/cli/handlers/maintenance.js +0 -107
  196. package/dist/cli/handlers/multi-agent.js +0 -165
  197. package/dist/cli/handlers/node.js +0 -41
  198. package/dist/cli/handlers/operational.js +0 -155
  199. package/dist/cli/handlers/operator.js +0 -146
  200. package/dist/cli/handlers/registry.js +0 -68
  201. package/dist/cli/handlers/run.js +0 -153
  202. package/dist/cli/handlers/scheduling.js +0 -132
  203. package/dist/cli/handlers/workbench.js +0 -41
  204. package/dist/cli/handlers/worker.js +0 -45
  205. package/dist/cli/run-summary.js +0 -45
  206. package/dist/clones.js +0 -162
  207. package/dist/collaboration.js +0 -726
  208. package/dist/commit.js +0 -592
  209. package/dist/compare.js +0 -18
  210. package/dist/coordinator/classify.js +0 -45
  211. package/dist/coordinator/paths.js +0 -42
  212. package/dist/coordinator/util.js +0 -126
  213. package/dist/coordinator.js +0 -990
  214. package/dist/daemon.js +0 -44
  215. package/dist/dispatch.js +0 -250
  216. package/dist/doctor.js +0 -212
  217. package/dist/drive.js +0 -864
  218. package/dist/error-feedback.js +0 -419
  219. package/dist/evidence-grounding.js +0 -184
  220. package/dist/execution-backend/agent.js +0 -354
  221. package/dist/execution-backend/probes.js +0 -112
  222. package/dist/execution-backend/util.js +0 -47
  223. package/dist/execution-backend.js +0 -961
  224. package/dist/gates.js +0 -48
  225. package/dist/harness.js +0 -61
  226. package/dist/ledger.js +0 -313
  227. package/dist/loop-expansion.js +0 -60
  228. package/dist/mcp/tool-call.js +0 -470
  229. package/dist/mcp/tool-definitions.js +0 -1066
  230. package/dist/mcp-surface.js +0 -30
  231. package/dist/multi-agent/graph.js +0 -84
  232. package/dist/multi-agent/helpers.js +0 -141
  233. package/dist/multi-agent/ids.js +0 -20
  234. package/dist/multi-agent/paths.js +0 -22
  235. package/dist/multi-agent-eval/normalize.js +0 -51
  236. package/dist/multi-agent-eval.js +0 -678
  237. package/dist/multi-agent-host.js +0 -777
  238. package/dist/multi-agent.js +0 -984
  239. package/dist/node-projection.js +0 -59
  240. package/dist/node-snapshot.js +0 -260
  241. package/dist/operator-ux.js +0 -631
  242. package/dist/orchestrator/app-operations.js +0 -211
  243. package/dist/orchestrator/audit-operations.js +0 -182
  244. package/dist/orchestrator/candidate-operations.js +0 -117
  245. package/dist/orchestrator/cli-options.js +0 -294
  246. package/dist/orchestrator/collaboration-operations.js +0 -86
  247. package/dist/orchestrator/feedback-operations.js +0 -81
  248. package/dist/orchestrator/host-operations.js +0 -78
  249. package/dist/orchestrator/lifecycle-operations.js +0 -650
  250. package/dist/orchestrator/migration-operations.js +0 -44
  251. package/dist/orchestrator/multi-agent-operations.js +0 -362
  252. package/dist/orchestrator/topology-operations.js +0 -84
  253. package/dist/orchestrator.js +0 -925
  254. package/dist/pipeline-runner.js +0 -285
  255. package/dist/reclamation/hash.js +0 -72
  256. package/dist/reclamation.js +0 -812
  257. package/dist/reporter.js +0 -67
  258. package/dist/run-export.js +0 -815
  259. package/dist/run-registry/derive.js +0 -175
  260. package/dist/run-registry/format.js +0 -124
  261. package/dist/run-registry/gc.js +0 -251
  262. package/dist/run-registry/policy.js +0 -16
  263. package/dist/run-registry/queue.js +0 -115
  264. package/dist/run-registry.js +0 -850
  265. package/dist/run-state-schema.js +0 -68
  266. package/dist/scheduling.js +0 -184
  267. package/dist/state-explosion.js +0 -1014
  268. package/dist/state.js +0 -367
  269. package/dist/telemetry-ledger.js +0 -196
  270. package/dist/topology.js +0 -565
  271. package/dist/triggers.js +0 -184
  272. package/dist/trust-audit.js +0 -644
  273. package/dist/types/blackboard.js +0 -2
  274. package/dist/types/candidate.js +0 -2
  275. package/dist/types/collaboration.js +0 -2
  276. package/dist/types/core.js +0 -2
  277. package/dist/types/drive.js +0 -10
  278. package/dist/types/error-feedback.js +0 -2
  279. package/dist/types/evidence-reasoning.js +0 -2
  280. package/dist/types/execution-backend.js +0 -2
  281. package/dist/types/multi-agent.js +0 -2
  282. package/dist/types/observability.js +0 -2
  283. package/dist/types/pipeline.js +0 -2
  284. package/dist/types/reclamation.js +0 -8
  285. package/dist/types/report-bundle.js +0 -6
  286. package/dist/types/result.js +0 -2
  287. package/dist/types/run-registry.js +0 -2
  288. package/dist/types/run.js +0 -2
  289. package/dist/types/sandbox.js +0 -2
  290. package/dist/types/schedule.js +0 -2
  291. package/dist/types/state-node.js +0 -2
  292. package/dist/types/topology.js +0 -2
  293. package/dist/types/trust.js +0 -2
  294. package/dist/types/workbench.js +0 -2
  295. package/dist/types/worker.js +0 -2
  296. package/dist/types/workflow-app.js +0 -2
  297. package/dist/types.js +0 -44
  298. package/dist/util/fingerprint.js +0 -19
  299. package/dist/util/fingerprint.test.js +0 -27
  300. package/dist/verifier.js +0 -78
  301. package/dist/version.js +0 -8
  302. package/dist/workbench-host.js +0 -192
  303. package/dist/workbench.js +0 -192
  304. package/dist/worker-accept/acceptance.js +0 -114
  305. package/dist/worker-accept/blackboard-fanout.js +0 -80
  306. package/dist/worker-accept/blackboard-linkage.js +0 -19
  307. package/dist/worker-accept/context.js +0 -2
  308. package/dist/worker-accept/telemetry-ledger.js +0 -126
  309. package/dist/worker-accept/validation.js +0 -77
  310. package/dist/worker-accept/verifier-completion.js +0 -73
  311. package/dist/worker-isolation/helpers.js +0 -51
  312. package/dist/worker-isolation/paths.js +0 -46
  313. package/dist/worker-isolation.js +0 -656
  314. package/dist/workflow-api.js +0 -131
  315. /package/dist/{types → core/types}/boundary.js +0 -0
@@ -1,1189 +0,0 @@
1
- "use strict";
2
- // Shared capability core: the SINGLE source of truth for composite capabilities
3
- // whose payload is assembled from more than one orchestrator call.
4
- //
5
- // BSD discipline (mechanism vs policy): these functions are MECHANISM. They live
6
- // here — never in cli.ts or mcp-server.ts — so the CLI and the MCP surface are
7
- // two renderings of ONE data source. Any capability that composes multiple
8
- // runner calls (plan summary, app run, sandbox choice, commit envelope) MUST be
9
- // expressed here and called identically by both surfaces. A composite that lives
10
- // in only one surface is exactly the cross-surface drift v0.1.27 forbids.
11
- //
12
- // Capability metadata (which entry is on which surface, tool names, jsonMode) is
13
- // declared in ONE place — the BUILTIN_CAPABILITIES table in capability-registry.ts,
14
- // the single source of truth both surfaces and the parity gate read. New
15
- // capabilities add a row there. (A v0.1.46 "self-register at load time" mechanism
16
- // was removed: the registry snapshot was taken before those registrations ran, so
17
- // they were silently dead duplicates of the table — see capability-registry.ts.)
18
- //
19
- // See docs/cli-mcp-parity.7.md and src/capability-registry.ts.
20
- var __importDefault = (this && this.__importDefault) || function (mod) {
21
- return (mod && mod.__esModule) ? mod : { "default": mod };
22
- };
23
- Object.defineProperty(exports, "__esModule", { value: true });
24
- exports.gcClones = exports.listClones = exports.QUICKSTART_DEFAULT_APP = void 0;
25
- exports.planSummary = planSummary;
26
- exports.appRun = appRun;
27
- exports.sandboxChoose = sandboxChoose;
28
- exports.commitEnvelope = commitEnvelope;
29
- exports.compactOperatorStatus = compactOperatorStatus;
30
- exports.runRegistryFor = runRegistryFor;
31
- exports.runRegistryRefresh = runRegistryRefresh;
32
- exports.runRegistryShow = runRegistryShow;
33
- exports.runSearch = runSearch;
34
- exports.runList = runList;
35
- exports.runShow = runShow;
36
- exports.runResume = runResume;
37
- exports.runArchive = runArchive;
38
- exports.runRerun = runRerun;
39
- exports.runExportArchive = runExportArchive;
40
- exports.runImportArchive = runImportArchive;
41
- exports.runInspectArchive = runInspectArchive;
42
- exports.runVerifyImport = runVerifyImport;
43
- exports.runRestoreArchive = runRestoreArchive;
44
- exports.reportBundle = reportBundle;
45
- exports.runVerifyReportBundle = runVerifyReportBundle;
46
- exports.queueAdd = queueAdd;
47
- exports.queueList = queueList;
48
- exports.queueDrain = queueDrain;
49
- exports.queueShow = queueShow;
50
- exports.schedPlan = schedPlan;
51
- exports.schedLease = schedLease;
52
- exports.schedRelease = schedRelease;
53
- exports.schedComplete = schedComplete;
54
- exports.schedReclaim = schedReclaim;
55
- exports.schedReset = schedReset;
56
- exports.schedPolicyShow = schedPolicyShow;
57
- exports.schedPolicySet = schedPolicySet;
58
- exports.runDrivePreview = runDrivePreview;
59
- exports.runDrive = runDrive;
60
- exports.collectRunFindings = collectRunFindings;
61
- exports.quickstart = quickstart;
62
- exports.backendAgentConfigShow = backendAgentConfigShow;
63
- exports.backendAgentConfigSet = backendAgentConfigSet;
64
- exports.gcPlan = gcPlan;
65
- exports.gcRun = gcRun;
66
- exports.gcVerify = gcVerify;
67
- exports.runHistory = runHistory;
68
- exports.metricsSummary = metricsSummary;
69
- exports.sandboxProfileIdFrom = sandboxProfileIdFrom;
70
- exports.withoutRuntimeKeys = withoutRuntimeKeys;
71
- exports.optionalString = optionalString;
72
- exports.isRecord = isRecord;
73
- exports.telemetryVerify = telemetryVerify;
74
- exports.auditVerify = auditVerify;
75
- exports.demoTamper = demoTamper;
76
- exports.demoBundle = demoBundle;
77
- const drive_1 = require("./drive");
78
- const agent_config_1 = require("./agent-config");
79
- const run_registry_1 = require("./run-registry");
80
- const observability_1 = require("./observability");
81
- const telemetry_ledger_1 = require("./telemetry-ledger");
82
- const telemetry_attestation_1 = require("./telemetry-attestation");
83
- const trust_audit_1 = require("./trust-audit");
84
- const remote_source_1 = require("./remote-source");
85
- const telemetry_demo_1 = require("./telemetry-demo");
86
- const state_1 = require("./state");
87
- const run_export_1 = require("./run-export");
88
- const result_normalize_1 = require("./result-normalize");
89
- const cli_options_1 = require("./orchestrator/cli-options");
90
- const node_fs_1 = __importDefault(require("node:fs"));
91
- const node_path_1 = __importDefault(require("node:path"));
92
- const scheduling_1 = require("./scheduling");
93
- // ---- canonical plan payload -----------------------------------------------
94
- // Both `cw plan` (default + --json) and `cw_plan` resolve to this exact object.
95
- function planSummary(runner, workflowId, options) {
96
- const run = runner.plan(workflowId, options);
97
- return {
98
- runId: run.id,
99
- workflowId: run.workflow.id,
100
- statePath: run.paths.state,
101
- reportPath: run.paths.report,
102
- pendingTasks: run.tasks.filter((task) => task.status === "pending").length
103
- };
104
- }
105
- // ---- canonical app-run payload --------------------------------------------
106
- // Both `cw app run` and `cw_app_run` resolve to this exact object. Structured
107
- // app inputs + optional sandbox resolution, then a compact operator status.
108
- function appRun(runner, args) {
109
- const appId = String(args.appId || args.workflowId || "");
110
- const inputs = isRecord(args.inputs) ? args.inputs : {};
111
- const planOptions = { ...inputs, ...withoutRuntimeKeys(args) };
112
- const sandboxProfileId = sandboxProfileIdFrom(args);
113
- const resolvedSandbox = sandboxProfileId ? runner.showSandboxProfile(sandboxProfileId, args) : undefined;
114
- const run = runner.plan(appId, planOptions);
115
- const status = runner.operatorStatus(run.id);
116
- return {
117
- runId: run.id,
118
- workflowId: run.workflow.id,
119
- appId: run.workflow.app?.id || appId,
120
- appVersion: run.workflow.app?.version,
121
- statePath: run.paths.state,
122
- reportPath: run.paths.report,
123
- pendingTasks: run.tasks.filter((task) => task.status === "pending").length,
124
- operatorStatus: compactOperatorStatus(status),
125
- nextActions: status.nextActions,
126
- sandboxProfileId,
127
- sandboxProfile: resolvedSandbox
128
- };
129
- }
130
- // ---- canonical sandbox choice payload -------------------------------------
131
- // Both `cw sandbox choose|resolve` and `cw_sandbox_choose|cw_sandbox_resolve`
132
- // resolve to this exact object.
133
- function sandboxChoose(runner, args) {
134
- const profileId = sandboxProfileIdFrom(args) || "readonly";
135
- const profile = runner.showSandboxProfile(profileId, args);
136
- return {
137
- profileId,
138
- sandboxProfileId: profile.id,
139
- valid: true,
140
- profile
141
- };
142
- }
143
- // ---- canonical commit envelope --------------------------------------------
144
- // `cw_commit` resolves to this operator-facing envelope. The CLI `commit`
145
- // command intentionally emits the raw StateCommitResult for scripting; both
146
- // derive from the single core entry runner.commit (declared, not drift — see
147
- // the capability registry's `commit` descriptor).
148
- function commitEnvelope(runner, runId, args) {
149
- const result = runner.commit(runId, args);
150
- const commit = result.commit;
151
- const status = runner.operatorStatus(runId);
152
- return {
153
- runId,
154
- commitId: commit.id,
155
- verifierGated: commit.verifierGated,
156
- checkpoint: commit.checkpoint,
157
- verifierNodeId: commit.verifierNodeId,
158
- candidateId: commit.candidateId,
159
- selectionId: commit.selectionId,
160
- evidenceCount: (commit.evidence || []).length,
161
- snapshotPath: commit.snapshotPath,
162
- nextActions: status.nextActions,
163
- commit
164
- };
165
- }
166
- function compactOperatorStatus(status) {
167
- return {
168
- runId: status.runId,
169
- workflowId: status.workflowId,
170
- appId: status.appId,
171
- appVersion: status.appVersion,
172
- loopStage: status.loopStage,
173
- activePhase: status.activePhase,
174
- blocked: status.blocked,
175
- blockedReasons: status.blockedReasons,
176
- pendingTasks: status.tasks.pending.length,
177
- runningTasks: status.tasks.running.length,
178
- completedTasks: status.tasks.completed.length,
179
- nextActions: status.nextActions
180
- };
181
- }
182
- // ---- run registry / control plane (v0.1.28) -------------------------------
183
- // MECHANISM, ONE SOURCE: the CLI and MCP surfaces both route through these
184
- // functions so `cw <cmd> --json` is byte-identical to `cw_<tool>`. Each accepts
185
- // the raw CLI options OR the raw MCP arguments and normalizes them identically,
186
- // then calls the single RunRegistry method. The registry is constructed from the
187
- // same resolved cwd on both surfaces (CLI: --cwd|process.cwd(); MCP passes a
188
- // resolved cwd and scopes the runner), so repo/home roots line up.
189
- function runRegistryFor(args, planner) {
190
- return new run_registry_1.RunRegistry(String(args.cwd || process.cwd()), planner);
191
- }
192
- function scopeOf(args, fallback) {
193
- if (args.scope === "repo")
194
- return "repo";
195
- if (args.scope === "home")
196
- return "home";
197
- return fallback;
198
- }
199
- function lifecycleOf(value) {
200
- return (0, run_registry_1.isRunLifecycleState)(value) ? value : undefined;
201
- }
202
- function flag(value) {
203
- if (value === undefined)
204
- return undefined;
205
- if (value === false || value === "false" || value === "no" || value === "0")
206
- return false;
207
- return Boolean(value);
208
- }
209
- // F7: explicit invocation cwd — no more process.chdir bracket.
210
- // The runner resolves a run from process.cwd() by default; to operate WITH a run's
211
- // repo as base (run --drive --repo X from anywhere; cross-directory quickstart; run
212
- // import/export against a target dir) we now pass that base EXPLICITLY —
213
- // runner.withBaseDir(dir).loadRun(...) for run resolution (see
214
- // CoolWorkflowRunner.withBaseDir), and invocationCwd(args) to anchor relative path
215
- // args. Nothing mutates the global process.cwd(), so concurrent in-process callers
216
- // can no longer corrupt each other's working directory (the former reentrancy hazard).
217
- function invocationCwd(args) {
218
- return node_path_1.default.resolve(optionalString(args.cwd) || process.cwd());
219
- }
220
- function runRegistryRefresh(reg, args) {
221
- return reg.refresh({ scope: scopeOf(args, "repo") });
222
- }
223
- function runRegistryShow(reg, args) {
224
- return reg.show({ scope: scopeOf(args, "repo") });
225
- }
226
- function runSearch(reg, args) {
227
- return reg.search({
228
- scope: scopeOf(args, "home"),
229
- text: optionalString(args.text || args.q || args.query),
230
- app: optionalString(args.app || args.appId),
231
- status: lifecycleOf(args.status),
232
- repo: optionalString(args.repo),
233
- since: optionalString(args.since),
234
- until: optionalString(args.until),
235
- includeArchived: flag(args.includeArchived ?? args["include-archived"]),
236
- limit: args.limit === undefined ? undefined : Number(args.limit),
237
- offset: args.offset === undefined ? undefined : Number(args.offset)
238
- });
239
- }
240
- function runList(reg, args) {
241
- return reg.list({
242
- scope: scopeOf(args, "home"),
243
- includeArchived: flag(args.includeArchived ?? args["include-archived"]),
244
- limit: args.limit === undefined ? undefined : Number(args.limit),
245
- offset: args.offset === undefined ? undefined : Number(args.offset)
246
- });
247
- }
248
- function runShow(reg, runId, args) {
249
- return reg.showRun(runId, { scope: scopeOf(args, "home") });
250
- }
251
- function runResume(reg, runner, runId, args) {
252
- const base = reg.resume(runId, {
253
- scope: scopeOf(args, "home"),
254
- limit: args.limit === undefined ? undefined : Number(args.limit)
255
- });
256
- // Default (no --drive/--once): read-only, byte-identical to before.
257
- if (!isTrue(args.drive) && !isTrue(args.once))
258
- return base;
259
- // Opt-in continuation: hand the resolved run to the EXISTING agent-delegation
260
- // drive loop (re-plans nothing; picks up pending/running tasks from durable
261
- // state). An unconfigured agent surfaces drive.status="blocked" (fail-closed).
262
- const drive = runDrive(runner, { ...args, runId: base.runId, repo: base.repo, once: isTrue(args.once) });
263
- return { ...base, drive };
264
- }
265
- function runArchive(reg, runId, args) {
266
- if (runId) {
267
- return reg.archive(runId, {
268
- scope: scopeOf(args, "home"),
269
- reason: optionalString(args.reason),
270
- unarchive: flag(args.unarchive)
271
- });
272
- }
273
- const days = Number(args.olderThanDays ?? args["older-than-days"]);
274
- const states = parseLifecycleList(args.state ?? args.status);
275
- return reg.archiveByPolicy({
276
- schemaVersion: 1,
277
- archiveOlderThanDays: Number.isFinite(days) ? days : 0,
278
- archiveStates: states.length ? states : ["completed", "failed"],
279
- defaultQueuePriority: 100
280
- }, { scope: scopeOf(args, "home") });
281
- }
282
- function runRerun(reg, runId, args) {
283
- return reg.rerun(runId, { scope: scopeOf(args, "home"), reason: optionalString(args.reason) });
284
- }
285
- function runExportArchive(runner, runId, args) {
286
- const base = invocationCwd(args);
287
- const output = optionalString(args.output || args.path || args.archive) || `${runId}.cwrun.json`;
288
- // Optionally seal in the operator's PUBLIC trust key so the bundle re-verifies
289
- // offline. Default falls back to the same env the verify gate reads, so a single
290
- // configured key both attests at record-time and travels with the export.
291
- const trustPublicKey = optionalString(args["with-trust-key"] || args.withTrustKey || args.trustKey || args.pubkey) || process.env.CW_AGENT_ATTEST_PUBKEY;
292
- const resolvedOutput = node_path_1.default.resolve(base, output);
293
- const sysDirs = /^\/(etc|bin|sbin|usr|Library|System|Applications|boot|dev|proc|sys|root|var\/log|var\/run)\//;
294
- if (sysDirs.test(resolvedOutput)) {
295
- throw new Error(`Refusing to write archive to a system directory: ${output}`);
296
- }
297
- return (0, run_export_1.exportRun)(runner.withBaseDir(optionalString(args.cwd)).loadRun(runId), resolvedOutput, { trustPublicKey });
298
- }
299
- function runImportArchive(runner, args) {
300
- const base = invocationCwd(args);
301
- const archive = optionalString(args.archive || args.path || args.file);
302
- if (!archive)
303
- throw new Error("run import requires an archive path (positional, --archive, --path, or --file)");
304
- const target = node_path_1.default.resolve(base, optionalString(args.target || args.repo || args.cwd) || base);
305
- const imported = (0, run_export_1.importRun)(node_path_1.default.resolve(base, archive), target);
306
- const registry = new run_registry_1.RunRegistry(target, runner.withBaseDir(target));
307
- const registryReport = registry.refresh({ scope: "repo" });
308
- return { ...imported, registry: registryReport };
309
- }
310
- // Read-only: inspect a portable archive's integrity WITHOUT importing it. Routes
311
- // both surfaces through one shared core entry. The runner is unused (no registry
312
- // touch — inspection writes nothing) but kept for dispatch-signature symmetry.
313
- function runInspectArchive(_runner, args) {
314
- const base = invocationCwd(args);
315
- const archive = optionalString(args.archive || args.path || args.file);
316
- if (!archive)
317
- throw new Error("run inspect-archive requires an archive path (positional, --archive, --path, or --file)");
318
- return (0, run_export_1.inspectArchive)(node_path_1.default.resolve(base, archive));
319
- }
320
- function runVerifyImport(runner, runId, args) {
321
- return (0, run_export_1.verifyImportedRun)(runner.withBaseDir(optionalString(args.cwd)).loadRun(runId));
322
- }
323
- // Fail-closed atomic restore of a portable run archive. `run import` runs a
324
- // verification (importRun returns it as ImportResult.verification) but does NOT
325
- // fail on it — it exits 0 even when the telemetry-ledger or trust-audit hash chain
326
- // does not verify, so a chain-tampered run imports with a fabricated success.
327
- // `run restore` closes that gap in ONE step: it integrity-INSPECTS the archive
328
- // FIRST (writing nothing), refuses a bad bundle before any import, then IMPORTS
329
- // and REUSES importRun's verification verdict — reporting ok:true ONLY when verify
330
- // passes. Pure composition of the existing functions (inspectArchive + importRun,
331
- // whose own verification we reuse); no new crypto or IO logic, and no second
332
- // re-hash. The CLI/MCP surfaces map ok:false to exit 1.
333
- function runRestoreArchive(runner, args) {
334
- const base = invocationCwd(args);
335
- const archive = optionalString(args.archive || args.path || args.file);
336
- if (!archive)
337
- throw new Error("run restore requires an archive path (positional, --archive, --path, or --file)");
338
- const resolvedArchive = node_path_1.default.resolve(base, archive);
339
- const target = node_path_1.default.resolve(base, optionalString(args.target || args.repo || args.cwd) || base);
340
- // (1) Integrity-inspect FIRST — read-only, writes nothing. A bad bundle is
341
- // refused here, before any import touches the target tree (no partial restore).
342
- const inspect = (0, run_export_1.inspectArchive)(resolvedArchive);
343
- if (!inspect.ok) {
344
- return { schemaVersion: 1, ok: false, target, inspect, imported: null, verify: null, registry: null };
345
- }
346
- // (2) Intact: import + refresh the target registry (mirrors runImportArchive).
347
- const imported = (0, run_export_1.importRun)(resolvedArchive, target);
348
- const registry = new run_registry_1.RunRegistry(target, runner.withBaseDir(target));
349
- const registryReport = registry.refresh({ scope: "repo" });
350
- // (3) REUSE the verification importRun already ran — it re-proves the same
351
- // restored files PLUS the telemetry-ledger and trust-audit hash chains, but
352
- // returns the verdict WITHOUT throwing. We do NOT re-run verifyImportedRun (that
353
- // would re-hash every file a second time for no new information). The gap restore
354
- // closes is exactly that `run import` ships this verdict and exits 0 even when it
355
- // is false — restore fails-closed on it below (CLI maps ok:false → exit 1).
356
- const verify = imported.verification;
357
- return {
358
- schemaVersion: 1,
359
- // inspect.ok is guaranteed true here — the corrupt-archive case early-returned
360
- // above — so the verdict is purely verify.ok (the telemetry/trust-audit chain).
361
- ok: verify.ok,
362
- target,
363
- inspect,
364
- imported: { ...imported, registry: registryReport },
365
- verify,
366
- registry: registryReport
367
- };
368
- }
369
- // Produce-and-prove: export a run to a portable bundle sealed with the operator's
370
- // trust key (defaulting to CW_AGENT_ATTEST_PUBKEY, same as `run export`), then
371
- // IMMEDIATELY verify the artifact offline the way a recipient will. The producer
372
- // learns now — fail-closed — whether the bundle a client will check is actually
373
- // verifiable (e.g. an unconfigured attest key yields an unverifiable bundle). Pure
374
- // composition of runExportArchive + verifyReportBundle; spawns nothing, writes only
375
- // the archive (and, with --extract-report, the human report) that `run export` would.
376
- function reportBundle(runner, runId, args) {
377
- const exported = runExportArchive(runner, runId, args);
378
- const base = invocationCwd(args);
379
- const extractReportTo = optionalString(args["extract-report"] || args.extractReport || args.extractReportTo);
380
- const verification = (0, run_export_1.verifyReportBundle)(exported.path, {
381
- pubkey: optionalString(args.pubkey || args.pubKey || args.publicKey),
382
- extractReportTo: extractReportTo ? node_path_1.default.resolve(base, extractReportTo) : undefined,
383
- strictSignatures: Boolean(args["strict-signatures"] || args.strictSignatures || args.strictSigs),
384
- requireSignatures: Boolean(args["require-signatures"] || args.requireSignatures || args.requireSigs)
385
- });
386
- return {
387
- schemaVersion: 1,
388
- runId,
389
- archivePath: exported.path,
390
- trustKeyEmbedded: exported.trustKeyEmbedded,
391
- reportExtractedTo: verification.reportExtractedTo,
392
- verification,
393
- ok: verification.ok
394
- };
395
- }
396
- // Read-only: verify a portable run bundle OFFLINE and self-contained (archive bytes
397
- // + telemetry chain + trust-audit chain + embedded-key signatures). The runner is
398
- // unused — verification restores into its own throwaway tmpdir and writes nothing to
399
- // any registry — but kept for dispatch-signature symmetry with the other run verbs.
400
- function runVerifyReportBundle(_runner, args) {
401
- const base = invocationCwd(args);
402
- const archive = optionalString(args.archive || args.path || args.file || args.bundle);
403
- if (!archive)
404
- throw new Error("report verify-bundle requires a bundle path (positional, --archive, --path, --file, or --bundle)");
405
- const extractReportTo = optionalString(args["extract-report"] || args.extractReport || args.extractReportTo);
406
- return (0, run_export_1.verifyReportBundle)(node_path_1.default.resolve(base, archive), {
407
- pubkey: optionalString(args.pubkey || args.pubKey || args.publicKey),
408
- extractReportTo: extractReportTo ? node_path_1.default.resolve(base, extractReportTo) : undefined,
409
- strictSignatures: Boolean(args["strict-signatures"] || args.strictSignatures || args.strictSigs),
410
- requireSignatures: Boolean(args["require-signatures"] || args.requireSignatures || args.requireSigs)
411
- });
412
- }
413
- function queueAdd(reg, args) {
414
- return reg.queueAdd({
415
- runId: optionalString(args.runId),
416
- appId: optionalString(args.appId || args.app),
417
- workflowId: optionalString(args.workflowId || args.workflow),
418
- repo: optionalString(args.repo),
419
- priority: args.priority === undefined ? undefined : Number(args.priority),
420
- note: optionalString(args.note),
421
- id: optionalString(args.id)
422
- });
423
- }
424
- function queueList(reg, args) {
425
- const status = optionalString(args.status);
426
- return reg.queueList({
427
- status: status,
428
- repo: optionalString(args.repo)
429
- });
430
- }
431
- function queueDrain(reg, args) {
432
- return reg.queueDrain({
433
- limit: args.limit === undefined ? undefined : Number(args.limit),
434
- repo: optionalString(args.repo)
435
- });
436
- }
437
- function queueShow(reg, id) {
438
- return reg.queueShow(id);
439
- }
440
- // ---- control-plane scheduling (v0.1.37) -----------------------------------
441
- function loadSchedulingPolicy(reg) {
442
- const file = reg.schedulingPolicyPath();
443
- // Absent policy => conservative DEFAULT (an unconfigured backend, which §4
444
- // permits). But a PRESENT-but-corrupt policy must fail closed: silently
445
- // substituting defaults would schedule under settings the operator never
446
- // chose while their broken file sits on disk. Let readJson's throw surface it.
447
- if (node_fs_1.default.existsSync(file)) {
448
- return { policy: (0, scheduling_1.normalizeSchedulingPolicy)((0, state_1.readJson)(file)), source: "file" };
449
- }
450
- return { policy: scheduling_1.DEFAULT_SCHEDULING_POLICY, source: "default" };
451
- }
452
- function schedNow(args) {
453
- return optionalString(args.now) || new Date().toISOString();
454
- }
455
- function isTrue(value) {
456
- return value === true || value === "true" || value === "1";
457
- }
458
- function schedPlan(reg, args) {
459
- return (0, scheduling_1.planSchedule)(reg.loadQueueEntries(), loadSchedulingPolicy(reg).policy, schedNow(args));
460
- }
461
- function schedLease(reg, args) {
462
- const now = schedNow(args);
463
- const policy = loadSchedulingPolicy(reg).policy;
464
- const limit = args.limit === undefined ? undefined : Number(args.limit);
465
- const { entries, leases } = (0, scheduling_1.applyLease)(reg.loadQueueEntries(), policy, now, limit);
466
- reg.saveQueueEntries(entries);
467
- return { schemaVersion: 1, now, granted: leases.length, leases };
468
- }
469
- function schedRelease(reg, args) {
470
- const now = schedNow(args);
471
- const failed = isTrue(args.failed);
472
- const { entries, matched } = (0, scheduling_1.leaseRelease)(reg.loadQueueEntries(), String(args.leaseId || ""), loadSchedulingPolicy(reg).policy, now, {
473
- failed,
474
- reason: optionalString(args.reason)
475
- });
476
- if (!matched)
477
- throw new Error(`No active lease to release: ${args.leaseId}`);
478
- reg.saveQueueEntries(entries);
479
- return { schemaVersion: 1, released: String(args.leaseId || ""), failed };
480
- }
481
- function schedComplete(reg, args) {
482
- const { entries, matched } = (0, scheduling_1.leaseComplete)(reg.loadQueueEntries(), String(args.leaseId || ""), schedNow(args));
483
- if (!matched)
484
- throw new Error(`No active lease to complete: ${args.leaseId}`);
485
- reg.saveQueueEntries(entries);
486
- return { schemaVersion: 1, completed: String(args.leaseId || "") };
487
- }
488
- function schedReclaim(reg, args) {
489
- const now = schedNow(args);
490
- const { entries, reclaimed } = (0, scheduling_1.reclaimExpired)(reg.loadQueueEntries(), loadSchedulingPolicy(reg).policy, now);
491
- reg.saveQueueEntries(entries);
492
- return { schemaVersion: 1, now, reclaimed };
493
- }
494
- function schedReset(reg, args) {
495
- const { entries, matched } = (0, scheduling_1.resetEntry)(reg.loadQueueEntries(), String(args.id || ""));
496
- if (!matched)
497
- throw new Error(`No parked entry to reset: ${args.id}`);
498
- reg.saveQueueEntries(entries);
499
- return { schemaVersion: 1, reset: String(args.id || "") };
500
- }
501
- function schedPolicyShow(reg) {
502
- const { policy, source } = loadSchedulingPolicy(reg);
503
- return { schemaVersion: 1, policy, source };
504
- }
505
- function schedPolicySet(reg, args) {
506
- const current = loadSchedulingPolicy(reg).policy;
507
- const patch = {};
508
- for (const key of ["maxConcurrent", "maxAttempts", "leaseTtlMs", "backoffBaseMs", "backoffFactor", "backoffCapMs"]) {
509
- if (args[key] === undefined)
510
- continue;
511
- // Fail closed on a non-numeric flag instead of letting normalizeSchedulingPolicy
512
- // silently substitute the DEFAULT (which would report source:"file" + exit 0,
513
- // so the operator believes they set a value they didn't). Matches the
514
- // Number.isFinite guard the sibling reclaimPolicyFrom already uses.
515
- const value = Number(args[key]);
516
- if (!Number.isFinite(value)) {
517
- throw new Error(`Invalid --${key} "${String(args[key])}": expected a number (e.g. --${key} 4)`);
518
- }
519
- patch[key] = value;
520
- }
521
- const policy = (0, scheduling_1.normalizeSchedulingPolicy)({ ...current, ...patch });
522
- (0, state_1.writeJson)(reg.schedulingPolicyPath(), policy);
523
- return { schemaVersion: 1, policy, source: "file" };
524
- }
525
- // ---- agent delegation drive (v0.1.38) -------------------------------------
526
- // MECHANISM, ONE SOURCE: both surfaces route drive/preview/config through these.
527
- // The read-only preview + config-show payloads are deterministic (counts from
528
- // state, host-stable config path) with NO now-derived numeric field, so
529
- // `cw <cmd> --json` is byte-identical to `cw_<tool>`.
530
- /** Read-only, deterministic preview of a run's NEXT drive step. */
531
- function runDrivePreview(runner, args) {
532
- return (0, drive_1.drivePreview)(runner, String(args.runId || ""), args);
533
- }
534
- const DRIVE_RUNTIME_KEYS = [
535
- "once",
536
- "now",
537
- "preview",
538
- "step",
539
- "drive",
540
- "json",
541
- "format",
542
- "run",
543
- "runId",
544
- "cwd",
545
- "agentCommand",
546
- "agent-command",
547
- "agentArgs",
548
- "agent-args",
549
- "agentEndpoint",
550
- "agent-endpoint",
551
- "agentModel",
552
- "agent-model",
553
- "agentTimeoutMs",
554
- "agent-timeout-ms",
555
- "resume",
556
- "incremental",
557
- "concurrency",
558
- // Remote-source flags (v0.1.91): materialized into a local checkout in the capability
559
- // layer, never passed to plan as inputs (the resolved sourceUrl/sourceCommit ARE inputs).
560
- "link",
561
- "ref",
562
- "branch",
563
- "refresh"
564
- ];
565
- function planInputsFor(args) {
566
- const copy = withoutRuntimeKeys(args);
567
- for (const key of DRIVE_RUNTIME_KEYS)
568
- delete copy[key];
569
- return copy;
570
- }
571
- /** Mutating drive step/run. Plans a fresh run for an app id, or continues a run id.
572
- * The agent hop goes ONLY through the agent backend; this composes existing verbs.
573
- *
574
- * The run lives under its repo's `.cw/` and every runner verb resolves a run from
575
- * the process cwd, so the drive operates WITH the run's repo as cwd (exactly how
576
- * the golden path runs each verb from the repo dir) — then restores cwd. This lets
577
- * `cw run <app> --drive --repo X` work when invoked from anywhere. */
578
- function runDrive(runner, args) {
579
- let runId = optionalString(args.runId || args.run);
580
- let repoCwd = optionalString(args.repo);
581
- if (!runId) {
582
- const appId = String(args.appId || args.workflowId || args.app || "");
583
- if (!appId)
584
- throw new Error("run --drive requires an app id (or --run <run-id> to continue)");
585
- const run = runner.plan(appId, planInputsFor(args));
586
- runId = run.id;
587
- repoCwd = run.cwd;
588
- }
589
- // The runner resolves the run from its baseDir, so the drive must run WITH the
590
- // run's repo as base. Pass it explicitly via withBaseDir (F7 — no process.chdir).
591
- const target = repoCwd && node_fs_1.default.existsSync(repoCwd) ? repoCwd : undefined;
592
- const driveRunId = runId;
593
- return (0, drive_1.drive)(runner.withBaseDir(target), driveRunId, {
594
- once: isTrue(args.once),
595
- now: optionalString(args.now),
596
- incremental: isTrue(args.incremental),
597
- concurrency: (0, cli_options_1.numberOption)(args.concurrency),
598
- args
599
- });
600
- }
601
- /** The app the one-command quickstart plans when none is named. */
602
- exports.QUICKSTART_DEFAULT_APP = "architecture-review";
603
- /** ONE-COMMAND quickstart (v0.1.38+): plan(app) -> run --drive -> report in a single
604
- * invocation, so a newcomer gets a cited risk report from one command on a target
605
- * repo. This is a THIN UX wrapper — NOT a new engine: it composes the EXISTING
606
- * `runDrive` core (which already plans the run, then delegates every worker to the
607
- * configured agent backend and commits) and then writes the report. It introduces
608
- * no second executor, queue, or scheduler, and imports no model SDK.
609
- *
610
- * RED LINE (DIRECTION.md): worker execution still DELEGATES to the operator's own
611
- * agent backend (claude -p / codex exec / HTTP endpoint). With no agent configured
612
- * the drive fails closed (status=blocked) and we never fabricate a completion. */
613
- /** Collect a COMPACT findings list for the end-of-run summary by re-parsing each completed
614
- * worker's `result.md` `cw:result` block (the schema source of truth — `normalizeResultEnvelope`).
615
- * Stderr/human-side ONLY: this NEVER touches the byte-exact stdout payload, so it cannot perturb
616
- * `--json` or the `cw:result` fence. `baseDir` anchors to the run's OWN repo (quickstart may run
617
- * cross-directory). Best-effort: a missing/garbled result.md or an unloadable run is skipped, not
618
- * fatal — the full prose still lives in report.md + each worker transcript. */
619
- function collectRunFindings(runner, runId, baseDir) {
620
- const rows = [];
621
- try {
622
- const run = runner.withBaseDir(baseDir).loadRun(runId);
623
- for (const task of run.tasks) {
624
- if (task.status !== "completed" || !task.resultPath || !node_fs_1.default.existsSync(task.resultPath))
625
- continue;
626
- try {
627
- const envelope = (0, result_normalize_1.normalizeResultEnvelope)(node_fs_1.default.readFileSync(task.resultPath, "utf8"));
628
- for (const f of envelope.findings) {
629
- rows.push({ id: f.id, severity: f.severity || "none", classification: f.classification || "unknown" });
630
- }
631
- }
632
- catch {
633
- /* skip a garbled result.md — the transcript still holds the full record */
634
- }
635
- }
636
- }
637
- catch {
638
- /* run not loadable on this host — the summary degrades to no findings table */
639
- }
640
- return rows;
641
- }
642
- function quickstart(runner, args) {
643
- const appId = String(args.appId || args.app || args.workflowId || exports.QUICKSTART_DEFAULT_APP);
644
- // Remote source (v0.1.91): a `--link <url>` — or a URL passed to `--repo`/`-dir` — is
645
- // materialized to a LOCAL checkout in the capability layer (below). Cloning is
646
- // non-deterministic network I/O and must never enter the replay-deterministic core, so we
647
- // rewrite `args.repo` to the local path here; everything downstream is a normal local run.
648
- const linkArg = optionalString(args.link);
649
- const repoArgRaw = optionalString(args.repo);
650
- const remoteCandidate = linkArg || (repoArgRaw && (0, remote_source_1.isRemoteUrl)(repoArgRaw) ? repoArgRaw : undefined);
651
- // Run anywhere (like brew): default the repo-under-review to the caller's cwd when no
652
- // --repo/--cwd/--link is given. A remote candidate is materialized below, so it must NOT
653
- // fall through to the cwd default here.
654
- if (!remoteCandidate && !optionalString(args.repo) && !optionalString(args.cwd)) {
655
- args.repo = invocationCwd(args);
656
- }
657
- const agentConfigured = Boolean((0, agent_config_1.resolveAgentConfig)(args).command || (0, agent_config_1.resolveAgentConfig)(args).endpoint);
658
- if (isTrue(args.check)) {
659
- return remoteCandidate
660
- ? remoteQuickstartCheck(runner, appId, args, agentConfigured, remoteCandidate)
661
- : quickstartCheck(runner, appId, args, agentConfigured);
662
- }
663
- // Materialize the remote NOW — after `--check` (which never fetches) and before any
664
- // plan/preview/drive — so the orchestrator only ever sees the local checkout. Fails closed:
665
- // a bad URL / blocked scheme / network / auth failure throws here, before any run is planned.
666
- let remoteSource;
667
- if (remoteCandidate) {
668
- remoteSource = (0, remote_source_1.materializeRemote)(remoteCandidate, {
669
- ref: optionalString(args.ref || args.branch),
670
- refresh: isTrue(args.refresh)
671
- });
672
- args.repo = remoteSource.localPath;
673
- // Record the origin as plan INPUTS so it rides into run.inputs → the report header.
674
- args.sourceUrl = remoteSource.url;
675
- args.sourceCommit = remoteSource.commit;
676
- if (remoteSource.ref)
677
- args.sourceRef = remoteSource.ref;
678
- }
679
- // `--resume`: a discoverability flag over the existing continuation. With no
680
- // `--run`, advance exactly ONE step (reuse the `--once` path) and print a
681
- // copy-pasteable continue line; with `--run <id>`, continue that run to
682
- // completion (the default drive). It adds no new execution path.
683
- const resume = isTrue(args.resume);
684
- const resumeRunId = resume ? optionalString(args.runId || args.run) : undefined;
685
- // `--preview`: read-only, deterministic next-step projection (no spawn, no commit).
686
- // Plan a fresh run (the read-only first verb) then project the next drive step.
687
- if (isTrue(args.preview)) {
688
- let runId = optionalString(args.runId || args.run);
689
- let repoCwd = optionalString(args.repo);
690
- if (!runId) {
691
- const run = runner.plan(appId, planInputsFor(args));
692
- runId = run.id;
693
- repoCwd = run.cwd;
694
- }
695
- const previewRunId = runId;
696
- const target = repoCwd && node_fs_1.default.existsSync(repoCwd) ? repoCwd : undefined;
697
- return (0, drive_1.drivePreview)(runner.withBaseDir(target), previewRunId, args);
698
- }
699
- // Drive end-to-end (or one `--once` step). runDrive plans the run, delegates each
700
- // worker to the agent backend, and commits — we add only the report write + a
701
- // single assembled payload. No orchestration is duplicated here.
702
- // `--resume` with no run id advances a single step so a newcomer WITNESSES the
703
- // stop-then-resume; with a run id it continues to completion. Non-resume paths
704
- // are untouched (byte-identical default).
705
- const result = runDrive(runner, { ...args, appId, ...(resume && !resumeRunId ? { once: true } : {}) });
706
- // Always (re)write the report so the one command yields a report.md on disk, even
707
- // when the drive blocked/parked (a partial report is still useful triage).
708
- //
709
- // runDrive restored cwd, so the runs root would resolve against the CALLER's cwd
710
- // here — orphaning the run when quickstart is invoked cross-directory (cwd =
711
- // plugin dir, --repo elsewhere: the README's headline command). The run's
712
- // statePath (<repo>/.cw/runs/<id>/state.json) is authoritative however the run
713
- // was planned or continued; resolve to ITS repo BEFORE any run read, reentrant-safe.
714
- const runRepoCwd = node_path_1.default.resolve(node_path_1.default.dirname(result.statePath), "..", "..", "..");
715
- const reportTarget = node_fs_1.default.existsSync(runRepoCwd) ? runRepoCwd : undefined;
716
- const reportPath = runner.withBaseDir(reportTarget).report(result.runId).path;
717
- // Tamper-evident provenance: bind the remote origin (url@sha) into the run's hash-chained
718
- // trust-audit log so `cw audit verify` re-proves where the code came from. metadata is
719
- // auto-scrubbed of credentials by recordTrustAuditEvent → scrubMetadata. Best-effort: the
720
- // origin is already in run.inputs/report/the result, so a recording hiccup never fails the
721
- // review (additive trust evidence, not a gate).
722
- if (remoteSource) {
723
- try {
724
- const provRun = runner.withBaseDir(reportTarget).loadRun(result.runId);
725
- (0, trust_audit_1.recordTrustAuditEvent)(provRun, {
726
- kind: remoteSource.kind === "archive" ? "source.download" : "source.clone",
727
- decision: "recorded",
728
- source: "operator-recorded",
729
- metadata: { url: remoteSource.url, commit: remoteSource.commit, ref: remoteSource.ref || null, kind: remoteSource.kind, depth: 1 }
730
- });
731
- }
732
- catch {
733
- /* provenance is additive; never fail a completed review over an audit-log hiccup */
734
- }
735
- }
736
- // --bundle: after a COMPLETE drive, seal the run into a portable, self-verified
737
- // bundle so the one command yields a client-verifiable artifact. Pure composition
738
- // of reportBundle() (export sealed + offline self-verify); spawns nothing. Gated on
739
- // completion: a partial or blocked run is NEVER sealed (you must not ship an
740
- // uncommitted artifact).
741
- //
742
- // Run-state resolution MUST anchor to the run's OWN repo (reportTarget): the README
743
- // headline runs quickstart cross-directory (caller cwd != --repo), so a caller-cwd
744
- // loadRun would not find the run. But operator-supplied OUTPUT paths
745
- // (--output/--extract-report) and the default archive name resolve against the
746
- // CALLER's cwd — so artifacts land where the operator ran the command (and `&&
747
- // send out.md` works) and never pollute the analyzed repo's working tree, matching
748
- // standalone `report bundle`. Pre-resolving to absolute makes path.resolve(base, …)
749
- // inside reportBundle a no-op, so the run-repo cwd cannot reclaim them.
750
- let bundle;
751
- const wantsBundle = flag(args.bundle) === true;
752
- if (wantsBundle && result.status === "complete") {
753
- const callerBase = invocationCwd(args);
754
- const outArg = optionalString(args.output || args.path || args.archive);
755
- const extractArg = optionalString(args["extract-report"] || args.extractReport || args.extractReportTo);
756
- bundle = reportBundle(runner, result.runId, {
757
- ...args,
758
- cwd: reportTarget,
759
- output: node_path_1.default.resolve(callerBase, outArg || `${result.runId}.cwrun.json`),
760
- ...(extractArg ? { "extract-report": node_path_1.default.resolve(callerBase, extractArg) } : {})
761
- });
762
- }
763
- let hint;
764
- if (!agentConfigured) {
765
- hint =
766
- "agent backend not configured — set CW_AGENT_COMMAND (e.g. \"claude -p\") or pass --agent-command, then re-run. The one command DELEGATES worker execution to YOUR agent; it never executes a model itself.";
767
- }
768
- else if (result.status === "parked") {
769
- hint = `a worker parked past its retry budget — inspect: cw run show ${result.runId}`;
770
- }
771
- else if (result.status === "blocked") {
772
- hint = `the drive is blocked — inspect: cw run drive ${result.runId}`;
773
- }
774
- else if (result.status === "in-progress") {
775
- hint = resume
776
- ? `one step advanced — continue: cw quickstart ${appId} --run ${result.runId} --resume${wantsBundle ? " --bundle" : ""}`
777
- : `one step advanced (--once) — continue: cw quickstart ${appId} --run ${result.runId} --once`;
778
- }
779
- // --bundle on a run that didn't complete is a NO-OP, not silence: tell the operator
780
- // why nothing was sealed (Rule of Silence permits a human-facing hint).
781
- if (wantsBundle && result.status !== "complete") {
782
- hint = `${hint ? `${hint} ` : ""}--bundle skipped: the run did not complete (status=${result.status}); no bundle was sealed.`;
783
- }
784
- return {
785
- schemaVersion: 1,
786
- appId,
787
- runId: result.runId,
788
- workflowId: result.workflowId,
789
- status: result.status,
790
- plannedWorkers: result.plannedWorkers,
791
- completedWorkers: result.completedWorkers,
792
- parkedWorkers: result.parkedWorkers,
793
- commitId: result.commitId,
794
- reportPath,
795
- statePath: result.statePath,
796
- agentConfigured,
797
- steps: result.steps,
798
- hint,
799
- // Stamp resumedFrom ONLY when we continued an explicit run. Conditional spread
800
- // keeps the key absent on the default/fresh path (own-property absent + omitted
801
- // by JSON.stringify), so default output is byte-identical.
802
- ...(resumeRunId ? { resumedFrom: resumeRunId } : {}),
803
- // Same conditional-spread discipline: `bundle` is present only when --bundle ran
804
- // on a completed drive, so the default (no --bundle) payload is byte-identical.
805
- ...(bundle ? { bundle } : {}),
806
- // `remote` is present only when the review targeted a --link/URL source, so a local-repo
807
- // run stays byte-identical. Carries the sanitized origin + resolved commit for provenance.
808
- ...(remoteSource
809
- ? { remote: { url: remoteSource.url, commit: remoteSource.commit, kind: remoteSource.kind, cached: remoteSource.cached, ...(remoteSource.ref ? { ref: remoteSource.ref } : {}) } }
810
- : {})
811
- };
812
- }
813
- function quickstartCheck(runner, appId, args, agentConfigured) {
814
- const base = invocationCwd(args);
815
- const repoArg = optionalString(args.repo) || base;
816
- const repo = node_path_1.default.resolve(base, repoArg);
817
- const checks = [];
818
- try {
819
- runner.showApp(appId);
820
- checks.push({ name: "app", status: "ok", detail: `Workflow app ${appId} is available.` });
821
- }
822
- catch (error) {
823
- checks.push({
824
- name: "app",
825
- status: "blocked",
826
- detail: `Workflow app ${appId} is not available.`,
827
- fix: "Run `cw app list` and choose one of the listed app ids."
828
- });
829
- }
830
- let repoReadable = false;
831
- let repoStateWritable = false;
832
- try {
833
- const stat = node_fs_1.default.statSync(repo);
834
- repoReadable = stat.isDirectory();
835
- if (!repoReadable)
836
- throw new Error("not a directory");
837
- node_fs_1.default.accessSync(repo, node_fs_1.default.constants.R_OK);
838
- checks.push({ name: "repo", status: "ok", detail: `Repository path is readable (${repo}).` });
839
- }
840
- catch (error) {
841
- checks.push({
842
- name: "repo",
843
- status: "blocked",
844
- detail: `Repository path is not readable (${repo}).`,
845
- fix: "Pass --repo PATH for a readable repository directory."
846
- });
847
- }
848
- try {
849
- const cwDir = node_path_1.default.join(repo, ".cw");
850
- node_fs_1.default.accessSync(node_fs_1.default.existsSync(cwDir) ? cwDir : repo, node_fs_1.default.constants.W_OK);
851
- repoStateWritable = repoReadable;
852
- checks.push({ name: "repo-state", status: "ok", detail: "Run state location is writable." });
853
- }
854
- catch (error) {
855
- checks.push({
856
- name: "repo-state",
857
- status: "blocked",
858
- detail: "Run state location is not writable.",
859
- fix: "Use a writable repo, fix directory permissions, or pass --repo to a writable checkout."
860
- });
861
- }
862
- if (optionalString(args.question)) {
863
- checks.push({ name: "question", status: "ok", detail: "Question is set." });
864
- }
865
- else {
866
- checks.push({
867
- name: "question",
868
- status: "blocked",
869
- detail: "Question is missing.",
870
- fix: "Pass --question TEXT."
871
- });
872
- }
873
- if (agentConfigured) {
874
- checks.push({ name: "agent", status: "ok", detail: "Agent backend is configured." });
875
- }
876
- else {
877
- checks.push({
878
- name: "agent",
879
- status: "blocked",
880
- detail: "No agent backend is configured.",
881
- fix: "Pass --agent-command \"claude -p\", set $CW_AGENT_COMMAND, or use --agent-command builtin:claude."
882
- });
883
- }
884
- if (flag(args.bundle) === true) {
885
- const trustKey = optionalString(args["with-trust-key"] || args.withTrustKey || args.trustKey || args.pubkey) || process.env.CW_AGENT_ATTEST_PUBKEY;
886
- if (trustKey) {
887
- checks.push({ name: "bundle-trust-key", status: "ok", detail: "Bundle trust public key is configured." });
888
- }
889
- else if (Boolean(args["strict-signatures"] || args.strictSignatures || args.strictSigs)) {
890
- checks.push({
891
- name: "bundle-trust-key",
892
- status: "blocked",
893
- detail: "Strict signature verification needs a public trust key.",
894
- fix: "Pass --with-trust-key PATH or set $CW_AGENT_ATTEST_PUBKEY."
895
- });
896
- }
897
- else {
898
- checks.push({
899
- name: "bundle-trust-key",
900
- status: "warn",
901
- detail: "No public trust key is configured; unsigned or unkeyed bundles may verify with reduced signature proof.",
902
- fix: "Pass --with-trust-key PATH to embed the public key."
903
- });
904
- }
905
- }
906
- const ok = checks.every((check) => check.status !== "blocked") && repoStateWritable;
907
- return {
908
- schemaVersion: 1,
909
- mode: "check",
910
- ok,
911
- appId,
912
- repo,
913
- checks,
914
- nextCommand: quickstartNextCommand(appId, repo, args)
915
- };
916
- }
917
- /** Preflight for a `--link`/URL review: validate the URL SHAPE + tooling WITHOUT fetching
918
- * (a clone is heavy and side-effecting; --check stays read-only). Mirrors quickstartCheck's
919
- * app/question/agent sub-checks but swaps the local-repo readability checks for link+tooling.
920
- * `repo` carries the sanitized URL so the result reports what would be reviewed. */
921
- function remoteQuickstartCheck(runner, appId, args, agentConfigured, candidate) {
922
- const validation = (0, remote_source_1.validateRemoteUrl)(candidate);
923
- const checks = [];
924
- try {
925
- runner.showApp(appId);
926
- checks.push({ name: "app", status: "ok", detail: `Workflow app ${appId} is available.` });
927
- }
928
- catch (error) {
929
- checks.push({ name: "app", status: "blocked", detail: `Workflow app ${appId} is not available.`, fix: "Run `cw app list` and choose one of the listed app ids." });
930
- }
931
- if (validation.ok) {
932
- checks.push({ name: "link", status: "ok", detail: `Remote source is a valid ${validation.kind} URL (${validation.url}).` });
933
- }
934
- else {
935
- checks.push({ name: "link", status: "blocked", detail: `Remote source is not usable: ${validation.reason}.`, fix: "Pass a git URL (https/ssh/git/file or git@host:repo)." });
936
- }
937
- if ((0, remote_source_1.gitAvailable)()) {
938
- checks.push({ name: "tooling", status: "ok", detail: "git is available to clone the remote." });
939
- }
940
- else {
941
- checks.push({ name: "tooling", status: "blocked", detail: "git was not found on PATH.", fix: "Install git, then re-run." });
942
- }
943
- if (optionalString(args.question)) {
944
- checks.push({ name: "question", status: "ok", detail: "Question is set." });
945
- }
946
- else {
947
- checks.push({ name: "question", status: "blocked", detail: "Question is missing.", fix: "Pass --question TEXT." });
948
- }
949
- if (agentConfigured) {
950
- checks.push({ name: "agent", status: "ok", detail: "Agent backend is configured." });
951
- }
952
- else {
953
- checks.push({ name: "agent", status: "blocked", detail: "No agent backend is configured.", fix: "Pass --agent-command \"claude -p\", set $CW_AGENT_COMMAND, or use --agent-command builtin:claude." });
954
- }
955
- const ok = checks.every((check) => check.status !== "blocked");
956
- return {
957
- schemaVersion: 1,
958
- mode: "check",
959
- ok,
960
- appId,
961
- repo: validation.url,
962
- checks,
963
- nextCommand: `cw quickstart ${shellWord(appId)} --link ${shellWord(validation.url)}${optionalString(args.question) ? ` --question ${shellWord(String(args.question))}` : ""}`
964
- };
965
- }
966
- function quickstartNextCommand(appId, repo, args) {
967
- const parts = ["cw", "quickstart", shellWord(appId), "--repo", shellWord(repo)];
968
- const question = optionalString(args.question);
969
- if (question)
970
- parts.push("--question", shellWord(question));
971
- const command = optionalString(args.agentCommand || args["agent-command"]);
972
- if (command)
973
- parts.push("--agent-command", shellWord(command));
974
- if (flag(args.bundle) === true)
975
- parts.push("--bundle");
976
- const trustKey = optionalString(args["with-trust-key"] || args.withTrustKey || args.trustKey);
977
- if (trustKey)
978
- parts.push("--with-trust-key", shellWord(trustKey));
979
- if (Boolean(args["strict-signatures"] || args.strictSignatures || args.strictSigs))
980
- parts.push("--strict-signatures");
981
- return parts.join(" ");
982
- }
983
- function shellWord(value) {
984
- if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(value))
985
- return value;
986
- return `'${value.replace(/'/g, "'\\''")}'`;
987
- }
988
- /** Read-only, deterministic projection of the effective agent config (secret-stripped). */
989
- function backendAgentConfigShow(args) {
990
- return (0, agent_config_1.agentConfigShow)(args);
991
- }
992
- /** Persist the durable agent config (secret-stripped) and return the new state. */
993
- function backendAgentConfigSet(args) {
994
- (0, agent_config_1.setAgentConfigFile)(args);
995
- return (0, agent_config_1.agentConfigShow)(args);
996
- }
997
- // ---- run retention & provable reclamation (v0.1.39) -----------------------
998
- // MECHANISM, ONE SOURCE: both surfaces route gc plan/run/verify through these.
999
- // `gc plan`/`gc verify` are read-only + deterministic (only `generatedAt` is
1000
- // now-derived ISO, allowed by the parity rule); `gc run` is the disk-freeing tier.
1001
- function reclaimPolicyFrom(args) {
1002
- const policy = {};
1003
- const days = Number(args.reclaimAfterArchiveDays ?? args["reclaim-after-archive-days"] ?? args.olderThanDays ?? args["older-than-days"]);
1004
- if (Number.isFinite(days))
1005
- policy.reclaimAfterArchiveDays = days;
1006
- const keepScratch = flag(args.keepScratch ?? args["keep-scratch"]);
1007
- if (keepScratch !== undefined)
1008
- policy.keepScratch = keepScratch;
1009
- const keepSnapshots = flag(args.keepSnapshots ?? args["keep-snapshots"]);
1010
- if (keepSnapshots !== undefined)
1011
- policy.keepSnapshots = keepSnapshots;
1012
- const maxRuns = Number(args.maxReclaimRuns ?? args["max-reclaim-runs"]);
1013
- if (Number.isFinite(maxRuns))
1014
- policy.maxReclaimRuns = maxRuns;
1015
- const maxBytes = Number(args.maxReclaimBytes ?? args["max-reclaim-bytes"]);
1016
- if (Number.isFinite(maxBytes))
1017
- policy.maxReclaimBytes = maxBytes;
1018
- const states = parseLifecycleList(args.state ?? args.status);
1019
- if (states.length)
1020
- policy.reclaimStates = states;
1021
- return policy;
1022
- }
1023
- function gcPlan(reg, runId, args) {
1024
- return reg.gcPlan({ scope: scopeOf(args, "home"), runId: runId || optionalString(args.runId), policy: reclaimPolicyFrom(args), now: optionalString(args.now) });
1025
- }
1026
- function gcRun(reg, runId, args) {
1027
- return reg.gcRun({
1028
- scope: scopeOf(args, "home"),
1029
- runId: runId || optionalString(args.runId),
1030
- policy: reclaimPolicyFrom(args),
1031
- now: optionalString(args.now),
1032
- actor: optionalString(args.actor),
1033
- limit: args.limit === undefined ? undefined : Number(args.limit)
1034
- });
1035
- }
1036
- function gcVerify(reg, runId, args) {
1037
- return reg.gcVerify(runId, { scope: scopeOf(args, "home") });
1038
- }
1039
- // Remote-source clone cache (v0.1.91): list/reclaim the `~/.local/state/cool-workflow/clones`
1040
- // checkouts that `--link`/URL reviews populate. Pure filesystem work; both CLI and MCP route
1041
- // here so `cw clones …` and `cw_clones_…` are byte-identical.
1042
- var clones_1 = require("./clones");
1043
- Object.defineProperty(exports, "listClones", { enumerable: true, get: function () { return clones_1.listClones; } });
1044
- Object.defineProperty(exports, "gcClones", { enumerable: true, get: function () { return clones_1.gcClones; } });
1045
- function runHistory(reg, args) {
1046
- return reg.history({
1047
- scope: scopeOf(args, "home"),
1048
- app: optionalString(args.app || args.appId),
1049
- status: lifecycleOf(args.status),
1050
- limit: args.limit === undefined ? undefined : Number(args.limit),
1051
- offset: args.offset === undefined ? undefined : Number(args.offset)
1052
- });
1053
- }
1054
- // ---- observability + cost accounting (v0.1.31) ----------------------------
1055
- // MECHANISM, ONE SOURCE: both `cw metrics summary --json` and `cw_metrics_summary`
1056
- // route through this function. It enumerates the v0.1.28 registry (derived live
1057
- // from source), loads each run's durable state, and DERIVES the cross-repo
1058
- // rollup. Runs whose source is unreadable are counted in `unreadableRuns` (fail
1059
- // closed), never silently dropped. Pricing is POLICY via `--pricing`; `now` is
1060
- // injectable via `args.now` for eval/replay determinism.
1061
- function metricsSummary(reg, runner, args) {
1062
- const scope = scopeOf(args, "repo");
1063
- const report = reg.show({ scope });
1064
- const policy = (0, observability_1.loadCostPolicy)(args, runner.pluginRoot);
1065
- const now = optionalString(args.now) || new Date().toISOString();
1066
- const inputs = [];
1067
- let unreadableRuns = 0;
1068
- for (const record of report.index.records) {
1069
- try {
1070
- const loaded = (0, state_1.loadRunStateFile)(record.statePath, { dryRun: true });
1071
- if (loaded.report.status === "unsupported") {
1072
- unreadableRuns++;
1073
- continue;
1074
- }
1075
- inputs.push({
1076
- run: loaded.run,
1077
- repo: record.repo,
1078
- persistedFingerprint: (0, observability_1.loadPersistedMetricsFingerprint)(loaded.run)
1079
- });
1080
- }
1081
- catch {
1082
- unreadableRuns++;
1083
- }
1084
- }
1085
- return (0, observability_1.deriveMetricsSummary)(inputs, { now, scope, policy, unreadableRuns });
1086
- }
1087
- function parseLifecycleList(value) {
1088
- const raw = Array.isArray(value) ? value : value === undefined ? [] : [value];
1089
- const out = [];
1090
- for (const item of raw) {
1091
- if ((0, run_registry_1.isRunLifecycleState)(item))
1092
- out.push(item);
1093
- }
1094
- return out;
1095
- }
1096
- // ---- shared argument helpers ----------------------------------------------
1097
- function sandboxProfileIdFrom(args) {
1098
- return optionalString(args.sandbox || args.sandboxProfile || args.sandboxProfileId || args.profileId);
1099
- }
1100
- function withoutRuntimeKeys(args) {
1101
- const copy = { ...args };
1102
- for (const key of ["appId", "workflowId", "inputs", "sandbox", "sandboxProfile", "sandboxProfileId", "profileId"]) {
1103
- delete copy[key];
1104
- }
1105
- return copy;
1106
- }
1107
- function optionalString(value) {
1108
- if (value === undefined || value === null || value === "")
1109
- return undefined;
1110
- return String(value);
1111
- }
1112
- function isRecord(value) {
1113
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
1114
- }
1115
- // ---- telemetry attestation: read-only ledger verification (Track 1) --------
1116
- // Re-prove a run's telemetry chain offline: prevHash linkage + independent per-
1117
- // record hash recompute (never trusts the stored hash). The auditable claim made
1118
- // inspectable on demand — anyone can run this; a forged/edited record fails it.
1119
- function telemetryVerify(runner, args) {
1120
- const runId = optionalString(args.runId || args.run);
1121
- if (!runId)
1122
- throw new Error("telemetry verify requires a run id (cw telemetry verify <run-id>)");
1123
- const run = runner.loadRun(runId);
1124
- const v = (0, telemetry_ledger_1.verifyTelemetryLedger)(run);
1125
- // Opt-in independent signature re-verification. verifyTelemetryLedger re-proves
1126
- // the chain (so the stored attestation verdicts were not edited); supplying the
1127
- // trust public key (--pubkey / CW_AGENT_ATTEST_PUBKEY) additionally RE-RUNS the
1128
- // ed25519 check over each `attested` record's stored raw usage rather than
1129
- // trusting that verdict, so a forged signature can no longer ride a green chain.
1130
- const trustPublicKeyInput = optionalString(args.pubkey || args.pubKey || args.publicKey) || process.env.CW_AGENT_ATTEST_PUBKEY;
1131
- const trustPublicKey = (0, telemetry_attestation_1.resolveTrustPublicKey)(trustPublicKeyInput);
1132
- const keyChecks = trustPublicKeyInput && !trustPublicKey
1133
- ? [{ name: "signature-key", pass: false, code: "telemetry-pubkey-unreadable" }]
1134
- : [];
1135
- const sig = (0, telemetry_attestation_1.verifyTelemetrySignatures)(v.records, trustPublicKey);
1136
- const failedChecks = [...v.checks.filter((c) => !c.pass), ...keyChecks, ...sig.checks.filter((c) => !c.pass)];
1137
- return {
1138
- schemaVersion: 1,
1139
- runId: run.id,
1140
- present: v.present,
1141
- // Chain integrity AND (when a key was supplied) every attested signature must
1142
- // re-verify. With no key, sig.failed is 0 → unchanged chain-only behavior.
1143
- verified: v.verified && keyChecks.length === 0 && sig.failed === 0,
1144
- records: v.records.length,
1145
- attested: v.attested,
1146
- unattested: v.unattested,
1147
- absent: v.absent,
1148
- signatureKeyProvided: sig.keyProvided,
1149
- signaturesChecked: sig.checked,
1150
- signaturesReverified: sig.reverified,
1151
- signaturesFailed: sig.failed,
1152
- failedChecks: failedChecks.map((c) => ({ name: c.name, code: c.code }))
1153
- };
1154
- }
1155
- // audit.verify — fail-closed re-prove of a run's trust-audit hash chain. The peer
1156
- // of telemetry.verify for the sandbox/policy/commit-gate decision log: recomputes
1157
- // every event hash from genesis, checks chain linkage, and catches the
1158
- // unchained-event forgery. Exposed as a verb (not just embedded in `audit summary`,
1159
- // which always exits 0) so `cw audit verify <run> && deploy` can gate on the exit
1160
- // code. POLA: a run with no audit log is present:false / verified:true / exit 0.
1161
- function auditVerify(runner, args) {
1162
- const runId = optionalString(args.runId || args.run);
1163
- if (!runId)
1164
- throw new Error("audit verify requires a run id (cw audit verify <run-id>)");
1165
- const run = runner.loadRun(runId);
1166
- const v = (0, trust_audit_1.verifyTrustAudit)(run);
1167
- return {
1168
- schemaVersion: 1,
1169
- runId: run.id,
1170
- present: v.present,
1171
- verified: v.verified,
1172
- eventCount: v.eventCount,
1173
- chained: v.chained,
1174
- unchained: v.unchained,
1175
- corruptLines: v.corruptLines,
1176
- failedChecks: v.checks.filter((c) => !c.pass).map((c) => ({ name: c.name, code: c.code }))
1177
- };
1178
- }
1179
- // ---- demo: tamper-evidence (the one-command proof) -------------------------
1180
- // Hermetic, deterministic-shape: builds a real ed25519-signed telemetry ledger,
1181
- // then forges it three ways and shows all three tamper-evidence layers (ledger,
1182
- // signature, result) catch it. CLI-only
1183
- // (a human-facing demonstration; the underlying verify is the telemetry.verify verb).
1184
- function demoTamper(_runner, _args = {}) {
1185
- return (0, telemetry_demo_1.runTamperDemo)();
1186
- }
1187
- function demoBundle(_runner, _args = {}) {
1188
- return (0, telemetry_demo_1.runBundleDemo)();
1189
- }