cool-workflow 0.2.2 → 0.2.3
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/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/cli/dispatch.js +11 -5
- package/dist/cli/entry.js +41 -1
- package/dist/cli/io.js +6 -2
- package/dist/cli/parseargv.js +1 -0
- package/dist/core/capability-data.js +1 -0
- package/dist/core/format/completion.js +68 -0
- package/dist/core/format/help.js +25 -6
- package/dist/core/format/safe-json.js +73 -0
- package/dist/core/format/state-explosion-text.js +1 -1
- package/dist/core/multi-agent/candidate-scoring.js +5 -1
- package/dist/core/multi-agent/collaboration.js +3 -3
- package/dist/core/multi-agent/coordinator.js +5 -5
- package/dist/core/multi-agent/runtime.js +4 -4
- package/dist/core/multi-agent/topology.js +3 -3
- package/dist/core/pipeline/dispatch.js +18 -4
- package/dist/core/pipeline/drive-decide.js +2 -1
- package/dist/core/state/migrations.js +16 -1
- package/dist/core/state/state-explosion/digest.js +13 -13
- package/dist/core/state/state-explosion/graph.js +17 -11
- package/dist/core/state/state-explosion/report.js +6 -6
- package/dist/core/util/numeric-flag.js +40 -0
- package/dist/core/version.js +1 -1
- package/dist/mcp/server.js +99 -11
- package/dist/shell/audit-cli.js +57 -25
- package/dist/shell/coordinator-io.js +73 -13
- package/dist/shell/dispatch.js +1 -1
- package/dist/shell/doctor.js +80 -1
- package/dist/shell/drive.js +243 -49
- package/dist/shell/eval-text.js +2 -2
- package/dist/shell/evidence-reasoning.js +4 -4
- package/dist/shell/execution-backend/agent.js +20 -1
- package/dist/shell/execution-backend/container.js +4 -1
- package/dist/shell/execution-backend/local.js +4 -1
- package/dist/shell/feedback-cli.js +6 -6
- package/dist/shell/fs-atomic.js +218 -29
- package/dist/shell/man-cli.js +6 -0
- package/dist/shell/metrics-cli.js +2 -1
- package/dist/shell/multi-agent-cli.js +367 -323
- package/dist/shell/multi-agent-host.js +9 -9
- package/dist/shell/multi-agent-operator-ux.js +80 -38
- package/dist/shell/node-store.js +10 -4
- package/dist/shell/observability.js +1 -1
- package/dist/shell/operator-ux-text.js +22 -22
- package/dist/shell/operator-ux.js +15 -14
- package/dist/shell/orchestrator.js +49 -38
- package/dist/shell/pipeline-cli.js +87 -40
- package/dist/shell/reclamation-io.js +14 -4
- package/dist/shell/registry-cli.js +19 -17
- package/dist/shell/remote-source.js +13 -8
- package/dist/shell/report-cli.js +45 -0
- package/dist/shell/report.js +2 -1
- package/dist/shell/run-registry-io.js +77 -19
- package/dist/shell/run-store.js +74 -2
- package/dist/shell/scheduling-io.js +12 -13
- package/dist/shell/state-cli.js +2 -7
- package/dist/shell/state-explosion-cli.js +17 -9
- package/dist/shell/topology-io.js +36 -5
- package/dist/shell/trust-audit.js +224 -22
- package/dist/shell/trust-policy-io.js +1 -1
- package/dist/shell/worker-cli.js +35 -31
- package/dist/wiring/capability-table/basics.js +33 -8
- package/dist/wiring/capability-table/exec-backend.js +20 -11
- package/dist/wiring/capability-table/multi-agent.js +172 -165
- package/dist/wiring/capability-table/parity.js +1 -0
- package/dist/wiring/capability-table/pipeline.js +54 -42
- package/dist/wiring/capability-table/registry-core.js +29 -10
- package/dist/wiring/capability-table/reporting.js +137 -96
- package/dist/wiring/capability-table/scheduling-registry.js +195 -161
- package/dist/wiring/capability-table/state.js +36 -28
- package/dist/wiring/capability-table/trust-ledger.js +46 -22
- package/dist/wiring/capability-table/workflow-apps.js +83 -68
- package/docs/agent-delegation-drive.7.md +2 -0
- package/docs/candidate-scoring.7.md +1 -1
- package/docs/canonical-workflow-apps.7.md +7 -7
- package/docs/cli-mcp-parity.7.md +8 -3
- package/docs/contract-migration-tooling.7.md +2 -0
- package/docs/control-plane-scheduling.7.md +2 -0
- package/docs/coordinator-blackboard.7.md +17 -17
- package/docs/dogfood-one-real-repo.7.md +11 -11
- package/docs/durable-state-and-locking.7.md +2 -0
- package/docs/end-to-end-golden-path.7.md +14 -14
- package/docs/evidence-adoption-reasoning-chain.7.md +2 -0
- package/docs/execution-backends.7.md +2 -0
- package/docs/getting-started.md +37 -37
- package/docs/multi-agent-cli-mcp-surface.7.md +19 -17
- package/docs/multi-agent-eval-replay-harness.7.md +15 -13
- package/docs/multi-agent-operator-ux.7.md +21 -19
- package/docs/multi-agent-runtime-core.7.md +22 -22
- package/docs/multi-agent-topologies.7.md +6 -6
- package/docs/multi-agent-trust-policy-audit.7.md +11 -11
- package/docs/node-snapshot-diff-replay.7.md +2 -0
- package/docs/observability-cost-accounting.7.md +2 -0
- package/docs/operator-ux.7.md +34 -34
- package/docs/pipeline-runner.7.md +4 -4
- package/docs/project-index.md +27 -4
- package/docs/real-execution-backends.7.md +2 -0
- package/docs/release-and-migration.7.md +3 -1
- package/docs/release-tooling.7.md +2 -0
- package/docs/routines.md +4 -4
- package/docs/run-registry-control-plane.7.md +21 -19
- package/docs/run-retention-reclamation.7.md +2 -0
- package/docs/scheduled-tasks.md +14 -14
- package/docs/security-trust-hardening.7.md +15 -15
- package/docs/state-explosion-management.7.md +12 -10
- package/docs/team-collaboration.7.md +2 -0
- package/docs/trust-audit-anchor.7.md +2 -0
- package/docs/unix-principles.md +3 -1
- package/docs/verifier-gated-commit.7.md +1 -1
- package/docs/web-desktop-workbench.7.md +2 -0
- package/docs/workflow-app-framework.7.md +13 -13
- package/manifest/plugin.manifest.json +1 -1
- package/package.json +2 -2
- package/scripts/agents/claude-p-agent.js +2 -2
- package/scripts/block-unapproved-tag.sh +23 -2
- package/scripts/canonical-apps.js +4 -4
- package/scripts/children/batch-delegate-child.js +52 -2
- package/scripts/dogfood-release.js +1 -1
- package/scripts/fake-date-for-reproduction.js +44 -0
- package/scripts/golden-path.js +4 -4
- package/scripts/purity-baseline.json +3 -0
- package/scripts/release-flow.js +57 -1
- package/scripts/verdict-keygen.js +83 -0
- package/scripts/verify-bump-reproduction.sh +148 -0
- package/scripts/verify-verdict-signature.js +61 -0
- package/skills/cool-workflow/SKILL.md +9 -9
- package/skills/cool-workflow/references/commands.md +89 -88
- package/ui/workbench/app.css +37 -1
- package/ui/workbench/app.js +124 -6
|
@@ -124,21 +124,40 @@ function requireContext(run, id) {
|
|
|
124
124
|
}
|
|
125
125
|
function requireArtifactRefs(run, ids) {
|
|
126
126
|
const state = ensureBlackboardState(run);
|
|
127
|
+
// state.artifacts grows monotonically with run activity and this runs on
|
|
128
|
+
// essentially every blackboard write -- a Set built once here replaces
|
|
129
|
+
// re-scanning the whole (growing) artifacts array per referenced id
|
|
130
|
+
// (the same array-scan-per-item shape 024b007 fixed for phase/task
|
|
131
|
+
// selection).
|
|
132
|
+
const artifactIds = new Set(state.artifacts.map((artifact) => artifact.id));
|
|
127
133
|
for (const id of ids)
|
|
128
|
-
if (!
|
|
134
|
+
if (!artifactIds.has(id))
|
|
129
135
|
throw new Error(`Unknown BlackboardArtifactRef id: ${id}`);
|
|
130
136
|
return cb.unique(ids);
|
|
131
137
|
}
|
|
132
138
|
function requireMessages(run, ids) {
|
|
133
139
|
const state = ensureBlackboardState(run);
|
|
140
|
+
const messageIds = new Set(state.messages.map((message) => message.id));
|
|
134
141
|
for (const id of ids)
|
|
135
|
-
if (!
|
|
142
|
+
if (!messageIds.has(id))
|
|
136
143
|
throw new Error(`Unknown BlackboardMessage id: ${id}`);
|
|
137
144
|
return cb.unique(ids);
|
|
138
145
|
}
|
|
139
146
|
function checksumFile(file) {
|
|
140
147
|
return `sha256:${crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex")}`;
|
|
141
148
|
}
|
|
149
|
+
const blackboardDirtySets = new WeakMap();
|
|
150
|
+
function dirtySetsFor(state) {
|
|
151
|
+
let sets = blackboardDirtySets.get(state);
|
|
152
|
+
if (!sets) {
|
|
153
|
+
sets = { topics: new Set(), contexts: new Set(), artifacts: new Set(), snapshots: new Set(), decisions: new Set() };
|
|
154
|
+
blackboardDirtySets.set(state, sets);
|
|
155
|
+
}
|
|
156
|
+
return sets;
|
|
157
|
+
}
|
|
158
|
+
function markBlackboardDirty(state, kind, id) {
|
|
159
|
+
dirtySetsFor(state)[kind].add(id);
|
|
160
|
+
}
|
|
142
161
|
function linkMultiAgent(run, blackboardId, topicIds, input) {
|
|
143
162
|
const groupId = input.agentGroupId ?? input.groupId;
|
|
144
163
|
const roleId = input.agentRoleId ?? input.roleId;
|
|
@@ -200,16 +219,37 @@ function persistBlackboardState(run) {
|
|
|
200
219
|
messages: state.messages.map((message) => ({ id: message.id, blackboardId: message.blackboardId, topicId: message.topicId, createdAt: message.createdAt, status: message.status, author: message.author, evidenceRefs: message.linkedEvidenceRefs, artifactRefIds: message.linkedArtifactRefIds })),
|
|
201
220
|
});
|
|
202
221
|
fs.writeFileSync(messagesPath(run), state.messages.sort(cb.compareRecords).map((message) => JSON.stringify(message)).join("\n") + (state.messages.length ? "\n" : ""), "utf8");
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
for (const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
222
|
+
const dirty = dirtySetsFor(state);
|
|
223
|
+
for (const id of dirty.topics) {
|
|
224
|
+
const record = state.topics.find((entry) => entry.id === id);
|
|
225
|
+
if (record)
|
|
226
|
+
(0, fs_atomic_1.writeJson)(recordPath(run, "topics", id), record);
|
|
227
|
+
}
|
|
228
|
+
for (const id of dirty.contexts) {
|
|
229
|
+
const record = state.contexts.find((entry) => entry.id === id);
|
|
230
|
+
if (record)
|
|
231
|
+
(0, fs_atomic_1.writeJson)(recordPath(run, "contexts", id), record);
|
|
232
|
+
}
|
|
233
|
+
for (const id of dirty.artifacts) {
|
|
234
|
+
const record = state.artifacts.find((entry) => entry.id === id);
|
|
235
|
+
if (record)
|
|
236
|
+
(0, fs_atomic_1.writeJson)(recordPath(run, "artifacts", id), record);
|
|
237
|
+
}
|
|
238
|
+
for (const id of dirty.snapshots) {
|
|
239
|
+
const record = state.snapshots.find((entry) => entry.id === id);
|
|
240
|
+
if (record)
|
|
241
|
+
(0, fs_atomic_1.writeJson)(recordPath(run, "snapshots", id), record);
|
|
242
|
+
}
|
|
243
|
+
for (const id of dirty.decisions) {
|
|
244
|
+
const record = state.decisions.find((entry) => entry.id === id);
|
|
245
|
+
if (record)
|
|
246
|
+
(0, fs_atomic_1.writeJson)(recordPath(run, "decisions", id), record);
|
|
247
|
+
}
|
|
248
|
+
dirty.topics.clear();
|
|
249
|
+
dirty.contexts.clear();
|
|
250
|
+
dirty.artifacts.clear();
|
|
251
|
+
dirty.snapshots.clear();
|
|
252
|
+
dirty.decisions.clear();
|
|
213
253
|
}
|
|
214
254
|
function resolveBlackboard(run, input = {}) {
|
|
215
255
|
const state = ensureBlackboardState(run);
|
|
@@ -238,6 +278,7 @@ function createBlackboardTopic(run, input) {
|
|
|
238
278
|
cb.assertUnique(state.topics, id, "BlackboardTopic");
|
|
239
279
|
const topic = cb.buildTopic(run.id, board, input, id, now());
|
|
240
280
|
state.topics.push(topic);
|
|
281
|
+
markBlackboardDirty(state, "topics", topic.id);
|
|
241
282
|
board.topicIds = cb.unique([...board.topicIds, topic.id]);
|
|
242
283
|
cb.touch(board, now());
|
|
243
284
|
linkMultiAgent(run, board.id, [topic.id], board.links);
|
|
@@ -270,6 +311,7 @@ function postBlackboardMessage(run, input) {
|
|
|
270
311
|
topic.messageIds = cb.unique([...topic.messageIds, message.id]);
|
|
271
312
|
board.messageCount = state.messages.filter((entry) => entry.blackboardId === board.id).length;
|
|
272
313
|
cb.touch(topic, now());
|
|
314
|
+
markBlackboardDirty(state, "topics", topic.id);
|
|
273
315
|
cb.touch(board, now());
|
|
274
316
|
appendBlackboardNode(run, "blackboard-message", message.id, "completed", cb.truncate(message.body), messagesPath(run), [`${run.id}:blackboard:topic:${topic.id}`]);
|
|
275
317
|
const audit = (0, trust_audit_1.recordTrustAuditEvent)(run, {
|
|
@@ -353,17 +395,21 @@ function putBlackboardContext(run, input) {
|
|
|
353
395
|
superseded.status = "superseded";
|
|
354
396
|
superseded.supersededByContextId = id;
|
|
355
397
|
cb.touch(superseded, now());
|
|
398
|
+
markBlackboardDirty(state, "contexts", superseded.id);
|
|
356
399
|
}
|
|
357
400
|
const { context, conflicts } = cb.buildContext(run.id, board, topic, input, id, now(), state.contexts);
|
|
358
401
|
for (const conflict of conflicts) {
|
|
359
402
|
conflict.status = "conflicting";
|
|
360
403
|
conflict.conflictingContextIds = cb.unique([...conflict.conflictingContextIds, context.id]);
|
|
361
404
|
cb.touch(conflict, now());
|
|
405
|
+
markBlackboardDirty(state, "contexts", conflict.id);
|
|
362
406
|
}
|
|
363
407
|
state.contexts.push(context);
|
|
408
|
+
markBlackboardDirty(state, "contexts", context.id);
|
|
364
409
|
topic.contextIds = cb.unique([...topic.contextIds, context.id]);
|
|
365
410
|
board.contextIds = cb.unique([...board.contextIds, context.id]);
|
|
366
411
|
cb.touch(topic, now());
|
|
412
|
+
markBlackboardDirty(state, "topics", topic.id);
|
|
367
413
|
cb.touch(board, now());
|
|
368
414
|
const decision = recordCoordinatorDecision(run, {
|
|
369
415
|
blackboardId: board.id,
|
|
@@ -385,6 +431,11 @@ function putBlackboardContext(run, input) {
|
|
|
385
431
|
const writeAudit = (0, trust_policy_io_1.recordBlackboardWriteAudit)(run, { operation: "context", status: context.status, actor: context.author, multiAgentRunId: context.links.multiAgentRunId, agentGroupId: context.links.agentGroupId, agentRoleId: context.links.agentRoleId, agentMembershipId: context.links.agentMembershipId, blackboardId: board.id, blackboardTopicId: topic.id, blackboardContextId: context.id, coordinatorDecisionId: decision.id, evidenceRefs: context.evidenceRefs, parentEventIds: cb.unique([...(permission ? [permission.event.id] : []), audit.id]), policyRef: permission?.policyRef, metadata: { kind: context.kind, key: context.key, conflicts: context.conflictingContextIds } });
|
|
386
432
|
context.links.auditEventIds = cb.unique([...(context.links.auditEventIds || []), audit.id]);
|
|
387
433
|
context.links.auditEventIds = cb.unique([...(context.links.auditEventIds || []), writeAudit.id]);
|
|
434
|
+
// context.decisionId (above) and both auditEventIds assignments happen AFTER
|
|
435
|
+
// recordCoordinatorDecision's own nested persistBlackboardState call already
|
|
436
|
+
// flushed and cleared the dirty set, so context must be re-marked here or
|
|
437
|
+
// this call's final persist would skip rewriting it.
|
|
438
|
+
markBlackboardDirty(state, "contexts", context.id);
|
|
388
439
|
persistBlackboardState(run);
|
|
389
440
|
return context;
|
|
390
441
|
}
|
|
@@ -405,17 +456,24 @@ function addBlackboardArtifact(run, input) {
|
|
|
405
456
|
const checksum = absolutePath && fs.existsSync(absolutePath) && fs.statSync(absolutePath).isFile() ? checksumFile(absolutePath) : undefined;
|
|
406
457
|
const artifact = cb.buildArtifact(run.id, board, topic, input, id, now(), absolutePath, checksum);
|
|
407
458
|
state.artifacts.push(artifact);
|
|
459
|
+
markBlackboardDirty(state, "artifacts", artifact.id);
|
|
408
460
|
board.artifactRefIds = cb.unique([...board.artifactRefIds, artifact.id]);
|
|
409
461
|
if (topic)
|
|
410
462
|
topic.artifactRefIds = cb.unique([...topic.artifactRefIds, artifact.id]);
|
|
411
463
|
cb.touch(board, now());
|
|
412
|
-
if (topic)
|
|
464
|
+
if (topic) {
|
|
413
465
|
cb.touch(topic, now());
|
|
466
|
+
markBlackboardDirty(state, "topics", topic.id);
|
|
467
|
+
}
|
|
414
468
|
const decision = recordCoordinatorDecision(run, { blackboardId: board.id, topicId: topic?.id, kind: "artifact-index", outcome: "accepted", reason: `Indexed ${artifact.kind} artifact ${artifact.id}`, subjectIds: [artifact.id], evidenceRefs: artifact.evidenceRefs, artifactRefIds: [artifact.id], author: { kind: "coordinator", id: "cw" }, scope: artifact.scope, tags: ["artifact", artifact.kind] });
|
|
415
469
|
appendBlackboardNode(run, "blackboard-artifact", artifact.id, "completed", artifact.kind, recordPath(run, "artifacts", artifact.id), [topic ? `${run.id}:blackboard:topic:${topic.id}` : `${run.id}:blackboard:${board.id}`]);
|
|
416
470
|
const audit = (0, trust_audit_1.recordTrustAuditEvent)(run, { kind: "blackboard.artifact", decision: "accepted", source: cb.sourceForAuthor(artifact.author), actor: artifact.author.id, blackboardId: board.id, blackboardTopicId: topic?.id, blackboardArtifactRefId: artifact.id, coordinatorDecisionId: decision.id, workerId: artifact.provenance.workerId, taskId: artifact.provenance.taskId, candidateId: artifact.provenance.candidateId, commitId: artifact.provenance.commitId, normalizedPath: absolutePath, evidenceRefs: artifact.evidenceRefs, parentEventIds: artifact.trustAuditEventIds, metadata: { kind: artifact.kind, locator: artifact.locator, checksum: artifact.checksum } });
|
|
417
471
|
const writeAudit = (0, trust_policy_io_1.recordBlackboardWriteAudit)(run, { operation: "artifact", status: artifact.status, actor: artifact.author, multiAgentRunId: artifact.provenance.multiAgentRunId, agentGroupId: artifact.provenance.agentGroupId, agentRoleId: artifact.provenance.agentRoleId, agentMembershipId: artifact.provenance.agentMembershipId, blackboardId: board.id, blackboardTopicId: topic?.id, blackboardArtifactRefId: artifact.id, coordinatorDecisionId: decision.id, evidenceRefs: artifact.evidenceRefs, parentEventIds: cb.unique([...(permission ? [permission.event.id] : []), audit.id]), policyRef: permission?.policyRef, metadata: { kind: artifact.kind, locator: artifact.locator, checksum: artifact.checksum } });
|
|
418
472
|
artifact.trustAuditEventIds = cb.unique([...artifact.trustAuditEventIds, audit.id, writeAudit.id]);
|
|
473
|
+
// Happens AFTER recordCoordinatorDecision's own nested persist already
|
|
474
|
+
// flushed and cleared the dirty set — re-mark or this call's final persist
|
|
475
|
+
// would skip rewriting the artifact.
|
|
476
|
+
markBlackboardDirty(state, "artifacts", artifact.id);
|
|
419
477
|
persistBlackboardState(run);
|
|
420
478
|
return artifact;
|
|
421
479
|
}
|
|
@@ -428,6 +486,7 @@ function createBlackboardSnapshot(run, blackboardId) {
|
|
|
428
486
|
const messageIds = state.messages.filter((entry) => entry.blackboardId === board.id).map((entry) => entry.id);
|
|
429
487
|
const snapshot = cb.buildSnapshot(run.id, board, id, now(), snapshotPath, board.paths.index, summary, messageIds);
|
|
430
488
|
state.snapshots.push(snapshot);
|
|
489
|
+
markBlackboardDirty(state, "snapshots", snapshot.id);
|
|
431
490
|
board.snapshotIds = cb.unique([...board.snapshotIds, snapshot.id]);
|
|
432
491
|
cb.touch(board, now());
|
|
433
492
|
appendBlackboardNode(run, "blackboard-snapshot", snapshot.id, "completed", snapshot.id, snapshotPath, [`${run.id}:blackboard:${board.id}`]);
|
|
@@ -447,6 +506,7 @@ function recordCoordinatorDecision(run, input) {
|
|
|
447
506
|
requireMessages(run, input.messageIds || []);
|
|
448
507
|
const decision = cb.buildDecision(run.id, board, input, id, now());
|
|
449
508
|
state.decisions.push(decision);
|
|
509
|
+
markBlackboardDirty(state, "decisions", decision.id);
|
|
450
510
|
board.decisionIds = cb.unique([...board.decisionIds, decision.id]);
|
|
451
511
|
cb.touch(board, now());
|
|
452
512
|
appendBlackboardNode(run, "coordinator-decision", decision.id, cb.coordinatorStatusToNodeStatus(decision.status), `${decision.kind}:${decision.outcome}`, recordPath(run, "decisions", decision.id), [`${run.id}:blackboard:${board.id}`, ...(input.topicId ? [`${run.id}:blackboard:topic:${input.topicId}`] : [])]);
|
package/dist/shell/dispatch.js
CHANGED
|
@@ -145,7 +145,7 @@ function createDispatchManifest(run, limit, options = {}) {
|
|
|
145
145
|
dispatchId,
|
|
146
146
|
createdAt: now,
|
|
147
147
|
phase: tasks[0].phase,
|
|
148
|
-
instructions: "Spawn one worker per task when the user explicitly authorized agent/parallel/background work. Save each final summary as Markdown and record it with `cw
|
|
148
|
+
instructions: "Spawn one worker per task when the user explicitly authorized agent/parallel/background work. Save each final summary as Markdown and record it with `cw result <run-id> <task-id> <file>`.",
|
|
149
149
|
tasks: selectedRunTasks.map(dispatch_1.formatDispatchTask),
|
|
150
150
|
manifestPath,
|
|
151
151
|
stateNodeId: dispatchNode.id,
|
package/dist/shell/doctor.js
CHANGED
|
@@ -64,6 +64,8 @@ const path = __importStar(require("node:path"));
|
|
|
64
64
|
const node_child_process_1 = require("node:child_process");
|
|
65
65
|
const agent_config_1 = require("./agent-config");
|
|
66
66
|
const onramp_1 = require("./onramp");
|
|
67
|
+
const run_store_1 = require("./run-store");
|
|
68
|
+
const trust_audit_1 = require("./trust-audit");
|
|
67
69
|
const term_1 = require("./term");
|
|
68
70
|
function whichBinary(bin, env) {
|
|
69
71
|
if (bin.includes("/") || bin.includes("\\")) {
|
|
@@ -210,6 +212,75 @@ function runDoctor(args = {}, env = process.env, cwd = process.cwd()) {
|
|
|
210
212
|
detail: `Cannot write run state under ${cwState}.`,
|
|
211
213
|
fix: "Run from a writable working directory, or pass --cwd PATH.",
|
|
212
214
|
});
|
|
215
|
+
// 7. Run-state integrity — every run under <cwd>/.cw/runs must have a
|
|
216
|
+
// LOADABLE state.json: not corrupt, not an unsupported schema, not
|
|
217
|
+
// suspected data loss (a wiped state.json next to real task/commit/
|
|
218
|
+
// audit content). READ-ONLY: only dry-run loads each run's state,
|
|
219
|
+
// same as `cw status`/`cw run show` would — never writes or repairs.
|
|
220
|
+
const runsDir = path.join(path.resolve(cwd), ".cw", "runs");
|
|
221
|
+
let runDirs = [];
|
|
222
|
+
try {
|
|
223
|
+
runDirs = fs.readdirSync(runsDir).filter((name) => !name.startsWith("."));
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
runDirs = [];
|
|
227
|
+
}
|
|
228
|
+
const runStateProblems = [];
|
|
229
|
+
let runsScanned = 0;
|
|
230
|
+
const loadedRuns = [];
|
|
231
|
+
for (const runId of runDirs) {
|
|
232
|
+
const statePath = path.join(runsDir, runId, "state.json");
|
|
233
|
+
if (!fs.existsSync(statePath))
|
|
234
|
+
continue;
|
|
235
|
+
runsScanned += 1;
|
|
236
|
+
try {
|
|
237
|
+
const result = (0, run_store_1.loadRunStateFile)(statePath, { dryRun: true });
|
|
238
|
+
if (result.report.status === "unsupported") {
|
|
239
|
+
runStateProblems.push(`${runId}: ${result.report.errors.join("; ") || "unsupported run state"}`);
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
(0, run_store_1.assertNotSuspectedDataLoss)(runId, result);
|
|
243
|
+
loadedRuns.push({ runId, run: result.run });
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
runStateProblems.push(`${runId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
checks.push(runStateProblems.length === 0
|
|
250
|
+
? {
|
|
251
|
+
name: "run-state-integrity",
|
|
252
|
+
status: "ok",
|
|
253
|
+
detail: runsScanned > 0 ? `${runsScanned} run(s) under .cw/runs all have a loadable state.json.` : "No runs under .cw/runs yet.",
|
|
254
|
+
}
|
|
255
|
+
: {
|
|
256
|
+
name: "run-state-integrity",
|
|
257
|
+
status: "warn",
|
|
258
|
+
detail: `${runStateProblems.length} of ${runsScanned} run(s) have a state.json problem: ${runStateProblems.slice(0, 3).join(" | ")}${runStateProblems.length > 3 ? ` (+${runStateProblems.length - 3} more)` : ""}`,
|
|
259
|
+
fix: "Inspect with `cw status <run-id>` for the specific reason; restore state.json from a backup, or remove the run directory to start over.",
|
|
260
|
+
});
|
|
261
|
+
// 8. Trust-audit integrity — a torn trailing write from a crash mid-append
|
|
262
|
+
// can make a run's audit chain fail verification forever with no
|
|
263
|
+
// repair path. Only checked for runs that passed check 7 (a run whose
|
|
264
|
+
// state.json itself won't load has no `run` object to check audit for).
|
|
265
|
+
const auditProblems = [];
|
|
266
|
+
for (const { runId, run } of loadedRuns) {
|
|
267
|
+
const integrity = (0, trust_audit_1.verifyTrustAudit)(run);
|
|
268
|
+
if (integrity.present && !integrity.verified) {
|
|
269
|
+
auditProblems.push(`${runId}: ${integrity.checks.filter((c) => !c.pass).map((c) => c.code).join(", ")}`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
checks.push(auditProblems.length === 0
|
|
273
|
+
? {
|
|
274
|
+
name: "audit-integrity",
|
|
275
|
+
status: "ok",
|
|
276
|
+
detail: loadedRuns.length > 0 ? `Trust-audit log for all ${loadedRuns.length} loadable run(s) verifies clean.` : "No runs to check.",
|
|
277
|
+
}
|
|
278
|
+
: {
|
|
279
|
+
name: "audit-integrity",
|
|
280
|
+
status: "warn",
|
|
281
|
+
detail: `${auditProblems.length} run(s) fail trust-audit verification: ${auditProblems.slice(0, 3).join(" | ")}${auditProblems.length > 3 ? ` (+${auditProblems.length - 3} more)` : ""}`,
|
|
282
|
+
fix: "Run `cw audit repair <run-id>` to check for a repairable torn trailing write (a crash mid-append); anything else needs manual investigation, not an auto-fix.",
|
|
283
|
+
});
|
|
213
284
|
const fails = checks.filter((c) => c.status === "fail").length;
|
|
214
285
|
const warns = checks.filter((c) => c.status === "warn").length;
|
|
215
286
|
const ok = fails === 0;
|
|
@@ -248,7 +319,15 @@ function formatDoctorReport(report) {
|
|
|
248
319
|
lines.push("");
|
|
249
320
|
const summaryGlyph = report.ok ? (0, term_1.green)("✓") : (0, term_1.red)("✗");
|
|
250
321
|
lines.push(`${summaryGlyph} ${report.summary}`);
|
|
251
|
-
if (report.onramp) {
|
|
322
|
+
if (!report.onramp) {
|
|
323
|
+
// A first-time (or just not-yet-discovered) `cw doctor` never mentions
|
|
324
|
+
// the richer onramp content below without this pointer — the "Quick
|
|
325
|
+
// start (3 steps)"/"Recommended Checks"/per-scenario sections only ever
|
|
326
|
+
// render behind the `--onramp` opt-in flag, so a user who never reads
|
|
327
|
+
// `cw help doctor` has no way to find them otherwise.
|
|
328
|
+
lines.push(` ${(0, term_1.nextHint)("cw doctor --onramp")} for the 3-step quick start`);
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
252
331
|
lines.push("");
|
|
253
332
|
lines.push("Quick start (3 steps):");
|
|
254
333
|
lines.push(" 1. cw demo tamper — prove trust checks work (30s)");
|