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,11 +1,13 @@
1
1
  "use strict";
2
- // src/term.ts — zero-dependency terminal styling.
2
+ // shell/term.ts — zero-dependency terminal styling.
3
3
  //
4
- // Provides TTY-gated ANSI formatting for human-readable output. When output is
5
- // piped (non-TTY), styled calls return plain text. This keeps data channels
6
- // (stdout) and diagnostics (stderr) clean never adds escape codes to pipes.
7
- //
8
- // Used by: doctor, help, error messages, status summaries.
4
+ // MILESTONE 5 (docs/rebuild/PLAN.md build order, step 5, doctor/fix): TTY-gated
5
+ // ANSI formatting, so a piped run (every conformance case pipes with
6
+ // NO_COLOR=1) prints plain text. EXTENDED at MILESTONE 11 (reporting/
7
+ // observability) with the rest of the old build's src/term.ts: indent,
8
+ // sectionHeader, phaseProgressLine, printSuccessSummary, stripAnsi,
9
+ // visibleWidth, truncate, formatFindingsSummary, FindingRow — byte-exact
10
+ // port of plugins/cool-workflow/src/term.ts:81-210.
9
11
  Object.defineProperty(exports, "__esModule", { value: true });
10
12
  exports.bold = bold;
11
13
  exports.dim = dim;
@@ -14,9 +16,9 @@ exports.yellow = yellow;
14
16
  exports.red = red;
15
17
  exports.cyan = cyan;
16
18
  exports.doctorGlyph = doctorGlyph;
17
- exports.indent = indent;
18
- exports.nextHint = nextHint;
19
19
  exports.tryHint = tryHint;
20
+ exports.nextHint = nextHint;
21
+ exports.indent = indent;
20
22
  exports.sectionHeader = sectionHeader;
21
23
  exports.phaseProgressLine = phaseProgressLine;
22
24
  exports.printSuccessSummary = printSuccessSummary;
@@ -27,9 +29,6 @@ exports.formatFindingsSummary = formatFindingsSummary;
27
29
  function isTTY(stream = process.stderr) {
28
30
  return Boolean(stream.isTTY);
29
31
  }
