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
@@ -0,0 +1,527 @@
1
+ "use strict";
2
+ // core/state/state-explosion/graph.ts — buildCompactGraph + collapse rules.
3
+ //
4
+ // MILESTONE 4. Byte-exact port of the collapse-rule MACHINERY in the old
5
+ // build's src/state-explosion.ts (buildCompactGraph and everything it
6
+ // calls: collapseRuleFor, shouldCollapseKind, criticalPathNodeIds,
7
+ // bfsNeighborhood, filterByView, finalizeGraphRecord), PLUS a faithful
8
+ // port of `buildMultiAgentOperatorGraph`'s (src/multi-agent-operator-ux.ts)
9
+ // node/edge construction for every run-level array this milestone's state
10
+ // kernel actually carries: `tasks`, `dispatches`, `workers`,
11
+ // `candidates`/`candidateSelections`, `commits`, `feedback` (real fields
12
+ // on `WorkflowRun`, per core/state/types.ts — `candidates`/
13
+ // `candidateSelections`/`feedback`/`workers` are `unknown[]` there, so
14
+ // `runToGraphView` below defines the same minimal structural types for
15
+ // them that digest.ts uses for blackboard records: matching the old
16
+ // build's field usage exactly, degrading to empty when no milestone has
17
+ // written a record yet).
18
+ //
19
+ // DELIBERATE SCOPE CUT: the old build's `full` graph ALSO folds in
20
+ // `buildTopologyGraph`/`buildMultiAgentGraph`/`buildBlackboardGraph` (the
21
+ // topology/multi-agent/blackboard sub-graphs) and `deriveDependencies`'s
22
+ // edges — both are built entirely from `run.topologies`/`run.multiAgent`
23
+ // RECORD shapes that do not exist yet (milestone 9). `runToGraphView`
24
+ // leaves an explicit extension point (see its own comment) so milestone 9
25
+ // adds those sub-graphs and dependency edges additively, without this
26
+ // file's collapse-rule machinery changing at all.
27
+ //
28
+ // Evidence: SPEC/state-core.md "buildCompactGraph(...)", "State-explosion
29
+ // collapse rules"; v2/PLAN.md byte-compat item 9;
30
+ // src/multi-agent-operator-ux.ts:153-227 (buildMultiAgentOperatorGraph).
31
+ Object.defineProperty(exports, "__esModule", { value: true });
32
+ exports.byId = exports.GRAPH_VIEWS = void 0;
33
+ exports.runToGraphView = runToGraphView;
34
+ exports.buildCompactGraph = buildCompactGraph;
35
+ exports.runToGraphViewFromWorkflowRun = runToGraphViewFromWorkflowRun;
36
+ exports.buildCompactGraphFromView = buildCompactGraphFromView;
37
+ const size_1 = require("./size");
38
+ const helpers_1 = require("./helpers");
39
+ Object.defineProperty(exports, "byId", { enumerable: true, get: function () { return helpers_1.byId; } });
40
+ const runtime_1 = require("../../multi-agent/runtime");
41
+ const coordinator_1 = require("../../multi-agent/coordinator");
42
+ const topology_1 = require("../../multi-agent/topology");
43
+ exports.GRAPH_VIEWS = [
44
+ "full",
45
+ "compact",
46
+ "critical-path",
47
+ "failures",
48
+ "evidence",
49
+ "trust",
50
+ "topology",
51
+ "blackboard",
52
+ "candidate",
53
+ "commit-gate",
54
+ ];
55
+ /** Local re-import of `fingerprintStrings` kept file-scoped (not
56
+ * re-exported) since callers should import it from core/hash.ts or
57
+ * helpers.ts directly. */
58
+ const hash_1 = require("../../hash");
59
+ function scorePath(runId, candidateId, scoreId) {
60
+ return `${runId}/candidates/${candidateId}/scores/${scoreId}.json`;
61
+ }
62
+ /** Faithful port of `buildMultiAgentOperatorGraph`'s node/edge
63
+ * construction (src/multi-agent-operator-ux.ts:153-227), scoped to the
64
+ * run-level arrays this milestone's state kernel carries: `tasks`,
65
+ * `dispatches`, `workers`, `candidates`/`candidateSelections`, `commits`,
66
+ * `feedback`. The topology/multi-agent/blackboard sub-graphs
67
+ * (`buildTopologyGraph`/`buildMultiAgentGraph`/`buildBlackboardGraph`)
68
+ * and `deriveDependencies`'s edges are a milestone-9 concern (built
69
+ * entirely from `run.topologies`/`run.multiAgent` record shapes that do
70
+ * not exist yet) — a later milestone adds those nodes/edges into the
71
+ * same `addNode`/`addEdge` accumulators additively, without any other
72
+ * part of this file changing. */
73
+ function runToGraphView(run) {
74
+ const nodes = new Map();
75
+ const edges = [];
76
+ const addNode = (id, kind, status, label, pathValue) => {
77
+ if (!id)
78
+ return;
79
+ nodes.set(id, { id, kind, status, label, path: pathValue });
80
+ };
81
+ const addEdge = (from, to, label) => {
82
+ if (!from || !to)
83
+ return;
84
+ edges.push({ from, to, label });
85
+ };
86
+ addNode(`${run.id}:run`, "multi-agent-run-root", run.loopStage, run.id, run.paths.state);
87
+ for (const task of run.tasks || []) {
88
+ addNode(`${run.id}:task:${task.id}`, "task", task.status, task.id, task.taskPath);
89
+ addEdge(`${run.id}:run`, `${run.id}:task:${task.id}`, "owns");
90
+ addEdge(`${run.id}:task:${task.id}`, task.dispatchId ? `${run.id}:dispatch:${task.dispatchId}` : undefined, "dispatches");
91
+ addEdge(`${run.id}:task:${task.id}`, task.resultNodeId, "reports");
92
+ addEdge(`${run.id}:task:${task.id}`, task.verifierNodeId, "gates");
93
+ }
94
+ for (const dispatch of run.dispatches || []) {
95
+ addNode(`${run.id}:dispatch:${dispatch.id}`, "dispatch", "completed", dispatch.id, dispatch.manifestPath);
96
+ for (const workerId of dispatch.workerIds || [])
97
+ addEdge(`${run.id}:dispatch:${dispatch.id}`, `${run.id}:worker:${workerId}`, "dispatches");
98
+ }
99
+ for (const worker of run.workers || []) {
100
+ addNode(`${run.id}:worker:${worker.id}`, "worker", worker.status, worker.id, worker.inputPath);
101
+ addEdge(`${run.id}:worker:${worker.id}`, worker.resultNodeId, "reports");
102
+ addEdge(`${run.id}:worker:${worker.id}`, worker.output?.verifierNodeId, "gates");
103
+ for (const feedbackId of worker.feedbackIds || [])
104
+ addEdge(`${run.id}:worker:${worker.id}`, `${run.id}:feedback:${feedbackId}`, "blocks");
105
+ }
106
+ for (const candidate of run.candidates || []) {
107
+ const candidateId = `${run.id}:candidate:${candidate.id}`;
108
+ addNode(candidateId, "candidate", candidate.status, candidate.id, candidate.resultPath);
109
+ addEdge(candidate.workerId ? `${run.id}:worker:${candidate.workerId}` : candidate.resultNodeId, candidateId, "reports");
110
+ addEdge(candidate.verifierNodeId, candidateId, "gates");
111
+ for (const scoreId of candidate.scores || []) {
112
+ const nodeId = `${run.id}:score:${scoreId}`;
113
+ addNode(nodeId, "score", "completed", scoreId, scorePath(run.id, candidate.id, scoreId));
114
+ addEdge(candidateId, nodeId, "scores");
115
+ }
116
+ for (const feedbackId of candidate.feedbackIds || [])
117
+ addEdge(candidateId, `${run.id}:feedback:${feedbackId}`, "blocks");
118
+ }
119
+ for (const selection of run.candidateSelections || []) {
120
+ const nodeId = `${run.id}:selection:${selection.id}`;
121
+ addNode(nodeId, "selection", "accepted", selection.id, selection.rankingPath);
122
+ addEdge(`${run.id}:candidate:${selection.candidateId}`, nodeId, "selects");
123
+ if (selection.scoreId)
124
+ addEdge(`${run.id}:score:${selection.scoreId}`, nodeId, "selects");
125
+ addEdge(selection.verifierNodeId, nodeId, "gates");
126
+ }
127
+ for (const commit of run.commits || []) {
128
+ const nodeId = commit.stateNodeId || `${run.id}:commit:${commit.id}`;
129
+ addNode(nodeId, "commit", commit.verifierGated ? "committed" : "checkpoint", commit.id, commit.snapshotPath);
130
+ addEdge(commit.selectionId ? `${run.id}:selection:${commit.selectionId}` : undefined, nodeId, "commits");
131
+ addEdge(commit.verifierNodeId, nodeId, "gates");
132
+ }
133
+ for (const feedback of run.feedback || []) {
134
+ addNode(`${run.id}:feedback:${feedback.id}`, "feedback", feedback.status, `${feedback.severity} ${feedback.classification}`);
135
+ addEdge(feedback.nodeId, `${run.id}:feedback:${feedback.id}`, "blocks");
136
+ addEdge(feedback.taskId ? `${run.id}:task:${feedback.taskId}` : undefined, `${run.id}:feedback:${feedback.id}`, "blocks");
137
+ }
138
+ const edgeSeen = new Set();
139
+ const dedupedEdges = edges.filter((edge) => {
140
+ const key = `${edge.from}\0${edge.to}\0${edge.label || ""}`;
141
+ if (edgeSeen.has(key))
142
+ return false;
143
+ edgeSeen.add(key);
144
+ return true;
145
+ });
146
+ return {
147
+ nodes: [...nodes.values()].sort((a, b) => a.kind.localeCompare(b.kind) || a.id.localeCompare(b.id)),
148
+ edges: dedupedEdges.sort((a, b) => a.from.localeCompare(b.from) || a.to.localeCompare(b.to) || (a.label || "").localeCompare(b.label || "")),
149
+ };
150
+ }
151
+ /** `buildCompactGraph(run, view, options)` — builds the graph view via
152
+ * `runToGraphView`, then delegates to `buildCompactGraphFromView`. */
153
+ function buildCompactGraph(run, view = "compact", options = {}) {
154
+ return buildCompactGraphFromView(run.id, runToGraphView(run), view, options);
155
+ }
156
+ /** Adapts a real `WorkflowRun` (whose `workers`/`candidates`/
157
+ * `candidateSelections`/`feedback` are `unknown[]`-typed at this
158
+ * milestone — see core/state/types.ts's header note) into
159
+ * `RunToGraphViewInput`'s typed shape. The ONE cast point every shell/
160
+ * cli caller goes through, instead of repeating the cast at each call
161
+ * site; degrades to empty arrays today (genuinely the only value those
162
+ * fields can hold before their owning milestone writes real records) and
163
+ * needs no change once real records exist. */
164
+ function runToGraphViewFromWorkflowRun(run) {
165
+ const base = runToGraphView(run);
166
+ // Fold in the multi-agent, blackboard, and topology sub-graphs (the
167
+ // milestone-9 extension point promised in this file's header note). These
168
+ // add the high-volume, low-signal node kinds the collapse rules exist for
169
+ // (blackboard-message/context/snapshot, agent-membership/role) plus the
170
+ // multi-agent-run/group/fanout/fanin roots the critical path is keyed on.
171
+ // Byte-behavior port of the old build's runToGraphView, which merged the
172
+ // same three sub-graphs via summarizeMultiAgentOperator. Node `path` values
173
+ // are display-only (never affect counts/collapse), so the blackboard graph
174
+ // gets lightweight path stubs here.
175
+ const multiAgent = (0, runtime_1.buildMultiAgentGraph)(run);
176
+ const blackboardState = run.blackboard || (0, coordinator_1.emptyBlackboardState)();
177
+ const blackboard = (0, coordinator_1.buildBlackboardGraph)(run.id, blackboardState, (kind, id) => `${run.paths.runDir}/blackboard/${kind}/${id}.json`, `${run.paths.runDir}/blackboard/messages.jsonl`);
178
+ const topologyRuns = (run.topologies?.runs || []);
179
+ const topology = (0, topology_1.buildTopologyGraphFromRuns)(run.id, topologyRuns, (id) => `${run.paths.runDir}/topologies/${id}.json`);
180
+ const nodes = new Map();
181
+ for (const node of [...base.nodes, ...multiAgent.nodes, ...blackboard.nodes, ...topology.nodes]) {
182
+ if (!nodes.has(node.id))
183
+ nodes.set(node.id, node);
184
+ }
185
+ const edges = [];
186
+ const edgeSeen = new Set();
187
+ for (const edge of [...base.edges, ...multiAgent.edges, ...blackboard.edges, ...topology.edges]) {
188
+ const key = `${edge.from}\0${edge.to}\0${edge.label || ""}`;
189
+ if (edgeSeen.has(key))
190
+ continue;
191
+ edgeSeen.add(key);
192
+ edges.push(edge);
193
+ }
194
+ return {
195
+ nodes: [...nodes.values()].sort((a, b) => a.kind.localeCompare(b.kind) || a.id.localeCompare(b.id)),
196
+ edges: edges.sort((a, b) => a.from.localeCompare(b.from) || a.to.localeCompare(b.to) || (a.label || "").localeCompare(b.label || "")),
197
+ };
198
+ }
199
+ function collapseRuleFor() {
200
+ return {
201
+ bucketBy: (node, parentOf) => {
202
+ switch (node.kind) {
203
+ case "blackboard-message":
204
+ return "messages";
205
+ case "blackboard-context":
206
+ return "contexts";
207
+ case "agent-membership": {
208
+ const parent = parentOf(node.id);
209
+ return `memberships:${parent ? parent.split(":").pop() : "unscoped"}`;
210
+ }
211
+ case "worker":
212
+ return "workers";
213
+ case "score":
214
+ return "scores";
215
+ case "blackboard-snapshot":
216
+ return "snapshots";
217
+ default:
218
+ return `${node.kind}`;
219
+ }
220
+ },
221
+ };
222
+ }
223
+ /** Collapsible kinds ONLY (v2/PLAN.md byte-compat item 9): high-volume,
224
+ * low-individual-signal. `decisions, artifacts, fanins, candidates,
225
+ * selections, commits, feedback` are NEVER collapsed so failures,
226
+ * evidence, policy, and judge rationale stay visible. */
227
+ function shouldCollapseKind(kind) {
228
+ return [
229
+ "blackboard-message",
230
+ "blackboard-context",
231
+ "agent-membership",
232
+ "worker",
233
+ "score",
234
+ "blackboard-snapshot",
235
+ "agent-role",
236
+ ].includes(kind);
237
+ }
238
+ /** Critical-path node ids. The run root, plus the multi-agent-run/group/
239
+ * fanout/fanin roots, the candidate/selection reasoning chain, and every
240
+ * verifier-gated commit — derived from the merged graph's own node kinds
241
+ * (the sub-graphs `runToGraphViewFromWorkflowRun` now folds in). Byte-
242
+ * behavior port of the old build's criticalPathNodeIds. The two extension
243
+ * points (`reasoningCriticalIds`, `linkedFailureIds`) still feed in extra
244
+ * ids without reshaping this function. */
245
+ function criticalPathNodeIds(runId, options, nodes = []) {
246
+ const ids = [`${runId}:run`, ...(options.linkedFailureIds || [])];
247
+ for (const node of nodes) {
248
+ switch (node.kind) {
249
+ case "multi-agent-run":
250
+ case "agent-group":
251
+ case "agent-fanout":
252
+ case "agent-fanin":
253
+ case "candidate":
254
+ case "selection":
255
+ ids.push(node.id);
256
+ break;
257
+ case "commit":
258
+ if (node.status === "committed")
259
+ ids.push(node.id);
260
+ break;
261
+ default:
262
+ break;
263
+ }
264
+ }
265
+ return (0, helpers_1.unique)(ids);
266
+ }
267
+ function bfsNeighborhood(focus, nodes, edges, depth) {
268
+ const adjacency = new Map();
269
+ for (const edge of edges) {
270
+ if (!adjacency.has(edge.from))
271
+ adjacency.set(edge.from, new Set());
272
+ if (!adjacency.has(edge.to))
273
+ adjacency.set(edge.to, new Set());
274
+ adjacency.get(edge.from).add(edge.to);
275
+ adjacency.get(edge.to).add(edge.from);
276
+ }
277
+ const keep = new Set([focus]);
278
+ let frontier = new Set([focus]);
279
+ for (let level = 0; level < Math.max(0, depth); level += 1) {
280
+ const next = new Set();
281
+ for (const id of frontier) {
282
+ for (const neighbor of adjacency.get(id) || []) {
283
+ if (!keep.has(neighbor)) {
284
+ keep.add(neighbor);
285
+ next.add(neighbor);
286
+ }
287
+ }
288
+ }
289
+ frontier = next;
290
+ }
291
+ return keep;
292
+ }
293
+ function expansionCommandFor(runId, key) {
294
+ if (key === "messages" || key.startsWith("thread"))
295
+ return `node scripts/cw.js blackboard message list ${runId}`;
296
+ if (key.startsWith("memberships"))
297
+ return `node scripts/cw.js multi-agent graph ${runId} --view full --json`;
298
+ return `node scripts/cw.js multi-agent graph ${runId} --view full --focus ${key} --json`;
299
+ }
300
+ function filterByView(runId, view, full, protectedIds) {
301
+ const keepKinds = (kinds) => {
302
+ const ids = new Set();
303
+ for (const node of full.nodes) {
304
+ if (kinds.includes(node.kind) || protectedIds.has(node.id))
305
+ ids.add(node.id);
306
+ }
307
+ return ids;
308
+ };
309
+ let ids;
310
+ switch (view) {
311
+ case "failures": {
312
+ ids = new Set();
313
+ for (const node of full.nodes)
314
+ if ((0, helpers_1.isProtectedStatus)(node.status))
315
+ ids.add(node.id);
316
+ ids.add(`${runId}:run`);
317
+ break;
318
+ }
319
+ case "evidence":
320
+ ids = keepKinds([
321
+ "multi-agent-run-root",
322
+ "blackboard",
323
+ "blackboard-topic",
324
+ "blackboard-artifact",
325
+ "blackboard-message",
326
+ "agent-membership",
327
+ "agent-fanin",
328
+ "candidate",
329
+ "selection",
330
+ "commit",
331
+ ]);
332
+ break;
333
+ case "trust":
334
+ ids = keepKinds(["multi-agent-run-root", "blackboard", "coordinator-decision", "agent-fanin", "candidate", "selection", "commit"]);
335
+ break;
336
+ case "topology":
337
+ ids = keepKinds(["multi-agent-run-root", "topology", "multi-agent-run", "agent-group", "agent-role", "agent-fanout", "agent-fanin"]);
338
+ break;
339
+ case "blackboard":
340
+ ids = keepKinds([
341
+ "multi-agent-run-root",
342
+ "blackboard",
343
+ "blackboard-topic",
344
+ "blackboard-message",
345
+ "blackboard-context",
346
+ "blackboard-artifact",
347
+ "blackboard-snapshot",
348
+ "coordinator-decision",
349
+ ]);
350
+ break;
351
+ case "candidate":
352
+ ids = keepKinds(["multi-agent-run-root", "candidate", "score", "selection", "worker", "agent-fanin"]);
353
+ break;
354
+ case "commit-gate":
355
+ ids = keepKinds(["multi-agent-run-root", "selection", "commit", "candidate", "agent-fanin"]);
356
+ break;
357
+ default:
358
+ ids = new Set(full.nodes.map((n) => n.id));
359
+ }
360
+ const nodes = full.nodes.filter((node) => ids.has(node.id));
361
+ const edges = full.edges.filter((edge) => ids.has(edge.from) && ids.has(edge.to));
362
+ return { nodes, edges };
363
+ }
364
+ function finalizeGraphRecord(runId, view, options, full, built) {
365
+ const collapsedNodeCount = built.syntheticNodes.reduce((acc, syn) => acc + syn.collapsedNodeCount, 0);
366
+ const collapsedEdgeCount = built.syntheticNodes.reduce((acc, syn) => acc + syn.collapsedEdgeCount, 0);
367
+ const blockedReasons = (0, helpers_1.unique)(built.syntheticNodes.filter((s) => s.blockedReason).map((s) => s.blockedReason));
368
+ return {
369
+ schemaVersion: 1,
370
+ runId,
371
+ id: `graph-${view}${options.focus ? `:focus:${(0, helpers_1.slug)(options.focus)}` : ""}`,
372
+ scope: "run",
373
+ view,
374
+ focus: options.focus,
375
+ depth: options.depth,
376
+ fullNodeCount: full.nodes.length,
377
+ fullEdgeCount: full.edges.length,
378
+ compactNodeCount: built.nodes.length,
379
+ compactEdgeCount: built.edges.length,
380
+ collapsedNodeCount,
381
+ collapsedEdgeCount,
382
+ syntheticNodes: built.syntheticNodes,
383
+ criticalPath: built.critical,
384
+ blockedReasons,
385
+ nodes: built.nodes,
386
+ edges: built.edges,
387
+ sourceRecordIds: full.nodes.map((n) => n.id).sort(),
388
+ sourceFingerprint: (0, hash_1.fingerprintStrings)(full.nodes.map((n) => `${n.id}:${n.status}`)),
389
+ includedCount: built.nodes.length,
390
+ omittedCount: collapsedNodeCount,
391
+ importantRefs: built.critical,
392
+ evidenceRefs: [],
393
+ trustAuditEventRefs: [],
394
+ generatedAt: options.now || new Date().toISOString(),
395
+ status: "valid",
396
+ deterministic: true,
397
+ nextAction: collapsedNodeCount > 0
398
+ ? `node scripts/cw.js multi-agent graph ${runId} --view full --json`
399
+ : `node scripts/cw.js multi-agent graph ${runId} --view ${view} --json`,
400
+ };
401
+ }
402
+ /** The collapse-rule core, generic over any `{ nodes, edges }` graph view
403
+ * (see the file header on why this milestone feeds it the run's task/
404
+ * dispatch/worker/candidate/commit/feedback graph via `runToGraphView`,
405
+ * and why a later milestone's fuller graph — with topology/multi-agent/
406
+ * blackboard sub-graphs folded in — can reuse this unchanged). */
407
+ function buildCompactGraphFromView(runId, full, view = "compact", options = {}) {
408
+ const thresholds = options.thresholds || size_1.DEFAULT_STATE_EXPLOSION_THRESHOLDS;
409
+ const critical = criticalPathNodeIds(runId, options, full.nodes);
410
+ const protectedIds = new Set(critical);
411
+ // Failures, blocked, rejected, conflicting nodes are always preserved.
412
+ for (const node of full.nodes) {
413
+ if ((0, helpers_1.isProtectedStatus)(node.status))
414
+ protectedIds.add(node.id);
415
+ }
416
+ // Reasoning-critical nodes are on the critical path and must never be
417
+ // collapsed into a synthetic summary node (v2/PLAN.md byte-compat item 9).
418
+ for (const id of options.reasoningCriticalIds || [])
419
+ protectedIds.add(id);
420
+ const parents = (0, helpers_1.parentMap)(full.edges);
421
+ const parentOf = (id) => parents.get(id);
422
+ let scopeNodes = full.nodes;
423
+ let scopeEdges = full.edges;
424
+ if (view !== "full" && view !== "compact" && view !== "critical-path") {
425
+ const filtered = filterByView(runId, view, full, protectedIds);
426
+ scopeNodes = filtered.nodes;
427
+ scopeEdges = filtered.edges;
428
+ }
429
+ // Focus + depth: keep nodes within BFS depth of focus; collapse the rest.
430
+ let focusKeep;
431
+ if (options.focus) {
432
+ focusKeep = bfsNeighborhood(options.focus, scopeNodes, scopeEdges, options.depth ?? 1);
433
+ for (const id of focusKeep)
434
+ protectedIds.add(id);
435
+ }
436
+ const collapseEnabled = view === "compact" || view === "critical-path" || Boolean(options.focus);
437
+ if (view === "full" || !collapseEnabled) {
438
+ // No collapse: emit scoped graph verbatim (still records provenance +
439
+ // critical path).
440
+ return finalizeGraphRecord(runId, view, options, full, {
441
+ nodes: scopeNodes.map((node) => ({ ...node })),
442
+ edges: scopeEdges.map((edge) => ({ ...edge })),
443
+ syntheticNodes: [],
444
+ critical,
445
+ });
446
+ }
447
+ // Determine collapse buckets per node.
448
+ const rule = collapseRuleFor();
449
+ const keep = new Set();
450
+ const buckets = new Map();
451
+ for (const node of scopeNodes) {
452
+ if (protectedIds.has(node.id) || (focusKeep && focusKeep.has(node.id))) {
453
+ keep.add(node.id);
454
+ continue;
455
+ }
456
+ if (view === "critical-path") {
457
+ // Collapse everything not on the critical path into one bucket per kind.
458
+ const bucketKey = `critical-context:${node.kind}`;
459
+ buckets.set(bucketKey, [...(buckets.get(bucketKey) || []), node.id]);
460
+ continue;
461
+ }
462
+ if (!shouldCollapseKind(node.kind)) {
463
+ keep.add(node.id);
464
+ continue;
465
+ }
466
+ const bucketKey = rule.bucketBy(node, parentOf);
467
+ buckets.set(bucketKey, [...(buckets.get(bucketKey) || []), node.id]);
468
+ }
469
+ // Buckets smaller than the collapse threshold stay expanded (unless
470
+ // critical-path — v2/PLAN.md byte-compat item 9).
471
+ const synthetic = [];
472
+ const collapsedNodeIds = new Map(); // sourceNodeId -> syntheticId
473
+ for (const [bucketKey, ids] of [...buckets.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
474
+ if (view !== "critical-path" && ids.length < thresholds.collapseBucket) {
475
+ for (const id of ids)
476
+ keep.add(id);
477
+ continue;
478
+ }
479
+ const members = scopeNodes.filter((node) => ids.includes(node.id));
480
+ const internalEdges = scopeEdges.filter((edge) => ids.includes(edge.from) && ids.includes(edge.to));
481
+ const syntheticId = `${runId}:summary:${(0, helpers_1.slug)(bucketKey)}`;
482
+ const dominant = (0, helpers_1.dominantStatus)(members.map((m) => m.status));
483
+ const blocked = members.find((m) => (0, helpers_1.isProtectedStatus)(m.status));
484
+ synthetic.push({
485
+ id: syntheticId,
486
+ kind: "summary",
487
+ label: `${bucketKey} (${ids.length} collapsed)`,
488
+ status: dominant,
489
+ collapsedNodeCount: ids.length,
490
+ collapsedEdgeCount: internalEdges.length,
491
+ sourceIds: [...ids].sort(),
492
+ dominantStatus: dominant,
493
+ blockedReason: blocked ? `${blocked.kind} ${blocked.id} is ${blocked.status}` : undefined,
494
+ expansionCommand: expansionCommandFor(runId, bucketKey),
495
+ });
496
+ for (const id of ids)
497
+ collapsedNodeIds.set(id, syntheticId);
498
+ }
499
+ const redirect = (id) => collapsedNodeIds.get(id) || id;
500
+ const nodes = [];
501
+ for (const node of scopeNodes) {
502
+ if (keep.has(node.id))
503
+ nodes.push({ ...node });
504
+ }
505
+ for (const syn of synthetic) {
506
+ nodes.push({ id: syn.id, kind: "summary", label: syn.label, status: syn.status, synthetic: syn });
507
+ }
508
+ const edgeSeen = new Set();
509
+ const edges = [];
510
+ for (const edge of scopeEdges) {
511
+ const from = redirect(edge.from);
512
+ const to = redirect(edge.to);
513
+ if (from === to)
514
+ continue; // edge fully internal to a synthetic node
515
+ const edgeKey = `${from}\0${to}\0${edge.label || ""}`;
516
+ if (edgeSeen.has(edgeKey))
517
+ continue;
518
+ edgeSeen.add(edgeKey);
519
+ edges.push({ from, to, label: edge.label });
520
+ }
521
+ return finalizeGraphRecord(runId, view, options, full, {
522
+ nodes: nodes.sort((a, b) => a.kind.localeCompare(b.kind) || a.id.localeCompare(b.id)),
523
+ edges: edges.sort((a, b) => a.from.localeCompare(b.from) || a.to.localeCompare(b.to) || (a.label || "").localeCompare(b.label || "")),
524
+ syntheticNodes: synthetic.sort((a, b) => a.id.localeCompare(b.id)),
525
+ critical,
526
+ });
527
+ }
@@ -1,4 +1,23 @@
1
1
  "use strict";
2
+ // core/state/state-explosion/helpers.ts — pure, stateless helpers for the
3
+ // state-explosion derived-index layer.
4
+ //
5
+ // MILESTONE 4. Byte-exact port of the old build's
6
+ // src/state-explosion/helpers.ts. `fingerprintStrings`/`fingerprintRecords`
7
+ // live in core/hash.ts (the one hash module, per v2/PLAN.md's byte-compat
8
+ // item 2) and are re-exported here so every importer of this file keeps
9
+ // the same surface the old build had.
10
+ //
11
+ // BYTE-COMPAT ITEM 3 [load-bearing]: this file's `unique` DROPS falsy
12
+ // values AND SORTS its output. This is the kernel-side, SORTING variant —
13
+ // a later milestone's topology/candidate/host-decide code has its OWN
14
+ // separate `unique` (dedup-only, unsorted) that must NEVER be merged with
15
+ // this one; collapsing them changes persisted record order and eval
16
+ // parity (v2/PLAN.md byte-compat item 3, Open risk 2). See
17
+ // core/state/state-node.ts's own local `unique` for the sibling that must
18
+ // stay separate.
19
+ //
20
+ // Evidence: SPEC/state-core.md "Helpers (src/state-explosion/helpers.ts)".
2
21
  Object.defineProperty(exports, "__esModule", { value: true });
3
22
  exports.fingerprintStrings = exports.fingerprintRecords = void 0;
4
23
  exports.isProtectedStatus = isProtectedStatus;
@@ -11,12 +30,16 @@ exports.unique = unique;
11
30
  exports.byId = byId;
12
31
  exports.truncate = truncate;
13
32
  exports.slug = slug;
14
- const fingerprint_1 = require("../util/fingerprint");
15
- Object.defineProperty(exports, "fingerprintRecords", { enumerable: true, get: function () { return fingerprint_1.fingerprintRecords; } });
16
- Object.defineProperty(exports, "fingerprintStrings", { enumerable: true, get: function () { return fingerprint_1.fingerprintStrings; } });
33
+ const hash_1 = require("../../hash");
34
+ Object.defineProperty(exports, "fingerprintRecords", { enumerable: true, get: function () { return hash_1.fingerprintRecords; } });
35
+ Object.defineProperty(exports, "fingerprintStrings", { enumerable: true, get: function () { return hash_1.fingerprintStrings; } });
36
+ /** True for `failed`, `blocked`, `rejected`, `conflicting` — the never-
37
+ * collapse status set (v2/PLAN.md byte-compat item 9). */
17
38
  function isProtectedStatus(status) {
18
39
  return ["failed", "blocked", "rejected", "conflicting"].includes(status);
19
40
  }
41
+ /** Priority order `failed, blocked, rejected, conflicting, running,
42
+ * pending`; else the first status; else `"completed"`. */
20
43
  function dominantStatus(statuses) {
21
44
  for (const priority of ["failed", "blocked", "rejected", "conflicting", "running", "pending"]) {
22
45
  if (statuses.includes(priority))
@@ -24,6 +47,8 @@ function dominantStatus(statuses) {
24
47
  }
25
48
  return statuses[0] || "completed";
26
49
  }
50
+ /** First edge in wins — `Map<childId, parentId>` built by iterating edges
51
+ * in order and only setting an id the first time it is seen as a `to`. */
27
52
  function parentMap(edges) {
28
53
  const parents = new Map();
29
54
  for (const edge of edges) {
@@ -32,6 +57,7 @@ function parentMap(edges) {
32
57
  }
33
58
  return parents;
34
59
  }
60
+ /** Key-sorted `JSON.stringify` — used for deterministic eval lines. */
35
61
  function stableLine(value) {
36
62
  return JSON.stringify(sortKeys(value));
37
63
  }
@@ -50,16 +76,21 @@ function sortKeys(value) {
50
76
  function stripRunId(run, id) {
51
77
  return id.startsWith(`${run.id}:`) ? id.slice(run.id.length + 1) : id;
52
78
  }
79
+ /** DROPS falsy values, then sorts (default string sort). This is the
80
+ * kernel-side SORTING `unique` — see the file header's byte-compat note;
81
+ * never merge with an unsorted dedup-only sibling. */
53
82
  function unique(values) {
54
83
  return Array.from(new Set(values.filter(Boolean))).sort();
55
84
  }
56
85
  function byId(a, b) {
57
86
  return a.id.localeCompare(b.id);
58
87
  }
88
+ /** Whitespace-collapsed; over 80 chars becomes the first 77 chars + `...`. */
59
89
  function truncate(value) {
60
90
  const single = value.replace(/\s+/g, " ").trim();
61
91
  return single.length > 80 ? `${single.slice(0, 77)}...` : single;
62
92
  }
93
+ /** Chars outside `[a-zA-Z0-9._:-]` become `-`. */
63
94
  function slug(value) {
64
95
  return value.replace(/[^a-zA-Z0-9._:-]/g, "-");
65
96
  }