cool-workflow 0.1.98 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (306) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/README.md +11 -2
  4. package/apps/architecture-review/app.json +1 -1
  5. package/apps/architecture-review-fast/app.json +1 -1
  6. package/apps/end-to-end-golden-path/app.json +1 -1
  7. package/apps/pr-review-fix-ci/app.json +1 -1
  8. package/apps/release-cut/app.json +1 -1
  9. package/apps/research-synthesis/app.json +1 -1
  10. package/dist/cli/dispatch.js +236 -0
  11. package/dist/cli/entry.js +120 -0
  12. package/dist/cli/io.js +21 -4
  13. package/dist/cli/parseargv.js +157 -0
  14. package/dist/cli.js +6 -33
  15. package/dist/core/capability-table.js +3534 -0
  16. package/dist/core/format/help.js +314 -0
  17. package/dist/{state-explosion/format.js → core/format/state-explosion-text.js} +10 -1
  18. package/dist/core/hash.js +137 -0
  19. package/dist/core/multi-agent/candidate-scoring.js +219 -0
  20. package/dist/core/multi-agent/collaboration.js +481 -0
  21. package/dist/core/multi-agent/coordinator.js +515 -0
  22. package/dist/core/multi-agent/eval-replay.js +306 -0
  23. package/dist/core/multi-agent/runtime.js +929 -0
  24. package/dist/core/multi-agent/topology.js +298 -0
  25. package/dist/core/multi-agent/trust-policy.js +197 -0
  26. package/dist/core/pipeline/commit-gate.js +320 -0
  27. package/dist/{pipeline-contract.js → core/pipeline/contract.js} +24 -37
  28. package/dist/core/pipeline/dispatch.js +103 -0
  29. package/dist/core/pipeline/drive-decide.js +227 -0
  30. package/dist/core/pipeline/error-feedback.js +161 -0
  31. package/dist/core/pipeline/loop-expansion.js +124 -0
  32. package/dist/{result-normalize.js → core/pipeline/result-normalize.js} +14 -37
  33. package/dist/core/pipeline/runner.js +230 -0
  34. package/dist/{contract-migration.js → core/state/contract-migration.js} +68 -92
  35. package/dist/{state-migrations.js → core/state/migrations.js} +103 -96
  36. package/dist/core/state/node-projection.js +95 -0
  37. package/dist/core/state/node-snapshot.js +230 -0
  38. package/dist/core/state/run-paths.js +93 -0
  39. package/dist/{schema-validate.js → core/state/schema-validate.js} +35 -28
  40. package/dist/core/state/schema.js +50 -0
  41. package/dist/core/state/state-explosion/digest.js +243 -0
  42. package/dist/core/state/state-explosion/graph.js +527 -0
  43. package/dist/{state-explosion → core/state/state-explosion}/helpers.js +34 -3
  44. package/dist/core/state/state-explosion/report.js +187 -0
  45. package/dist/{state-explosion → core/state/state-explosion}/size.js +25 -7
  46. package/dist/{state-node.js → core/state/state-node.js} +90 -113
  47. package/dist/core/state/types.js +19 -0
  48. package/dist/{validation.js → core/state/validation.js} +64 -131
  49. package/dist/core/trust/evidence-grounding.js +134 -0
  50. package/dist/core/trust/ledger.js +199 -0
  51. package/dist/{telemetry-attestation.js → core/trust/telemetry-attestation.js} +94 -68
  52. package/dist/core/trust/telemetry-ledger.js +127 -0
  53. package/dist/core/types.js +20 -0
  54. package/dist/core/version.js +16 -0
  55. package/dist/{workflow-app-framework.js → core/workflow-apps/app-schema.js} +337 -426
  56. package/dist/mcp/dispatch.js +104 -0
  57. package/dist/mcp/server.js +144 -0
  58. package/dist/mcp-server.js +6 -84
  59. package/dist/{agent-config.js → shell/agent-config.js} +118 -71
  60. package/dist/shell/app-run-cli.js +88 -0
  61. package/dist/shell/audit-cli.js +259 -0
  62. package/dist/shell/audit-provenance.js +83 -0
  63. package/dist/shell/candidate-scoring-io.js +459 -0
  64. package/dist/shell/collaboration-io.js +264 -0
  65. package/dist/shell/commit-summary.js +104 -0
  66. package/dist/shell/commit.js +290 -0
  67. package/dist/shell/coordinator-io.js +476 -0
  68. package/dist/shell/demo-cli.js +19 -0
  69. package/dist/shell/dispatch.js +162 -0
  70. package/dist/{doctor.js → shell/doctor.js} +123 -56
  71. package/dist/shell/drive.js +873 -0
  72. package/dist/shell/error-feedback-io.js +305 -0
  73. package/dist/shell/eval-io.js +473 -0
  74. package/dist/{multi-agent-eval/format.js → shell/eval-text.js} +78 -49
  75. package/dist/{evidence-reasoning.js → shell/evidence-reasoning.js} +124 -255
  76. package/dist/shell/exec-backend-cli.js +88 -0
  77. package/dist/shell/execution-backend/agent.js +475 -0
  78. package/dist/shell/execution-backend/ci.js +15 -0
  79. package/dist/shell/execution-backend/container.js +69 -0
  80. package/dist/shell/execution-backend/envelopes.js +55 -0
  81. package/dist/shell/execution-backend/local.js +113 -0
  82. package/dist/shell/execution-backend/probes.js +175 -0
  83. package/dist/shell/execution-backend/registry.js +402 -0
  84. package/dist/shell/execution-backend/remote.js +128 -0
  85. package/dist/shell/execution-backend/types.js +11 -0
  86. package/dist/shell/feedback-cli.js +81 -0
  87. package/dist/shell/feedback-operations.js +48 -0
  88. package/dist/shell/fs-atomic.js +276 -0
  89. package/dist/shell/harness.js +98 -0
  90. package/dist/shell/ledger-cli.js +212 -0
  91. package/dist/shell/ledger-io.js +169 -0
  92. package/dist/shell/man-cli.js +89 -0
  93. package/dist/shell/metrics-cli.js +98 -0
  94. package/dist/shell/multi-agent-cli.js +1002 -0
  95. package/dist/shell/multi-agent-host.js +563 -0
  96. package/dist/shell/multi-agent-io.js +387 -0
  97. package/dist/{multi-agent-operator-ux.js → shell/multi-agent-operator-ux.js} +234 -191
  98. package/dist/shell/node-store.js +124 -0
  99. package/dist/{observability/format.js → shell/observability-format.js} +7 -1
  100. package/dist/{observability/intake.js → shell/observability-intake.js} +79 -56
  101. package/dist/{observability.js → shell/observability.js} +159 -332
  102. package/dist/{onramp.js → shell/onramp.js} +11 -0
  103. package/dist/{operator-ux/format.js → shell/operator-ux-text.js} +250 -239
  104. package/dist/shell/operator-ux.js +431 -0
  105. package/dist/shell/orchestrator.js +231 -0
  106. package/dist/shell/pipeline-cli.js +667 -0
  107. package/dist/shell/pipeline.js +217 -0
  108. package/dist/shell/reclamation-io.js +1366 -0
  109. package/dist/shell/registry-cli.js +329 -0
  110. package/dist/{remote-source.js → shell/remote-source.js} +2 -2
  111. package/dist/shell/report-cli.js +101 -0
  112. package/dist/shell/report-view-cli.js +117 -0
  113. package/dist/{orchestrator → shell}/report.js +289 -282
  114. package/dist/shell/reporter.js +62 -0
  115. package/dist/shell/run-export-cli.js +106 -0
  116. package/dist/shell/run-export.js +680 -0
  117. package/dist/shell/run-registry-io.js +1014 -0
  118. package/dist/shell/run-store.js +164 -0
  119. package/dist/{sandbox-profile.js → shell/sandbox-profile.js} +134 -95
  120. package/dist/{scheduler.js → shell/scheduler-io.js} +248 -48
  121. package/dist/shell/scheduling-io.js +311 -0
  122. package/dist/shell/state-cli.js +181 -0
  123. package/dist/shell/state-explosion-cli.js +197 -0
  124. package/dist/shell/telemetry-cli.js +85 -0
  125. package/dist/{telemetry-demo.js → shell/telemetry-demo.js} +149 -119
  126. package/dist/shell/telemetry-ledger-io.js +132 -0
  127. package/dist/{term.js → shell/term.js} +36 -46
  128. package/dist/shell/topology-io.js +361 -0
  129. package/dist/shell/trust-audit.js +471 -0
  130. package/dist/{multi-agent-trust.js → shell/trust-policy-io.js} +67 -205
  131. package/dist/shell/verifier.js +48 -0
  132. package/dist/shell/workbench-host.js +250 -0
  133. package/dist/shell/workbench-text.js +18 -0
  134. package/dist/shell/workbench.js +175 -0
  135. package/dist/shell/worker-cli.js +124 -0
  136. package/dist/shell/worker-isolation.js +852 -0
  137. package/dist/shell/workflow-app-loader.js +650 -0
  138. package/docs/agent-delegation-drive.7.md +2 -0
  139. package/docs/cli-mcp-parity.7.md +280 -219
  140. package/docs/contract-migration-tooling.7.md +2 -0
  141. package/docs/control-plane-scheduling.7.md +2 -0
  142. package/docs/durable-state-and-locking.7.md +2 -0
  143. package/docs/evidence-adoption-reasoning-chain.7.md +2 -0
  144. package/docs/execution-backends.7.md +2 -0
  145. package/docs/multi-agent-cli-mcp-surface.7.md +2 -0
  146. package/docs/multi-agent-eval-replay-harness.7.md +2 -0
  147. package/docs/multi-agent-operator-ux.7.md +2 -0
  148. package/docs/node-snapshot-diff-replay.7.md +2 -0
  149. package/docs/observability-cost-accounting.7.md +2 -0
  150. package/docs/project-index.md +132 -71
  151. package/docs/real-execution-backends.7.md +2 -0
  152. package/docs/release-and-migration.7.md +2 -0
  153. package/docs/release-tooling.7.md +2 -0
  154. package/docs/run-registry-control-plane.7.md +2 -0
  155. package/docs/run-retention-reclamation.7.md +23 -0
  156. package/docs/state-explosion-management.7.md +2 -0
  157. package/docs/team-collaboration.7.md +2 -0
  158. package/docs/web-desktop-workbench.7.md +2 -0
  159. package/manifest/plugin.manifest.json +1 -1
  160. package/manifest/source-context-profiles.json +9 -13
  161. package/package.json +1 -1
  162. package/scripts/agents/cw-attest-wrap.js +2 -2
  163. package/scripts/bump-version.js +4 -3
  164. package/scripts/canonical-apps.js +4 -4
  165. package/scripts/dogfood-architecture-review.js +2 -3
  166. package/scripts/dogfood-release.js +3 -3
  167. package/scripts/gen-parity-doc.js +15 -2
  168. package/scripts/golden-path.js +4 -4
  169. package/scripts/onramp-check.js +1 -1
  170. package/scripts/parity-check.js +38 -21
  171. package/scripts/release-flow.js +2 -2
  172. package/scripts/sync-project-index.js +51 -27
  173. package/scripts/validate-run-state-schema.js +2 -2
  174. package/scripts/version-sync-check.js +30 -30
  175. package/dist/candidate-scoring.js +0 -729
  176. package/dist/capability-core.js +0 -1189
  177. package/dist/capability-registry.js +0 -885
  178. package/dist/cli/command-surface.js +0 -494
  179. package/dist/cli/format.js +0 -56
  180. package/dist/cli/handlers/audit.js +0 -82
  181. package/dist/cli/handlers/blackboard.js +0 -81
  182. package/dist/cli/handlers/candidate.js +0 -40
  183. package/dist/cli/handlers/clones.js +0 -34
  184. package/dist/cli/handlers/collaboration.js +0 -61
  185. package/dist/cli/handlers/eval.js +0 -40
  186. package/dist/cli/handlers/ledger.js +0 -169
  187. package/dist/cli/handlers/maintenance.js +0 -107
  188. package/dist/cli/handlers/multi-agent.js +0 -165
  189. package/dist/cli/handlers/node.js +0 -41
  190. package/dist/cli/handlers/operational.js +0 -155
  191. package/dist/cli/handlers/operator.js +0 -146
  192. package/dist/cli/handlers/registry.js +0 -68
  193. package/dist/cli/handlers/run.js +0 -153
  194. package/dist/cli/handlers/scheduling.js +0 -132
  195. package/dist/cli/handlers/workbench.js +0 -41
  196. package/dist/cli/handlers/worker.js +0 -45
  197. package/dist/cli/run-summary.js +0 -45
  198. package/dist/clones.js +0 -162
  199. package/dist/collaboration.js +0 -726
  200. package/dist/commit.js +0 -592
  201. package/dist/compare.js +0 -18
  202. package/dist/coordinator/classify.js +0 -45
  203. package/dist/coordinator/paths.js +0 -42
  204. package/dist/coordinator/util.js +0 -126
  205. package/dist/coordinator.js +0 -990
  206. package/dist/daemon.js +0 -44
  207. package/dist/dispatch.js +0 -250
  208. package/dist/drive.js +0 -864
  209. package/dist/error-feedback.js +0 -419
  210. package/dist/evidence-grounding.js +0 -184
  211. package/dist/execution-backend/agent.js +0 -354
  212. package/dist/execution-backend/probes.js +0 -112
  213. package/dist/execution-backend/util.js +0 -47
  214. package/dist/execution-backend.js +0 -961
  215. package/dist/gates.js +0 -48
  216. package/dist/harness.js +0 -61
  217. package/dist/ledger.js +0 -313
  218. package/dist/loop-expansion.js +0 -60
  219. package/dist/mcp/tool-call.js +0 -470
  220. package/dist/mcp/tool-definitions.js +0 -1066
  221. package/dist/mcp-surface.js +0 -30
  222. package/dist/multi-agent/graph.js +0 -84
  223. package/dist/multi-agent/helpers.js +0 -141
  224. package/dist/multi-agent/ids.js +0 -20
  225. package/dist/multi-agent/paths.js +0 -22
  226. package/dist/multi-agent-eval/normalize.js +0 -51
  227. package/dist/multi-agent-eval.js +0 -678
  228. package/dist/multi-agent-host.js +0 -777
  229. package/dist/multi-agent.js +0 -984
  230. package/dist/node-projection.js +0 -59
  231. package/dist/node-snapshot.js +0 -260
  232. package/dist/operator-ux.js +0 -631
  233. package/dist/orchestrator/app-operations.js +0 -211
  234. package/dist/orchestrator/audit-operations.js +0 -182
  235. package/dist/orchestrator/candidate-operations.js +0 -117
  236. package/dist/orchestrator/cli-options.js +0 -294
  237. package/dist/orchestrator/collaboration-operations.js +0 -86
  238. package/dist/orchestrator/feedback-operations.js +0 -81
  239. package/dist/orchestrator/host-operations.js +0 -78
  240. package/dist/orchestrator/lifecycle-operations.js +0 -650
  241. package/dist/orchestrator/migration-operations.js +0 -44
  242. package/dist/orchestrator/multi-agent-operations.js +0 -362
  243. package/dist/orchestrator/topology-operations.js +0 -84
  244. package/dist/orchestrator.js +0 -925
  245. package/dist/pipeline-runner.js +0 -285
  246. package/dist/reclamation/hash.js +0 -72
  247. package/dist/reclamation.js +0 -812
  248. package/dist/reporter.js +0 -67
  249. package/dist/run-export.js +0 -815
  250. package/dist/run-registry/derive.js +0 -175
  251. package/dist/run-registry/format.js +0 -124
  252. package/dist/run-registry/gc.js +0 -251
  253. package/dist/run-registry/policy.js +0 -16
  254. package/dist/run-registry/queue.js +0 -115
  255. package/dist/run-registry.js +0 -850
  256. package/dist/run-state-schema.js +0 -68
  257. package/dist/scheduling.js +0 -184
  258. package/dist/state-explosion.js +0 -1014
  259. package/dist/state.js +0 -367
  260. package/dist/telemetry-ledger.js +0 -196
  261. package/dist/topology.js +0 -565
  262. package/dist/triggers.js +0 -184
  263. package/dist/trust-audit.js +0 -644
  264. package/dist/types/blackboard.js +0 -2
  265. package/dist/types/candidate.js +0 -2
  266. package/dist/types/collaboration.js +0 -2
  267. package/dist/types/core.js +0 -2
  268. package/dist/types/drive.js +0 -10
  269. package/dist/types/error-feedback.js +0 -2
  270. package/dist/types/evidence-reasoning.js +0 -2
  271. package/dist/types/execution-backend.js +0 -2
  272. package/dist/types/multi-agent.js +0 -2
  273. package/dist/types/observability.js +0 -2
  274. package/dist/types/pipeline.js +0 -2
  275. package/dist/types/reclamation.js +0 -8
  276. package/dist/types/report-bundle.js +0 -6
  277. package/dist/types/result.js +0 -2
  278. package/dist/types/run-registry.js +0 -2
  279. package/dist/types/run.js +0 -2
  280. package/dist/types/sandbox.js +0 -2
  281. package/dist/types/schedule.js +0 -2
  282. package/dist/types/state-node.js +0 -2
  283. package/dist/types/topology.js +0 -2
  284. package/dist/types/trust.js +0 -2
  285. package/dist/types/workbench.js +0 -2
  286. package/dist/types/worker.js +0 -2
  287. package/dist/types/workflow-app.js +0 -2
  288. package/dist/types.js +0 -44
  289. package/dist/util/fingerprint.js +0 -19
  290. package/dist/util/fingerprint.test.js +0 -27
  291. package/dist/verifier.js +0 -78
  292. package/dist/version.js +0 -8
  293. package/dist/workbench-host.js +0 -192
  294. package/dist/workbench.js +0 -192
  295. package/dist/worker-accept/acceptance.js +0 -114
  296. package/dist/worker-accept/blackboard-fanout.js +0 -80
  297. package/dist/worker-accept/blackboard-linkage.js +0 -19
  298. package/dist/worker-accept/context.js +0 -2
  299. package/dist/worker-accept/telemetry-ledger.js +0 -126
  300. package/dist/worker-accept/validation.js +0 -77
  301. package/dist/worker-accept/verifier-completion.js +0 -73
  302. package/dist/worker-isolation/helpers.js +0 -51
  303. package/dist/worker-isolation/paths.js +0 -46
  304. package/dist/worker-isolation.js +0 -656
  305. package/dist/workflow-api.js +0 -131
  306. /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 (v2/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
+ }