30
- /** Whether to emit ANSI color on a stream, honoring the de-facto env standards:
31
- * NO_COLOR / CW_NO_COLOR (any non-empty value) disable; FORCE_COLOR (non-"0") forces on
32
- * even when piped; otherwise fall back to isTTY. The `--no-color` flag sets CW_NO_COLOR. */
33
32
  function colorEnabled(stream, env = process.env) {
34
33
  if ((env.NO_COLOR ?? "") !== "" || (env.CW_NO_COLOR ?? "") !== "")
35
34
  return false;
@@ -37,7 +36,6 @@ function colorEnabled(stream, env = process.env) {
37
36
  return true;
38
37
  return isTTY(stream);
39
38
  }
40
- // ---- ansi codes ----
41
39
  const ansi = {
42
40
  reset: "\x1b[0m",
43
41
  bold: "\x1b[1m",
@@ -47,7 +45,6 @@ const ansi = {
47
45
  red: "\x1b[31m",
48
46
  cyan: "\x1b[36m",
49
47
  };
50
- // ---- styled text ----
51
48
  function style(code, text, stream) {
52
49
  if (!colorEnabled(stream))
53
50
  return text;
@@ -71,7 +68,6 @@ function red(text, stream) {
71
68
  function cyan(text, stream) {
72
69
  return style(ansi.cyan, text, stream);
73
70
  }
74
- // ---- semantic helpers ----
75
71
  /** Returns the styled glyph + label for a doctor check severity. */
76
72
  function doctorGlyph(status, stream) {
77
73
  const glyph = { ok: "✓", warn: "!", fail: "✗" };
@@ -82,42 +78,41 @@ function doctorGlyph(status, stream) {
82
78
  };
83
79
  return color[status](`${glyph[status]}`, stream);
84
80
  }
85
- /** Render a multi-line block with consistent 2-space indentation. */
86
- function indent(text, spaces = 2) {
87
- const prefix = " ".repeat(spaces);
88
- return text.split("\n").map((line) => `${prefix}${line}`).join("\n");
81
+ /** A `Try: <cmd>` recovery hint (brew-style; the command stays plain to copy). */
82
+ function tryHint(cmd, stream) {
83
+ return `${dim("Try:", stream)} ${cmd}`;
89
84
  }
90
85
  /** A `Next: <cmd>` hint line (the command stays plain so it is copy-pasteable). */
91
86
  function nextHint(cmd, stream) {
92
87
  return `${dim("Next:", stream)} ${cmd}`;
93
88
  }
94
- /** A `Try: <cmd>` recovery hint (brew-style; the command stays plain to copy). */
95
- function tryHint(cmd, stream) {
96
- return `${dim("Try:", stream)} ${cmd}`;
89
+ /** Render a multi-line block with consistent 2-space indentation. */
90
+ function indent(text, spaces = 2) {
91
+ const prefix = " ".repeat(spaces);
92
+ return text
93
+ .split("\n")
94
+ .map((line) => `${prefix}${line}`)
95
+ .join("\n");
97
96
  }
98
97
  /** A `==> Title` section header (brew-style). */
99
98
  function sectionHeader(title, stream) {
100
99
  return `${bold("==>", stream)} ${title}`;
101
100
  }
102
- /** A phase-progress line: `==> Map ✓ (6/6)` / `==> Assess … (3/6)`. Parallel phases
103
- * use ⇉, sequential use …; a finished phase uses a green ✓. */
101
+ /** A phase-progress line: `==> Map ✓ (6/6)` / `==> Assess … (3/6)`. Parallel
102
+ * phases use ⇉, sequential use …; a finished phase uses a green ✓. */
104
103
  function phaseProgressLine(name, done, total, mode, stream) {
105
104
  const complete = total > 0 && done >= total;
106
- const glyph = complete ? green("✓", stream) : (mode === "parallel" ? "⇉" : "…");
105
+ const glyph = complete ? green("✓", stream) : mode === "parallel" ? "⇉" : "…";
107
106
  const count = total > 0 ? ` (${done}/${total})` : "";
108
107
  return `${sectionHeader(name, stream)} ${glyph}${count}`;
109
108
  }
110
- /** Print a success summary to stderr (TTY-gated). Shows the report path, a one-line
111
- * status (with N/N worker counts when known), and a copy-pasteable next/recovery
112
- * command. Pipe-friendly: silent when stderr is not a TTY, so it never pollutes
113
- * piped/`--json` stdout. A non-complete run with no agent configured gets a brew-style
114
- * `Try: cw doctor` recovery line; otherwise `Next: cw status <id>` to inspect. */
109
+ /** Print a success summary to stderr (TTY-gated). Silent when stderr is
110
+ * not a TTY, so it never pollutes piped/`--json` stdout. */
115
111
  function printSuccessSummary(fields, stream) {
116
112
  if (!isTTY(stream))
117
113
  return;
118
114
  const s = stream || process.stderr;
119
- const counts = (typeof fields.completedWorkers === "number" && typeof fields.plannedWorkers === "number")
120
- ? ` — ${fields.completedWorkers}/${fields.plannedWorkers}` : "";
115
+ const counts = typeof fields.completedWorkers === "number" && typeof fields.plannedWorkers === "number" ? ` — ${fields.completedWorkers}/${fields.plannedWorkers}` : "";
121
116
  s.write(`\n${green("✓", s)} Report: ${fields.reportPath}\n`);
122
117
  if (fields.status === "complete") {
123
118
  s.write(` ${green("✓", s)} Status: complete${counts}\n`);
@@ -125,8 +120,6 @@ function printSuccessSummary(fields, stream) {
125
120
  }
126
121
  else {
127
122
  s.write(` ${yellow("!", s)} Status: ${fields.status}${counts}\n`);
128
- // No agent backend is the #1 first-run blocker — point at the one command that
129
- // diagnoses and prints the fix, brew-style. Otherwise inspect the run's state.
130
123
  if (fields.agentConfigured === false)
131
124
  s.write(` ${tryHint("cw doctor", s)}\n`);
132
125
  else
@@ -139,13 +132,15 @@ const ANSI_RE = /\x1b\[[0-9;]*m/g;
139
132
  function stripAnsi(text) {
140
133
  return text.replace(ANSI_RE, "");
141
134
  }
142
- /** Visible width of a string, ignoring ANSI. Counts each code point as width 1 — a known
143
- * minor caveat for wide (CJK/emoji) glyphs, acceptable for one-line status truncation. */
135
+ /** Visible width of a string, ignoring ANSI. Counts each code point as
136
+ * width 1 — a known minor caveat for wide (CJK/emoji) glyphs, acceptable
137
+ * for one-line status truncation. */
144
138
  function visibleWidth(text) {
145
139
  return [...stripAnsi(text)].length;
146
140
  }
147
- /** Truncate a (possibly styled) string to `maxWidth` visible columns, appending `…` when cut.
148
- * Operates on the PLAIN text (callers truncate before styling), so no ANSI is split. */
141
+ /** Truncate a (possibly styled) string to `maxWidth` visible columns,
142
+ * appending `…` when cut. Operates on the PLAIN text (callers truncate
143
+ * before styling), so no ANSI is split. */
149
144
  function truncate(text, maxWidth) {
150
145
  if (maxWidth <= 0)
151
146
  return "";
@@ -157,12 +152,10 @@ function truncate(text, maxWidth) {
157
152
  return `${chars.slice(0, maxWidth - 1).join("")}…`;
158
153
  }
159
154
  // ---- findings summary table (end-of-run, compact) ----
160
- /** Severity order for sorting + counting (most severe first). */
161
155
  const SEVERITY_ORDER = ["P0", "P1", "P2", "P3", "none"];
162
- /** Render a compact findings summary assembled from the run's `cw:result` blocks: a one-line
163
- * count headline (e.g. `Findings: 3 2×P1, 1×P2`) plus a small id/severity/class table. This
164
- * is the end-of-run summary NOT the full prose (that stays in report.md + the transcript).
165
- * Returns "" when there are no findings (caller prints nothing). */
156
+ /** Render a compact findings summary assembled from the run's `cw:result`
157
+ * blocks: a one-line count headline plus a small id/severity/class
158
+ * table. Returns "" when there are no findings. */
166
159
  function formatFindingsSummary(findings, stream) {
167
160
  if (!findings.length)
168
161
  return "";
@@ -180,10 +173,7 @@ function formatFindingsSummary(findings, stream) {
180
173
  const rows = [...findings].sort((a, b) => order(a.severity) - order(b.severity));
181
174
  const sevW = Math.max(8, ...rows.map((r) => (r.severity || "none").length));
182
175
  const clsW = Math.max(5, ...rows.map((r) => (r.classification || "unknown").length));
183
- const lines = [
184
- `${bold("Findings:", stream)} ${findings.length} — ${counts}`,
185
- dim(` ${"SEVERITY".padEnd(sevW)} ${"CLASS".padEnd(clsW)} ID`, stream)
186
- ];
176
+ const lines = [`${bold("Findings:", stream)} ${findings.length} — ${counts}`, dim(` ${"SEVERITY".padEnd(sevW)} ${"CLASS".padEnd(clsW)} ID`, stream)];
187
177
  for (const r of rows) {
188
178
  const sev = (r.severity || "none").padEnd(sevW);
189
179
  const cls = (r.classification || "unknown").padEnd(clsW);
@@ -0,0 +1,361 @@
1
+ "use strict";
2
+ // shell/topology-io.ts — applyTopology's IO wiring: materializes the
3
+ // blackboard + multi-agent state a topology needs, then persists the
4
+ // topology-run record.
5
+ //
6
+ // MILESTONE 9. Byte-exact port of the impure half of the old build's
7
+ // src/topology.ts's applyTopology + summarizeTopologies/buildTopologyGraph/
8
+ // showTopologyRun/persistTopologyState.
9
+ //
10
+ // Evidence: SPEC/multi-agent.md section B; plugins/cool-workflow/src/
11
+ // topology.ts:139-455 (byte-exact source for the wiring sequence).
12
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ var desc = Object.getOwnPropertyDescriptor(m, k);
15
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
16
+ desc = { enumerable: true, get: function() { return m[k]; } };
17
+ }
18
+ Object.defineProperty(o, k2, desc);
19
+ }) : (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ o[k2] = m[k];
22
+ }));
23
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
24
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
25
+ }) : function(o, v) {
26
+ o["default"] = v;
27
+ });
28
+ var __importStar = (this && this.__importStar) || (function () {
29
+ var ownKeys = function(o) {
30
+ ownKeys = Object.getOwnPropertyNames || function (o) {
31
+ var ar = [];
32
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
33
+ return ar;
34
+ };
35
+ return ownKeys(o);
36
+ };
37
+ return function (mod) {
38
+ if (mod && mod.__esModule) return mod;
39
+ var result = {};
40
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
41
+ __setModuleDefault(result, mod);
42
+ return result;
43
+ };
44
+ })();
45
+ Object.defineProperty(exports, "__esModule", { value: true });
46
+ exports.ensureTopologyState = ensureTopologyState;
47
+ exports.persistTopologyState = persistTopologyState;
48
+ exports.applyTopology = applyTopology;
49
+ exports.showTopologyRun = showTopologyRun;
50
+ exports.summarizeTopologies = summarizeTopologies;
51
+ exports.buildTopologyGraph = buildTopologyGraph;
52
+ exports.formatTopologySummaryText = formatTopologySummaryText;
53
+ exports.formatTopologyGraphText = formatTopologyGraphText;
54
+ const fs = __importStar(require("node:fs"));
55
+ const path = __importStar(require("node:path"));
56
+ const fs_atomic_1 = require("./fs-atomic");
57
+ const node_store_1 = require("./node-store");
58
+ const state_node_1 = require("../core/state/state-node");
59
+ const contract_1 = require("../core/pipeline/contract");
60
+ const trust_audit_1 = require("./trust-audit");
61
+ const hash_1 = require("../core/hash");
62
+ const hash_2 = require("../core/hash");
63
+ const topo = __importStar(require("../core/multi-agent/topology"));
64
+ const multi_agent_io_1 = require("./multi-agent-io");
65
+ const coordinator_io_1 = require("./coordinator-io");
66
+ function topologyRoot(run) {
67
+ return run.paths.topologiesDir || path.join(run.paths.runDir, "topologies");
68
+ }
69
+ function topologyRunPath(run, id) {
70
+ const safe = id.replace(/[^a-zA-Z0-9_.:-]+/g, "_");
71
+ return path.join(topologyRoot(run), "runs", `${safe}.json`);
72
+ }
73
+ function ensureTopologyState(run) {
74
+ run.paths.topologiesDir = topologyRoot(run);
75
+ fs.mkdirSync(run.paths.topologiesDir, { recursive: true });
76
+ fs.mkdirSync(path.join(run.paths.topologiesDir, "runs"), { recursive: true });
77
+ const existing = run.topologies;
78
+ const state = existing || { schemaVersion: topo.TOPOLOGY_SCHEMA_VERSION, runs: [] };
79
+ state.schemaVersion = topo.TOPOLOGY_SCHEMA_VERSION;
80
+ state.runs = state.runs || [];
81
+ run.topologies = state;
82
+ return state;
83
+ }
84
+ function persistTopologyState(run) {
85
+ const state = ensureTopologyState(run);
86
+ (0, fs_atomic_1.writeJson)(path.join(topologyRoot(run), "index.json"), {
87
+ schemaVersion: topo.TOPOLOGY_SCHEMA_VERSION,
88
+ runId: run.id,
89
+ counts: { runs: state.runs.length },
90
+ runs: state.runs.map((record) => ({ id: record.id, topologyId: record.topologyId, status: record.status, updatedAt: record.updatedAt })),
91
+ });
92
+ for (const record of state.runs)
93
+ (0, fs_atomic_1.writeJson)(topologyRunPath(run, record.id), record);
94
+ }
95
+ function now() {
96
+ return new Date().toISOString();
97
+ }
98
+ function compact(value) {
99
+ return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
100
+ }
101
+ function appendTopologyNode(run, record, status) {
102
+ (0, node_store_1.appendRunNode)(run, (0, state_node_1.createStateNode)({
103
+ id: `${run.id}:topology:${record.id}`,
104
+ kind: "topology-run",
105
+ status,
106
+ loopStage: run.loopStage,
107
+ outputs: { topologyId: record.topologyId, status: record.status },
108
+ artifacts: [{ id: "topology-run", kind: "json", path: topologyRunPath(run, record.id) }],
109
+ parents: [`${run.id}:multi-agent:${record.multiAgentRunId}`, `${run.id}:blackboard:${record.blackboardId}`],
110
+ contractId: contract_1.DEFAULT_PIPELINE_CONTRACT_ID,
111
+ metadata: { topologyId: record.topologyId, topologyRunId: record.id },
112
+ }));
113
+ }
114
+ function applyTopology(run, topologyId, input = {}) {
115
+ const validation = topo.validateTopologyDefinition(topologyId);
116
+ if (!validation.valid || !validation.definition) {
117
+ throw new Error(`Invalid topology ${topologyId}: ${validation.issues.map((entry) => entry.message).join("; ")}`);
118
+ }
119
+ const definition = validation.definition;
120
+ const state = ensureTopologyState(run);
121
+ const taskIds = topo.selectedTaskIds(run.tasks, input.taskIds);
122
+ const id = input.id || topo.topologyRunId(definition, taskIds, run.id, state.runs.length, (value) => (0, hash_1.sha256)((0, hash_2.stableStringify)(value)));
123
+ if (state.runs.some((record) => record.id === id))
124
+ throw new Error(`Duplicate MultiAgentTopologyRun id: ${id}`);
125
+ const board = (0, coordinator_io_1.resolveBlackboard)(run, { id: input.blackboardId || `${id}-blackboard`, title: `${definition.title} Blackboard`, tags: ["topology", definition.id] });
126
+ const topics = definition.blackboardTopics.map((topic) => (0, coordinator_io_1.createBlackboardTopic)(run, { id: `${id}-${topic.id}`, title: topic.title, description: topic.description, blackboardId: board.id, tags: ["topology", definition.id] }));
127
+ const multiAgentRun = (0, multi_agent_io_1.createMultiAgentRun)(run, {
128
+ id: input.multiAgentRunId || `${id}-ma`,
129
+ title: input.title || definition.title,
130
+ objective: definition.summary,
131
+ blackboardId: board.id,
132
+ topicIds: topics.map((topic) => topic.id),
133
+ metadata: { topologyId: definition.id, topologyRunId: id },
134
+ });
135
+ const roleIds = [];
136
+ for (const role of topo.materializedRoles(definition, topo.withLegacyRoleCounts(input))) {
137
+ const record = (0, multi_agent_io_1.createAgentRole)(run, {
138
+ id: `${id}-${role.id}`,
139
+ multiAgentRunId: multiAgentRun.id,
140
+ title: role.title,
141
+ responsibilities: role.responsibilities,
142
+ requiredEvidence: role.requiredEvidence,
143
+ expectedArtifacts: role.expectedArtifacts,
144
+ faninObligations: role.faninObligations,
145
+ blackboardId: board.id,
146
+ topicIds: topics.map((topic) => topic.id),
147
+ metadata: { topologyId: definition.id, topologyRunId: id, topologyRoleId: role.id },
148
+ });
149
+ roleIds.push(record.id);
150
+ }
151
+ const group = (0, multi_agent_io_1.createAgentGroup)(run, { id: `${id}-group`, multiAgentRunId: multiAgentRun.id, title: `${definition.title} Group`, phase: definition.title, taskIds, blackboardId: board.id, topicIds: topics.map((topic) => topic.id), metadata: { topologyId: definition.id, topologyRunId: id } });
152
+ const fanoutRoles = topo.fanoutRoleIds(roleIds);
153
+ const fanout = (0, multi_agent_io_1.createAgentFanout)(run, {
154
+ id: `${id}-fanout`,
155
+ multiAgentRunId: multiAgentRun.id,
156
+ groupId: group.id,
157
+ reason: `${definition.id} topology fanout`,
158
+ roleIds: fanoutRoles,
159
+ taskIds,
160
+ concurrencyLimit: fanoutRoles.length,
161
+ expectedReturnShape: `${definition.title} worker output must include cw:result evidence and blackboard-indexable artifacts/messages.`,
162
+ blackboardId: board.id,
163
+ topicIds: topics.map((topic) => topic.id),
164
+ metadata: { topologyId: definition.id, topologyRunId: id, fanoutStrategy: definition.fanoutStrategy },
165
+ });
166
+ const message = (0, coordinator_io_1.postBlackboardMessage)(run, { topicId: topics[0].id, blackboardId: board.id, body: `${definition.title} topology applied. Roles=${roleIds.join(", ")} fanout=${fanout.id}.`, tags: ["topology", definition.id], metadata: { topologyRunId: id } });
167
+ const decision = (0, coordinator_io_1.recordCoordinatorDecision)(run, {
168
+ blackboardId: board.id,
169
+ topicId: topics[0].id,
170
+ kind: "context-update",
171
+ outcome: "accepted",
172
+ reason: `${definition.title} topology materialized on multi-agent runtime and blackboard.`,
173
+ subjectIds: [multiAgentRun.id, group.id, fanout.id],
174
+ messageIds: [message.id],
175
+ tags: ["topology", definition.id],
176
+ metadata: { topologyRunId: id },
177
+ });
178
+ let fanin;
179
+ if (input.collectInitialFanin) {
180
+ fanin = (0, multi_agent_io_1.collectAgentFanin)(run, { id: `${id}-fanin-initial`, multiAgentRunId: multiAgentRun.id, groupId: group.id, fanoutId: fanout.id, requiredRoleIds: fanout.roleIds, strategy: definition.faninStrategy, blackboardId: board.id, topicIds: topics.map((topic) => topic.id), metadata: { topologyId: definition.id, topologyRunId: id } });
181
+ }
182
+ const audit = (0, trust_audit_1.recordTrustAuditEvent)(run, {
183
+ kind: "topology.create",
184
+ decision: "recorded",
185
+ source: "runtime-derived",
186
+ topologyId: definition.id,
187
+ topologyRunId: id,
188
+ multiAgentRunId: multiAgentRun.id,
189
+ agentGroupId: group.id,
190
+ agentFanoutId: fanout.id,
191
+ blackboardId: board.id,
192
+ blackboardMessageId: message.id,
193
+ coordinatorDecisionId: decision.id,
194
+ metadata: { fanoutStrategy: definition.fanoutStrategy, faninStrategy: definition.faninStrategy },
195
+ });
196
+ const stamp = now();
197
+ const record = {
198
+ schemaVersion: topo.TOPOLOGY_SCHEMA_VERSION,
199
+ id,
200
+ runId: run.id,
201
+ topologyId: definition.id,
202
+ createdAt: stamp,
203
+ updatedAt: stamp,
204
+ status: fanin?.status === "blocked" ? "blocked" : "planned",
205
+ title: input.title || definition.title,
206
+ multiAgentRunId: multiAgentRun.id,
207
+ blackboardId: board.id,
208
+ topicIds: topics.map((topic) => topic.id),
209
+ roleIds,
210
+ groupIds: [group.id],
211
+ fanoutIds: [fanout.id],
212
+ faninIds: fanin ? [fanin.id] : [],
213
+ messageIds: [message.id],
214
+ artifactRefIds: [],
215
+ coordinatorDecisionIds: [decision.id],
216
+ candidateIds: [],
217
+ selectionIds: [],
218
+ commitIds: [],
219
+ missingEvidence: fanin?.blockedReasons || definition.requiredEvidence,
220
+ conflicts: [],
221
+ nextActions: topo.nextActionsFor(run.id, id, fanout.id),
222
+ links: {
223
+ workflowRunId: run.id,
224
+ multiAgentRunId: multiAgentRun.id,
225
+ blackboardId: board.id,
226
+ blackboardTopicIds: topics.map((topic) => topic.id),
227
+ agentRoleIds: roleIds,
228
+ agentGroupIds: [group.id],
229
+ agentFanoutIds: [fanout.id],
230
+ agentFaninIds: fanin ? [fanin.id] : [],
231
+ coordinatorDecisionIds: [decision.id],
232
+ candidateIds: [],
233
+ selectionIds: [],
234
+ commitIds: [],
235
+ auditEventIds: [audit.id],
236
+ },
237
+ metadata: compact({ ...(input.metadata || {}), topology: definition }),
238
+ };
239
+ state.runs.push(record);
240
+ appendTopologyNode(run, record, topo.statusToNodeStatus(record.status));
241
+ (0, trust_audit_1.recordTrustAuditEvent)(run, {
242
+ kind: "topology.verdict",
243
+ decision: record.status === "blocked" ? "failed" : "recorded",
244
+ source: "cw-validated",
245
+ topologyId: definition.id,
246
+ topologyRunId: id,
247
+ multiAgentRunId: multiAgentRun.id,
248
+ agentFanoutId: fanout.id,
249
+ agentFaninId: fanin?.id,
250
+ blackboardId: board.id,
251
+ coordinatorDecisionId: decision.id,
252
+ metadata: { status: record.status, missingEvidence: record.missingEvidence },
253
+ });
254
+ persistTopologyState(run);
255
+ return record;
256
+ }
257
+ function showTopologyRun(run, topologyRunId) {
258
+ const record = ensureTopologyState(run).runs.find((entry) => entry.id === topologyRunId);
259
+ if (!record)
260
+ throw new Error(`Unknown topology run id: ${topologyRunId}`);
261
+ return record;
262
+ }
263
+ function countBy(items, pick) {
264
+ const counts = {};
265
+ for (const item of items)
266
+ counts[pick(item)] = (counts[pick(item)] || 0) + 1;
267
+ return counts;
268
+ }
269
+ function summarizeTopologies(run) {
270
+ const state = ensureTopologyState(run);
271
+ const multi = run.multiAgent || {};
272
+ const fanins = multi.fanins || [];
273
+ const active = state.runs.map((record) => {
274
+ const inferredFanins = fanins.filter((fanin) => record.groupIds.includes(fanin.groupId) || record.fanoutIds.includes(fanin.fanoutId || ""));
275
+ const allFaninIds = topo.unique([...record.faninIds, ...inferredFanins.map((fanin) => fanin.id)]);
276
+ const blocked = inferredFanins.filter((fanin) => fanin.status === "blocked" || !fanin.verifierReady);
277
+ const ready = inferredFanins.some((fanin) => fanin.verifierReady);
278
+ const missingEvidence = topo.unique([...record.missingEvidence, ...blocked.flatMap((fanin) => fanin.blockedReasons)]);
279
+ return {
280
+ id: record.id,
281
+ topologyId: record.topologyId,
282
+ status: ready ? "ready" : blocked.length ? "blocked" : record.status,
283
+ multiAgentRunId: record.multiAgentRunId,
284
+ blackboardId: record.blackboardId,
285
+ roles: record.roleIds,
286
+ groups: record.groupIds,
287
+ topics: record.topicIds,
288
+ fanouts: record.fanoutIds,
289
+ fanins: allFaninIds,
290
+ missingEvidence,
291
+ conflicts: record.conflicts,
292
+ readiness: ready ? "fanin ready" : missingEvidence.length ? "missing evidence" : "awaiting worker output",
293
+ nextActions: ready ? [`node scripts/cw.js candidate register ${run.id} --result-node <reducer-or-panel-result>`] : record.nextActions,
294
+ };
295
+ });
296
+ return {
297
+ runId: run.id,
298
+ totalRuns: state.runs.length,
299
+ runsByStatus: countBy(active, (record) => record.status),
300
+ officialTopologies: topo.listTopologyDefinitions().map((definition) => definition.id),
301
+ active,
302
+ nextAction: active.find((record) => record.nextActions.length)?.nextActions[0] || `node scripts/cw.js topology apply ${run.id} map-reduce --task <task-id>`,
303
+ };
304
+ }
305
+ function buildTopologyGraph(run) {
306
+ const state = ensureTopologyState(run);
307
+ return topo.buildTopologyGraphFromRuns(run.id, state.runs, (id) => topologyRunPath(run, id));
308
+ }
309
+ function formatTopologyCounts(counts) {
310
+ const entries = Object.entries(counts).sort(([a], [b]) => a.localeCompare(b));
311
+ if (!entries.length)
312
+ return "none";
313
+ return entries.map(([k, v]) => `${k}=${v}`).join(", ");
314
+ }
315
+ /** `cw topology summary <run>` human text — port of the old build's
316
+ * formatTopologyPanel (operator-ux/format.ts): a `Topologies` rollup with
317
+ * per-run roles/topics/fanout/fanin/readiness. */
318
+ function formatTopologySummaryText(summary) {
319
+ const lines = [
320
+ "Topologies",
321
+ ` runs=${summary.totalRuns}; status=${formatTopologyCounts(summary.runsByStatus)}; official=${summary.officialTopologies.join(", ")}`,
322
+ ];
323
+ for (const record of summary.active.slice(0, 6)) {
324
+ lines.push(` ${record.id}: ${record.topologyId}, status=${record.status}, readiness=${record.readiness}`);
325
+ lines.push(` run=${record.multiAgentRunId} board=${record.blackboardId}`);
326
+ lines.push(` roles=${record.roles.join(", ") || "none"} topics=${record.topics.join(", ") || "none"}`);
327
+ lines.push(` fanout=${record.fanouts.join(", ") || "none"} fanin=${record.fanins.join(", ") || "none"}`);
328
+ for (const missing of record.missingEvidence.slice(0, 4))
329
+ lines.push(` missing=${missing}`);
330
+ for (const conflict of record.conflicts.slice(0, 4))
331
+ lines.push(` conflict=${conflict}`);
332
+ if (record.nextActions[0])
333
+ lines.push(` next=${record.nextActions[0]}`);
334
+ }
335
+ if (summary.nextAction)
336
+ lines.push(` next=${summary.nextAction}`);
337
+ return lines.join("\n");
338
+ }
339
+ /** `cw topology graph <run>` human text — the same `Run Graph:` render
340
+ * `cw graph` uses, over the topology graph's nodes/edges (old build:
341
+ * formatOperatorGraph({ runId, nodes, edges })). */
342
+ function formatTopologyGraphText(runId, graph) {
343
+ const lines = [`Run Graph: ${runId}`, "", "Nodes"];
344
+ const groups = {};
345
+ for (const node of graph.nodes)
346
+ (groups[node.kind] ||= []).push(node);
347
+ for (const kind of Object.keys(groups).sort()) {
348
+ lines.push(` ${kind}`);
349
+ for (const node of groups[kind]) {
350
+ const suffix = node.path ? ` -> ${node.path}` : "";
351
+ lines.push(` [${node.status}] ${node.id} (${node.label})${suffix}`);
352
+ }
353
+ }
354
+ lines.push("", "Edges");
355
+ if (!graph.edges.length)
356
+ lines.push(" none");
357
+ for (const edge of graph.edges) {
358
+ lines.push(` ${edge.from} -> ${edge.to}${edge.label ? ` (${edge.label})` : ""}`);
359
+ }
360
+ return lines.join("\n");
361
+ }