cool-workflow 0.1.80 → 0.1.82

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 (122) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/README.md +42 -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/agent-config.js +21 -7
  11. package/dist/candidate-scoring.js +62 -48
  12. package/dist/capability-core.js +152 -96
  13. package/dist/capability-registry.js +160 -174
  14. package/dist/cli.js +90 -100
  15. package/dist/collaboration.js +5 -6
  16. package/dist/commit.js +232 -209
  17. package/dist/compare.js +18 -0
  18. package/dist/coordinator/classify.js +45 -0
  19. package/dist/coordinator/paths.js +42 -0
  20. package/dist/coordinator/util.js +126 -0
  21. package/dist/coordinator.js +127 -300
  22. package/dist/dispatch.js +46 -3
  23. package/dist/drive.js +7 -7
  24. package/dist/error-feedback.js +8 -4
  25. package/dist/evidence-reasoning.js +5 -2
  26. package/dist/execution-backend/agent.js +294 -0
  27. package/dist/execution-backend/probes.js +96 -0
  28. package/dist/execution-backend/util.js +47 -0
  29. package/dist/execution-backend.js +77 -450
  30. package/dist/gates.js +48 -0
  31. package/dist/mcp-server.js +34 -173
  32. package/dist/multi-agent/graph.js +84 -0
  33. package/dist/multi-agent/helpers.js +141 -0
  34. package/dist/multi-agent/ids.js +20 -0
  35. package/dist/multi-agent/paths.js +22 -0
  36. package/dist/multi-agent-eval/format.js +194 -0
  37. package/dist/multi-agent-eval/normalize.js +51 -0
  38. package/dist/multi-agent-eval.js +63 -242
  39. package/dist/multi-agent-host.js +53 -40
  40. package/dist/multi-agent-operator-ux.js +2 -1
  41. package/dist/multi-agent-trust.js +5 -5
  42. package/dist/multi-agent.js +125 -314
  43. package/dist/node-projection.js +59 -0
  44. package/dist/node-snapshot.js +11 -21
  45. package/dist/observability/format.js +61 -0
  46. package/dist/observability/intake.js +98 -0
  47. package/dist/observability.js +14 -160
  48. package/dist/operator-ux/format.js +364 -0
  49. package/dist/operator-ux.js +22 -363
  50. package/dist/orchestrator/lifecycle-operations.js +22 -1
  51. package/dist/orchestrator/report.js +8 -0
  52. package/dist/orchestrator.js +41 -10
  53. package/dist/reclamation.js +32 -55
  54. package/dist/run-export.js +138 -14
  55. package/dist/run-registry/derive.js +172 -0
  56. package/dist/run-registry/format.js +124 -0
  57. package/dist/run-registry/gc.js +251 -0
  58. package/dist/run-registry/policy.js +16 -0
  59. package/dist/run-registry/queue.js +116 -0
  60. package/dist/run-registry.js +78 -593
  61. package/dist/run-state-schema.js +1 -0
  62. package/dist/sandbox-profile.js +43 -2
  63. package/dist/scheduler.js +34 -4
  64. package/dist/state-explosion/format.js +159 -0
  65. package/dist/state-explosion/helpers.js +82 -0
  66. package/dist/state-explosion.js +65 -283
  67. package/dist/state-node.js +19 -4
  68. package/dist/telemetry-attestation.js +55 -0
  69. package/dist/telemetry-demo.js +15 -3
  70. package/dist/telemetry-ledger.js +60 -15
  71. package/dist/topology.js +50 -12
  72. package/dist/triggers.js +33 -14
  73. package/dist/trust-audit.js +215 -71
  74. package/dist/validation.js +328 -0
  75. package/dist/version.js +1 -1
  76. package/dist/worker-isolation/helpers.js +51 -0
  77. package/dist/worker-isolation/paths.js +46 -0
  78. package/dist/worker-isolation.js +182 -173
  79. package/docs/agent-delegation-drive.7.md +14 -0
  80. package/docs/cli-mcp-parity.7.md +5 -0
  81. package/docs/contract-migration-tooling.7.md +3 -0
  82. package/docs/control-plane-scheduling.7.md +3 -0
  83. package/docs/dogfood/resume-drive-real-agent-2026-06-14.md +40 -0
  84. package/docs/durable-state-and-locking.7.md +5 -0
  85. package/docs/evidence-adoption-reasoning-chain.7.md +3 -0
  86. package/docs/execution-backends.7.md +3 -0
  87. package/docs/index.md +1 -0
  88. package/docs/launch/launch-kit.md +46 -23
  89. package/docs/launch/pre-launch-checklist.md +14 -14
  90. package/docs/multi-agent-cli-mcp-surface.7.md +5 -0
  91. package/docs/multi-agent-eval-replay-harness.7.md +3 -0
  92. package/docs/multi-agent-operator-ux.7.md +3 -0
  93. package/docs/multi-agent-trust-policy-audit.7.md +27 -0
  94. package/docs/node-snapshot-diff-replay.7.md +3 -0
  95. package/docs/observability-cost-accounting.7.md +3 -0
  96. package/docs/project-index.md +23 -6
  97. package/docs/real-execution-backends.7.md +3 -0
  98. package/docs/release-and-migration.7.md +5 -0
  99. package/docs/release-tooling.7.md +35 -2
  100. package/docs/run-registry-control-plane.7.md +55 -8
  101. package/docs/run-retention-reclamation.7.md +5 -0
  102. package/docs/state-explosion-management.7.md +3 -0
  103. package/docs/team-collaboration.7.md +3 -0
  104. package/docs/trust-model.md +267 -0
  105. package/docs/vendor-manifest-loadability.7.md +43 -0
  106. package/docs/web-desktop-workbench.7.md +3 -0
  107. package/manifest/plugin.manifest.json +1 -1
  108. package/manifest/source-context-profiles.json +1 -1
  109. package/package.json +4 -2
  110. package/scripts/agents/builtin-templates.json +7 -0
  111. package/scripts/bump-version.js +5 -11
  112. package/scripts/canonical-apps-list.js +64 -0
  113. package/scripts/canonical-apps.js +19 -4
  114. package/scripts/children/batch-delegate-child.js +58 -0
  115. package/scripts/children/http-delegate-child.js +39 -0
  116. package/scripts/dogfood-release.js +1 -1
  117. package/scripts/golden-path.js +4 -4
  118. package/scripts/parity-check.js +5 -0
  119. package/scripts/release-check.js +5 -1
  120. package/scripts/release-flow.js +181 -5
  121. package/scripts/version-sync-check.js +5 -8
  122. package/dist/capability-dispatcher.js +0 -86
