cool-workflow 0.1.80 → 0.1.81
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.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +42 -2
- package/apps/architecture-review/app.json +1 -1
- package/apps/architecture-review-fast/app.json +1 -1
- package/apps/end-to-end-golden-path/app.json +1 -1
- package/apps/pr-review-fix-ci/app.json +1 -1
- package/apps/release-cut/app.json +1 -1
- package/apps/research-synthesis/app.json +1 -1
- package/dist/agent-config.js +21 -7
- package/dist/candidate-scoring.js +42 -22
- package/dist/capability-core.js +94 -17
- package/dist/capability-registry.js +138 -171
- package/dist/cli.js +90 -100
- package/dist/collaboration.js +5 -6
- package/dist/commit.js +20 -6
- package/dist/compare.js +18 -0
- package/dist/coordinator/classify.js +45 -0
- package/dist/coordinator/paths.js +42 -0
- package/dist/coordinator/util.js +129 -0
- package/dist/coordinator.js +127 -300
- package/dist/dispatch.js +35 -0
- package/dist/drive.js +7 -7
- package/dist/error-feedback.js +8 -4
- package/dist/evidence-reasoning.js +1 -1
- package/dist/execution-backend/agent.js +331 -0
- package/dist/execution-backend/probes.js +96 -0
- package/dist/execution-backend/util.js +47 -0
- package/dist/execution-backend.js +67 -420
- package/dist/mcp-server.js +34 -173
- package/dist/multi-agent/graph.js +84 -0
- package/dist/multi-agent/helpers.js +145 -0
- package/dist/multi-agent/paths.js +22 -0
- package/dist/multi-agent-eval/format.js +194 -0
- package/dist/multi-agent-eval/normalize.js +51 -0
- package/dist/multi-agent-eval.js +39 -244
- package/dist/multi-agent-host.js +0 -19
- package/dist/multi-agent.js +125 -314
- package/dist/node-snapshot.js +3 -3
- package/dist/observability/format.js +61 -0
- package/dist/observability/intake.js +98 -0
- package/dist/observability.js +14 -160
- package/dist/operator-ux/format.js +364 -0
- package/dist/operator-ux.js +22 -363
- package/dist/orchestrator/report.js +8 -0
- package/dist/orchestrator.js +25 -8
- package/dist/reclamation.js +26 -21
- package/dist/run-export.js +138 -14
- package/dist/run-registry/derive.js +172 -0
- package/dist/run-registry/format.js +124 -0
- package/dist/run-registry/gc.js +251 -0
- package/dist/run-registry/policy.js +16 -0
- package/dist/run-registry/queue.js +116 -0
- package/dist/run-registry.js +78 -593
- package/dist/run-state-schema.js +1 -0
- package/dist/sandbox-profile.js +43 -2
- package/dist/state-explosion/format.js +159 -0
- package/dist/state-explosion/helpers.js +82 -0
- package/dist/state-explosion.js +65 -283
- package/dist/state-node.js +19 -4
- package/dist/telemetry-attestation.js +55 -0
- package/dist/telemetry-demo.js +15 -3
- package/dist/telemetry-ledger.js +60 -15
- package/dist/topology.js +25 -8
- package/dist/triggers.js +33 -14
- package/dist/trust-audit.js +145 -33
- package/dist/version.js +1 -1
- package/dist/worker-isolation/helpers.js +51 -0
- package/dist/worker-isolation/paths.js +46 -0
- package/dist/worker-isolation.js +39 -115
- package/docs/agent-delegation-drive.7.md +13 -0
- package/docs/cli-mcp-parity.7.md +4 -0
- package/docs/contract-migration-tooling.7.md +2 -0
- package/docs/control-plane-scheduling.7.md +2 -0
- package/docs/dogfood/resume-drive-real-agent-2026-06-14.md +40 -0
- package/docs/durable-state-and-locking.7.md +4 -0
- package/docs/evidence-adoption-reasoning-chain.7.md +2 -0
- package/docs/execution-backends.7.md +2 -0
- package/docs/index.md +1 -0
- package/docs/launch/launch-kit.md +46 -23
- package/docs/launch/pre-launch-checklist.md +14 -14
- package/docs/multi-agent-cli-mcp-surface.7.md +4 -0
- package/docs/multi-agent-eval-replay-harness.7.md +2 -0
- package/docs/multi-agent-operator-ux.7.md +2 -0
- package/docs/multi-agent-trust-policy-audit.7.md +27 -0
- package/docs/node-snapshot-diff-replay.7.md +2 -0
- package/docs/observability-cost-accounting.7.md +2 -0
- package/docs/project-index.md +18 -5
- package/docs/real-execution-backends.7.md +2 -0
- package/docs/release-and-migration.7.md +4 -0
- package/docs/release-tooling.7.md +2 -0
- package/docs/run-registry-control-plane.7.md +54 -8
- package/docs/run-retention-reclamation.7.md +4 -0
- package/docs/state-explosion-management.7.md +2 -0
- package/docs/team-collaboration.7.md +2 -0
- package/docs/trust-model.md +267 -0
- package/docs/vendor-manifest-loadability.7.md +43 -0
- package/docs/web-desktop-workbench.7.md +2 -0
- package/manifest/plugin.manifest.json +1 -1
- package/package.json +4 -2
- package/scripts/agents/builtin-templates.json +7 -0
- package/scripts/bump-version.js +5 -11
- package/scripts/canonical-apps-list.js +64 -0
- package/scripts/canonical-apps.js +19 -4
- package/scripts/dogfood-release.js +1 -1
- package/scripts/golden-path.js +4 -4
- package/scripts/parity-check.js +5 -0
- package/scripts/release-check.js +5 -1
- package/scripts/version-sync-check.js +5 -8
- package/dist/capability-dispatcher.js +0 -86
package/dist/run-state-schema.js
CHANGED
package/dist/sandbox-profile.js
CHANGED
|
@@ -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
|
-
|
|
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()) {
|
|
@@ -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
|
+
}
|