@@ -58,6 +58,7 @@ exports.OPTIONAL_TOP_LEVEL_KEYS = [
58
58
  "audit",
59
59
  "workers",
60
60
  "sandboxProfiles",
61
+ "customSandboxProfiles",
61
62
  "candidates",
62
63
  "candidateSelections",
63
64
  "multiAgent",
@@ -124,7 +124,37 @@ function showBundledSandboxProfile(id, context = defaultSandboxContext()) {
124
124
  return resolveSandboxProfile(profile, context);
125
125
  }
126
126
  function resolveSandboxProfileById(id, context = defaultSandboxContext()) {
127
- return showBundledSandboxProfile(id || exports.DEFAULT_SANDBOX_PROFILE_ID, context);
127
+ const requested = id || exports.DEFAULT_SANDBOX_PROFILE_ID;
128
+ if (isBundledSandboxProfileId(requested))
129
+ return showBundledSandboxProfile(requested, context);
130
+ // A non-bundled id that resolves to a readable profile FILE is a CUSTOM profile:
131
+ // validate and ENFORCE it (the resolved policy snapshots onto the worker scope).
132
+ // This closes the gap where `sandbox validate` accepted a custom profile that
133
+ // dispatch/worker-isolation then refused — validated but never enforceable.
134
+ // A non-bundled, non-file id still fails closed via showBundledSandboxProfile.
135
+ const absolute = node_path_1.default.resolve(requested);
136
+ if (node_fs_1.default.existsSync(absolute) && node_fs_1.default.statSync(absolute).isFile()) {
137
+ const result = validateSandboxProfileFile(requested, context);
138
+ if (!result.valid || !result.profile) {
139
+ throw new SandboxProfileError("sandbox-profile-invalid", `Custom sandbox profile is invalid: ${requested}`, {
140
+ details: { issues: result.issues }
141
+ });
142
+ }
143
+ return result.profile;
144
+ }
145
+ // H7: a custom profile loaded from a FILE at dispatch persists as a DEFINITION
146
+ // in run.customSandboxProfiles (threaded here as context.customProfiles). After a
147
+ // worker scope snapshot is lost, the boundary re-resolves by the profile's
148
+ // LOGICAL id (e.g. "my-custom"), and the dispatch-time file path is gone. Resolve
149
+ // the persisted definition against the CURRENT (worker) context so worker-specific
150
+ // path tokens ($workerDir etc.) bind to THIS worker — re-enforcing the same
151
+ // policy instead of throwing not-found. This runs only after the bundled +
152
+ // file-path branches, so a custom id never shadows a bundled or on-disk profile.
153
+ const customDefinition = context.customProfiles?.[requested];
154
+ if (customDefinition) {
155
+ return resolveSandboxProfile(customDefinition, context);
156
+ }
157
+ return showBundledSandboxProfile(requested, context);
128
158
  }
129
159
  function resolveSandboxProfile(profile, context = defaultSandboxContext()) {
130
160
  const issues = validateSandboxProfileDefinition(profile, context);
@@ -189,6 +219,14 @@ function validateSandboxProfileFile(profileFile, context = defaultSandboxContext
189
219
  issues.push(issue("sandbox-profile-invalid", `Profile file is not valid JSON: ${messageOf(error)}`, absolutePath));
190
220
  return { valid: false, profileFile: absolutePath, issues };
191
221
  }
222
+ // Fail closed if a CUSTOM file reuses a BUNDLED id (H7 hardening): resolution is
223
+ // bundled-first, so a custom "default"/"workspace-write"/... would be silently
224
+ // shadowed by the WIDER bundled policy on a snapshot-loss re-resolve — widening
225
+ // the sandbox with no error. Reserve the bundled names for bundled profiles.
226
+ if (profile && typeof profile.id === "string" && isBundledSandboxProfileId(profile.id)) {
227
+ issues.push(issue("sandbox-profile-invalid", `Custom sandbox profile id "${profile.id}" is reserved (collides with a bundled profile); choose a different id`, absolutePath));
228
+ return { valid: false, profileFile: absolutePath, issues };
229
+ }
192
230
  issues.push(...validateSandboxProfileDefinition(profile, context));
193
231
  if (issues.length)
194
232
  return { valid: false, profileFile: absolutePath, issues };
@@ -280,7 +318,10 @@ function upsertRunSandboxPolicy(run, policy) {
280
318
  function sandboxContextForRun(run) {
281
319
  return {
282
320
  cwd: run.cwd,
283
- runDir: run.paths.runDir
321
+ runDir: run.paths.runDir,
322
+ // H7: thread persisted custom profile DEFINITIONS so a boundary re-resolve by
323
+ // logical id can find + re-resolve a custom profile after snapshot loss.
324
+ customProfiles: run.customSandboxProfiles
284
325
  };
285
326
  }
286
327
  function sandboxContextForValidation(cwd = process.cwd()) {
package/dist/scheduler.js CHANGED
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.Scheduler = void 0;
7
+ const node_crypto_1 = __importDefault(require("node:crypto"));
7
8
  const node_fs_1 = __importDefault(require("node:fs"));
8
9
  const node_path_1 = __importDefault(require("node:path"));
9
10
  const state_1 = require("./state");
@@ -233,19 +234,48 @@ function matchesCron(value, expr, min, max) {
233
234
  return Number.isInteger(parsed) && parsed >= min && parsed <= max && parsed === value;
234
235
  });
235
236
  }
237
+ // Deterministic jitter (replay-determinism self-audit): the jittered Date is NOT a
238
+ // runtime-only sleep — it lands in persisted/replayed state (task.nextRunAt and,
239
+ // transitively, the schedule store), so a Math.random() offset broke replay
240
+ // determinism. The spread (0..jitterSeconds) is now derived from a content hash of
241
+ // the base instant, so the same base time + jitter window always yields the same
242
+ // offset; distinct base times still spread out across the window. The base Date is
243
+ // itself an edge timestamp that is recorded once.
236
244
  function addJitter(date, jitterSeconds) {
237
245
  if (!jitterSeconds)
238
246
  return date;
239
- const offset = Math.floor(Math.random() * (jitterSeconds + 1)) * 1000;
240
- return new Date(date.getTime() + offset);
247
+ const digest = node_crypto_1.default.createHash("sha256").update(`${date.getTime()}`).digest();
248
+ const seconds = digest.readUInt32BE(0) % (jitterSeconds + 1);
249
+ return new Date(date.getTime() + seconds * 1000);
241
250
  }
251
+ // Deterministic schedule id (replay-determinism self-audit): the stamp is an edge
252
+ // timestamp (recorded once), but the former Math.random() suffix made each
253
+ // persisted schedule id non-reproducible. The suffix is now a content hash of the
254
+ // schedule's deterministic identity (kind + the recorded stamp), so re-deriving the
255
+ // id for a recorded schedule yields the byte-identical value while schedules created
256
+ // at distinct instants still get distinct ids. Mirrors src/worker-isolation/paths.ts.
257
+ let scheduleIdSequence = 0;
242
258
  function createScheduleId(kind) {
243
259
  const stamp = new Date().toISOString().replace(/[-:]/g, "").replace(/\..+/, "Z");
244
- return `${kind}-${stamp}-${Math.random().toString(36).slice(2, 8)}`;
260
+ // Second-resolution stamp: two schedules of the same kind created within one
261
+ // second would otherwise collide on an identical id. process.pid + a monotonic
262
+ // counter break the tie across concurrent processes and within one process,
263
+ // deterministically (not a PRNG).
264
+ scheduleIdSequence += 1;
265
+ const suffix = node_crypto_1.default.createHash("sha256").update(`${kind}:${stamp}:${process.pid}:${scheduleIdSequence}`).digest("hex").slice(0, 6);
266
+ return `${kind}-${stamp}-${suffix}`;
245
267
  }
268
+ // Deterministic schedule-run (history) id — same rationale as createScheduleId. The
269
+ // history record stamp differs from the owning schedule's, so the hashed identity
270
+ // (kind + run stamp) stays distinct from the schedule id while remaining a pure
271
+ // function of already-recorded state.
272
+ let scheduleRunIdSequence = 0;
246
273
  function createScheduleRunId(kind) {
247
274
  const stamp = new Date().toISOString().replace(/[-:]/g, "").replace(/\..+/, "Z");
248
- return `run-${kind}-${stamp}-${Math.random().toString(36).slice(2, 8)}`;
275
+ // pid + counter break same-kind/same-second collisions (see createScheduleId).
276
+ scheduleRunIdSequence += 1;
277
+ const suffix = node_crypto_1.default.createHash("sha256").update(`run:${kind}:${stamp}:${process.pid}:${scheduleRunIdSequence}`).digest("hex").slice(0, 6);
278
+ return `run-${kind}-${stamp}-${suffix}`;
249
279
  }
250
280
  function requiredString(value, name) {
251
281
  const text = stringOption(value);
@@ -0,0 +1,159 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatStateExplosionReport = formatStateExplosionReport;
4
+ exports.formatCompactGraph = formatCompactGraph;
5
+ exports.formatBlackboardDigest = formatBlackboardDigest;
6
+ exports.stateExplosionReportLines = stateExplosionReportLines;
7
+ function formatStateExplosionReport(report) {
8
+ const lines = [];
9
+ const size = report.stateSize;
10
+ lines.push(`State Explosion Report: ${report.runId}`);
11
+ lines.push(`Freshness: ${report.freshness.status}${report.freshness.staleScopes.length ? ` (stale: ${report.freshness.staleScopes.join(", ")})` : ""}`);
12
+ lines.push("");
13
+ lines.push("State Size");
14
+ lines.push(` records=${size.total}; graph nodes=${size.graphNodes}; graph edges=${size.graphEdges}; messages=${size.messages}; compaction=${size.compactionRecommended ? "recommended" : "not needed"}`);
15
+ for (const reason of size.reasons)
16
+ lines.push(` - ${reason}`);
17
+ lines.push("");
18
+ lines.push("Compact Graph");
19
+ lines.push(` full=${report.compactGraph.fullNodeCount} nodes/${report.compactGraph.fullEdgeCount} edges -> compact=${report.compactGraph.compactNodeCount} nodes/${report.compactGraph.compactEdgeCount} edges`);
20
+ if (report.compactGraph.collapsedNodeCount > 0) {
21
+ lines.push(` Graph compacted: ${report.compactGraph.collapsedNodeCount} nodes collapsed into ${report.compactGraph.syntheticNodes.length} summary nodes`);
22
+ }
23
+ for (const syn of report.compactGraph.syntheticNodes) {
24
+ lines.push(` [${syn.dominantStatus}] ${syn.id} collapses ${syn.collapsedNodeCount} nodes/${syn.collapsedEdgeCount} edges${syn.blockedReason ? ` blocked=${syn.blockedReason}` : ""}; expand: ${syn.expansionCommand}`);
25
+ }
26
+ lines.push("");
27
+ lines.push("Blackboard Digest");
28
+ lines.push(` topics=${report.blackboardDigest.topicRollups.length}; threads=${report.blackboardDigest.threadSummaries.length}; unresolved=${report.blackboardDigest.unresolvedQuestions.length}; conflicts=${report.blackboardDigest.conflicts.length}; decisions=${report.blackboardDigest.decisions.length}; artifacts=${report.blackboardDigest.artifacts.length}`);
29
+ for (const topic of report.blackboardDigest.topicRollups.slice(0, 20))
30
+ lines.push(` - ${topic.label}; expand: ${topic.expansionCommand}`);
31
+ lines.push("");
32
+ lines.push("Critical Path");
33
+ if (!report.criticalPathGraph.criticalPath.length)
34
+ lines.push(" none");
35
+ for (const id of report.criticalPathGraph.criticalPath.slice(0, 40))
36
+ lines.push(` -> ${id}`);
37
+ lines.push("");
38
+ lines.push("Failures / Blockers");
39
+ if (!report.operatorDigest.failures.length)
40
+ lines.push(" none");
41
+ for (const failure of report.operatorDigest.failures.slice(0, 30))
42
+ lines.push(` [${failure.status}] ${failure.kind} ${failure.id}: ${failure.reason}; next=${failure.nextCommand}`);
43
+ lines.push("");
44
+ lines.push("Evidence Digest");
45
+ lines.push(` adopted=${report.operatorDigest.evidenceDigest.adopted}; missing=${report.operatorDigest.evidenceDigest.missing}; rejected=${report.operatorDigest.evidenceDigest.rejected}`);
46
+ lines.push("");
47
+ lines.push("Trust / Policy Digest");
48
+ lines.push(` events=${report.operatorDigest.trustDigest.events}; policyViolations=${report.operatorDigest.trustDigest.policyViolations}; judgeRationales=${report.operatorDigest.trustDigest.judgeRationales}`);
49
+ for (const violation of report.blackboardDigest.policyViolations.slice(0, 20))
50
+ lines.push(` [policy] ${violation.label}; expand: ${violation.expansionCommand}`);
51
+ lines.push("");
52
+ lines.push("Hidden Source Records");
53
+ if (!report.hiddenSourceRecords.length)
54
+ lines.push(" none (all records shown)");
55
+ for (const hidden of report.hiddenSourceRecords)
56
+ lines.push(` ${hidden.kind}: ${hidden.count} records hidden; expand: ${hidden.expansionCommand}`);
57
+ lines.push("");
58
+ lines.push("Expansion Commands");
59
+ for (const command of report.expansionCommands)
60
+ lines.push(` ${command}`);
61
+ lines.push("");
62
+ lines.push("Next Action");
63
+ lines.push(` ${report.nextAction}`);
64
+ return lines.join("\n");
65
+ }
66
+ function formatCompactGraph(graph) {
67
+ const lines = [];
68
+ lines.push(`Compact Graph (${graph.view}): ${graph.runId}`);
69
+ lines.push(` full=${graph.fullNodeCount} nodes/${graph.fullEdgeCount} edges -> view=${graph.compactNodeCount} nodes/${graph.compactEdgeCount} edges`);
70
+ if (graph.collapsedNodeCount > 0) {
71
+ lines.push(` Graph compacted: ${graph.collapsedNodeCount} nodes collapsed into ${graph.syntheticNodes.length} summary nodes`);
72
+ }
73
+ lines.push("");
74
+ lines.push("Critical Path");
75
+ if (!graph.criticalPath.length)
76
+ lines.push(" none");
77
+ for (const id of graph.criticalPath.slice(0, 40))
78
+ lines.push(` -> ${id}`);
79
+ lines.push("");
80
+ lines.push("Summary Nodes");
81
+ if (!graph.syntheticNodes.length)
82
+ lines.push(" none");
83
+ for (const syn of graph.syntheticNodes) {
84
+ lines.push(` [${syn.dominantStatus}] ${syn.id}: ${syn.collapsedNodeCount} nodes / ${syn.collapsedEdgeCount} edges${syn.blockedReason ? ` blocked=${syn.blockedReason}` : ""}`);
85
+ lines.push(` expand: ${syn.expansionCommand}`);
86
+ }
87
+ lines.push("");
88
+ lines.push("Blockers");
89
+ if (!graph.blockedReasons.length)
90
+ lines.push(" none");
91
+ for (const reason of graph.blockedReasons.slice(0, 20))
92
+ lines.push(` ${reason}`);
93
+ lines.push("");
94
+ lines.push("Nodes");
95
+ for (const node of graph.nodes.slice(0, 80)) {
96
+ lines.push(` [${node.status}] ${node.kind} ${node.id}${node.synthetic ? ` (summary of ${node.synthetic.collapsedNodeCount})` : ""}`);
97
+ }
98
+ if (graph.nodes.length > 80)
99
+ lines.push(` ... ${graph.nodes.length - 80} more`);
100
+ lines.push("");
101
+ lines.push("Next Action");
102
+ lines.push(` ${graph.nextAction}`);
103
+ return lines.join("\n");
104
+ }
105
+ function formatBlackboardDigest(record) {
106
+ const lines = [];
107
+ lines.push(`Blackboard Digest: ${record.runId}${record.blackboardId ? ` (${record.blackboardId})` : ""}`);
108
+ lines.push(` freshness=${record.status}; included=${record.includedCount}; omitted=${record.omittedCount}`);
109
+ const section = (title, entries) => {
110
+ lines.push("");
111
+ lines.push(title);
112
+ if (!entries.length) {
113
+ lines.push(" none");
114
+ return;
115
+ }
116
+ for (const entry of entries.slice(0, 25))
117
+ lines.push(` [${entry.status}] ${entry.label}; expand: ${entry.expansionCommand}`);
118
+ if (entries.length > 25)
119
+ lines.push(` ... ${entries.length - 25} more`);
120
+ };
121
+ section("Topic Rollups", record.topicRollups);
122
+ section("Thread Summaries", record.threadSummaries);
123
+ section("Unresolved Questions", record.unresolvedQuestions);
124
+ section("Conflicts", record.conflicts);
125
+ section("Decisions", record.decisions);
126
+ section("Artifacts", record.artifacts);
127
+ section("Adopted Evidence", record.adoptedEvidence);
128
+ section("Missing Evidence", record.missingEvidence);
129
+ section("Policy Violations", record.policyViolations);
130
+ section("Judge Rationale", record.judgeRationale);
131
+ section("Recent Changes", record.recentChanges);
132
+ section("High-Signal Records", record.highSignal);
133
+ lines.push("");
134
+ lines.push("Next Action");
135
+ lines.push(` ${record.nextAction}`);
136
+ return lines.join("\n");
137
+ }
138
+ function stateExplosionReportLines(report) {
139
+ // Markdown lines for inclusion in the run report.md State Size section.
140
+ const size = report.stateSize;
141
+ const lines = [
142
+ `- Records: ${size.total}; graph nodes: ${size.graphNodes}; graph edges: ${size.graphEdges}; messages: ${size.messages}`,
143
+ `- Compaction: ${size.compactionRecommended ? "recommended" : "not needed"}`,
144
+ `- Summary freshness: ${report.freshness.status}`
145
+ ];
146
+ for (const reason of size.reasons)
147
+ lines.push(` - ${reason}`);
148
+ if (report.compactGraph.collapsedNodeCount > 0) {
149
+ lines.push(`- Graph compacted: ${report.compactGraph.collapsedNodeCount} nodes collapsed into ${report.compactGraph.syntheticNodes.length} summary nodes`);
150
+ lines.push(` - Use: \`node scripts/cw.js multi-agent graph ${report.runId} --view full --json\``);
151
+ }
152
+ if (report.hiddenSourceRecords.length) {
153
+ for (const hidden of report.hiddenSourceRecords) {
154
+ lines.push(`- Hidden ${hidden.kind}: ${hidden.count} records; expand: \`${hidden.expansionCommand}\``);
155
+ }
156
+ }
157
+ lines.push(`- Next: \`${report.nextAction}\``);
158
+ return lines;
159
+ }
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.isProtectedStatus = isProtectedStatus;
7
+ exports.dominantStatus = dominantStatus;
8
+ exports.parentMap = parentMap;
9
+ exports.fingerprintRecords = fingerprintRecords;
10
+ exports.fingerprintStrings = fingerprintStrings;
11
+ exports.stableLine = stableLine;
12
+ exports.sortKeys = sortKeys;
13
+ exports.stripRunId = stripRunId;
14
+ exports.unique = unique;
15
+ exports.byId = byId;
16
+ exports.truncate = truncate;
17
+ exports.slug = slug;
18
+ // Pure, stateless helpers for the state-explosion derived-index layer —
19
+ // status priority, fingerprinting, deterministic key-sorting, id/string
20
+ // utilities. Carved out of state-explosion.ts (FreeBSD-audit god-module carve)
21
+ // so the report/graph/digest builders no longer bundle the primitive helper
22
+ // layer. Nothing here touches run state beyond its arguments; every function is
23
+ // pure (`fingerprintStrings` is re-exported from state-explosion.ts to keep the
24
+ // public surface byte-identical for importers).
25
+ const node_crypto_1 = __importDefault(require("node:crypto"));
26
+ function isProtectedStatus(status) {
27
+ return ["failed", "blocked", "rejected", "conflicting"].includes(status);
28
+ }
29
+ function dominantStatus(statuses) {
30
+ for (const priority of ["failed", "blocked", "rejected", "conflicting", "running", "pending"]) {
31
+ if (statuses.includes(priority))
32
+ return priority;
33
+ }
34
+ return statuses[0] || "completed";
35
+ }
36
+ function parentMap(edges) {
37
+ const parents = new Map();
38
+ for (const edge of edges) {
39
+ if (!parents.has(edge.to))
40
+ parents.set(edge.to, edge.from);
41
+ }
42
+ return parents;
43
+ }
44
+ function fingerprintRecords(records) {
45
+ return fingerprintStrings(records.map((r) => `${r.id}:${r.status || ""}`).sort());
46
+ }
47
+ function fingerprintStrings(values) {
48
+ const hash = node_crypto_1.default.createHash("sha256");
49
+ hash.update(JSON.stringify([...values].sort()));
50
+ return `sha256:${hash.digest("hex").slice(0, 32)}`;
51
+ }
52
+ function stableLine(value) {
53
+ return JSON.stringify(sortKeys(value));
54
+ }
55
+ function sortKeys(value) {
56
+ if (Array.isArray(value))
57
+ return value.map(sortKeys);
58
+ if (value && typeof value === "object") {
59
+ const record = value;
60
+ const result = {};
61
+ for (const key of Object.keys(record).sort())
62
+ result[key] = sortKeys(record[key]);
63
+ return result;
64
+ }
65
+ return value;
66
+ }
67
+ function stripRunId(run, id) {
68
+ return id.startsWith(`${run.id}:`) ? id.slice(run.id.length + 1) : id;
69
+ }
70
+ function unique(values) {
71
+ return Array.from(new Set(values.filter(Boolean))).sort();
72
+ }
73
+ function byId(a, b) {
74
+ return a.id.localeCompare(b.id);
75
+ }
76
+ function truncate(value) {
77
+ const single = value.replace(/\s+/g, " ").trim();
78
+ return single.length > 80 ? `${single.slice(0, 77)}...` : single;
79
+ }
80
+ function slug(value) {
81
+ return value.replace(/[^a-zA-Z0-9._:-]/g, "-");
82
+ }