cool-workflow 0.1.84 → 0.1.86
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 +47 -331
- package/apps/architecture-review/app.json +1 -1
- package/apps/architecture-review-fast/app.json +1 -1
- package/apps/architecture-review-fast/workflow.js +2 -0
- 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/capability-core.js +220 -5
- package/dist/capability-registry.js +90 -51
- package/dist/cli/command-surface.js +47 -2
- package/dist/doctor.js +37 -1
- package/dist/mcp/tool-call.js +428 -0
- package/dist/mcp/tool-definitions.js +1027 -0
- package/dist/mcp-surface.js +5 -1416
- package/dist/onramp.js +421 -0
- package/dist/orchestrator.js +20 -15
- package/dist/run-export.js +139 -1
- package/dist/telemetry-demo.js +119 -0
- package/dist/types/report-bundle.js +6 -0
- package/dist/types.js +1 -0
- package/dist/version.js +1 -1
- package/dist/workbench-host.js +1 -8
- package/docs/agent-delegation-drive.7.md +23 -2
- package/docs/cli-mcp-parity.7.md +38 -7
- package/docs/contract-migration-tooling.7.md +4 -0
- package/docs/control-plane-scheduling.7.md +4 -0
- package/docs/dogfood-one-real-repo.7.md +9 -1
- package/docs/durable-state-and-locking.7.md +4 -0
- package/docs/evidence-adoption-reasoning-chain.7.md +4 -0
- package/docs/execution-backends.7.md +4 -0
- package/docs/getting-started.md +40 -2
- package/docs/index.md +51 -37
- package/docs/multi-agent-cli-mcp-surface.7.md +4 -0
- package/docs/multi-agent-eval-replay-harness.7.md +4 -0
- package/docs/multi-agent-operator-ux.7.md +4 -0
- package/docs/node-snapshot-diff-replay.7.md +4 -0
- package/docs/observability-cost-accounting.7.md +4 -0
- package/docs/project-index.md +23 -7
- package/docs/real-execution-backends.7.md +4 -0
- package/docs/release-and-migration.7.md +5 -1
- package/docs/release-history.md +342 -0
- package/docs/release-tooling.7.md +18 -0
- package/docs/report-verifiable-bundle.7.md +123 -0
- package/docs/run-registry-control-plane.7.md +4 -0
- package/docs/run-retention-reclamation.7.md +4 -0
- package/docs/state-explosion-management.7.md +4 -0
- package/docs/team-collaboration.7.md +4 -0
- package/docs/web-desktop-workbench.7.md +4 -0
- package/manifest/plugin.manifest.json +1 -1
- package/manifest/source-context-profiles.json +1 -1
- package/package.json +2 -1
- package/scripts/architecture-review-fast.js +44 -2
- package/scripts/canonical-apps.js +4 -4
- package/scripts/dogfood-release.js +55 -133
- package/scripts/golden-path.js +4 -4
- package/scripts/onramp-check.js +47 -0
- package/scripts/parity-check.js +740 -2
- package/scripts/release-check.js +2 -0
- package/scripts/source-context.js +31 -5
- package/scripts/version-sync-check.js +4 -2
package/scripts/parity-check.js
CHANGED
|
@@ -30,6 +30,9 @@ const node = process.execPath;
|
|
|
30
30
|
const cli = path.join(pluginRoot, "dist", "cli.js");
|
|
31
31
|
const mcpServer = path.join(pluginRoot, "dist", "mcp-server.js");
|
|
32
32
|
const registry = require(path.join(pluginRoot, "dist", "capability-registry.js"));
|
|
33
|
+
const { formatHelp } = require(path.join(pluginRoot, "dist", "orchestrator.js"));
|
|
34
|
+
const { loadRunFromCwd, saveCheckpoint } = require(path.join(pluginRoot, "dist", "state.js"));
|
|
35
|
+
const { appendRunNode, createStateNode } = require(path.join(pluginRoot, "dist", "state-node.js"));
|
|
33
36
|
|
|
34
37
|
function capById(id) {
|
|
35
38
|
const cap = registry.CAPABILITY_REGISTRY.find((entry) => entry.capability === id);
|
|
@@ -41,6 +44,10 @@ function jsonFlag(cap) {
|
|
|
41
44
|
return cap.cli.jsonMode === "flag" ? ["--json"] : [];
|
|
42
45
|
}
|
|
43
46
|
|
|
47
|
+
function runCli(argv, cwd) {
|
|
48
|
+
return JSON.parse(execFileSync(node, [cli, ...argv], { cwd, encoding: "utf8" }));
|
|
49
|
+
}
|
|
50
|
+
|
|
44
51
|
// ---- 1. static surface parity ---------------------------------------------
|
|
45
52
|
function liveMcpTools() {
|
|
46
53
|
const result = execFileSync(node, [mcpServer], {
|
|
@@ -65,6 +72,22 @@ function cliDispatchSources() {
|
|
|
65
72
|
return [cli, path.join(pluginRoot, "dist", "cli", "command-surface.js")].filter((file) => fs.existsSync(file));
|
|
66
73
|
}
|
|
67
74
|
|
|
75
|
+
function cliHelpTokens() {
|
|
76
|
+
const lines = formatHelp().split(/\r?\n/);
|
|
77
|
+
const start = lines.indexOf("Commands:");
|
|
78
|
+
if (start < 0) return [];
|
|
79
|
+
const tokens = new Set();
|
|
80
|
+
for (const line of lines.slice(start + 1)) {
|
|
81
|
+
if (!line.trim()) break;
|
|
82
|
+
const first = line.trim().split(/\s+/)[0];
|
|
83
|
+
for (const token of first.split("|")) {
|
|
84
|
+
const clean = token.replace(/[<[].*$/, "");
|
|
85
|
+
if (clean) tokens.add(clean);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return [...tokens].sort();
|
|
89
|
+
}
|
|
90
|
+
|
|
68
91
|
// ---- 2. payload identity ---------------------------------------------------
|
|
69
92
|
// Generation-moment ISO timestamps are presentation metadata, not capability
|
|
70
93
|
// data: the same field carries the wall-clock instant of the render. Neutralize
|
|
@@ -73,6 +96,711 @@ function canonical(value) {
|
|
|
73
96
|
return JSON.stringify(value).replace(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z/g, "<ts>");
|
|
74
97
|
}
|
|
75
98
|
|
|
99
|
+
function escapeRegExp(value) {
|
|
100
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function replaceAllLiteral(text, search, replacement) {
|
|
104
|
+
return text.replace(new RegExp(escapeRegExp(search), "g"), replacement);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function canonicalScenario(value, context) {
|
|
108
|
+
let text = canonical(value);
|
|
109
|
+
for (const root of context.workspaceRoots || []) {
|
|
110
|
+
text = replaceAllLiteral(text, root, "<workspace>");
|
|
111
|
+
}
|
|
112
|
+
for (const runId of context.runIds || []) {
|
|
113
|
+
text = replaceAllLiteral(text, runId, "<runId>");
|
|
114
|
+
}
|
|
115
|
+
return text
|
|
116
|
+
.replace(/"durationMs":\d+/g, '"durationMs":<ms>')
|
|
117
|
+
.replace(/dispatch-\d{8}T\d{6}Z-\d{4}/g, "dispatch-<ts>-<seq>")
|
|
118
|
+
.replace(/(architecture-review|end-to-end-golden-path)-\d{8}T\d{6}Z-[a-f0-9]+/g, "<runId>")
|
|
119
|
+
.replace(/(architecture-review|end-to-end-golden-path)-<timestamp>-[a-f0-9]+/g, "<runId>")
|
|
120
|
+
.replace(/snap-<runId>([^"]*)-[a-f0-9]{12}/g, "snap-<runId>$1-<snap>")
|
|
121
|
+
.replace(/replay-snap-<runId>([^"]*)-<snap>-[a-f0-9]{8}/g, "replay-snap-<runId>$1-<snap>-<replay>")
|
|
122
|
+
.replace(/sha256:[a-f0-9]{32,64}/g, "sha256:<hash>")
|
|
123
|
+
.replace(/[a-f0-9]{64}/g, "<hex64>");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function makeWorkspace(label) {
|
|
127
|
+
const workspace = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), `cw-parity-${label}-`)));
|
|
128
|
+
fs.writeFileSync(path.join(workspace, "README.md"), "# CLI/MCP parity fixture\n", "utf8");
|
|
129
|
+
return workspace;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function bootstrapRun(workspace) {
|
|
133
|
+
return runCli(["plan", "end-to-end-golden-path", "--question", "CLI/MCP scenario payload parity"], workspace);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function bootstrapTopologyRun(workspace) {
|
|
137
|
+
return runCli(["plan", "architecture-review", "--repo", workspace, "--question", "CLI/MCP topology scenario parity"], workspace);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function writeParityResult(resultPath) {
|
|
141
|
+
fs.writeFileSync(
|
|
142
|
+
resultPath,
|
|
143
|
+
[
|
|
144
|
+
"# Result",
|
|
145
|
+
"",
|
|
146
|
+
"Deterministic parity result.",
|
|
147
|
+
"",
|
|
148
|
+
"```cw:result",
|
|
149
|
+
'{ "summary": "parity result", "findings": [], "evidence": ["README.md:1"] }',
|
|
150
|
+
"```",
|
|
151
|
+
""
|
|
152
|
+
].join("\n"),
|
|
153
|
+
"utf8"
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function readmePath(workspace) {
|
|
158
|
+
return path.join(workspace, "README.md");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function dispatchWorkerCli(workspace, runId) {
|
|
162
|
+
const dispatch = runCli(["dispatch", runId, "--limit", "1"], workspace);
|
|
163
|
+
const task = dispatch.tasks[0];
|
|
164
|
+
assert.ok(task?.workerId, `${runId}: dispatch produced no worker id`);
|
|
165
|
+
assert.ok(task.workerResultPath, `${runId}: dispatch produced no worker result path`);
|
|
166
|
+
return {
|
|
167
|
+
workerId: task.workerId,
|
|
168
|
+
taskId: task.id,
|
|
169
|
+
resultPath: task.workerResultPath
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function dispatchWorkerMcp(mcp, workspace, runId) {
|
|
174
|
+
const dispatch = await mcp.tool("cw_dispatch", { cwd: workspace, runId, limit: 1 });
|
|
175
|
+
const task = dispatch.tasks[0];
|
|
176
|
+
assert.ok(task?.workerId, `${runId}: MCP dispatch produced no worker id`);
|
|
177
|
+
assert.ok(task.workerResultPath, `${runId}: MCP dispatch produced no worker result path`);
|
|
178
|
+
return {
|
|
179
|
+
workerId: task.workerId,
|
|
180
|
+
taskId: task.id,
|
|
181
|
+
resultPath: task.workerResultPath
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function workerOutputCli(workspace, runId, worker) {
|
|
186
|
+
writeParityResult(worker.resultPath);
|
|
187
|
+
runCli(["worker", "output", runId, worker.workerId, worker.resultPath], workspace);
|
|
188
|
+
return worker;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function workerOutputMcp(mcp, workspace, runId, worker) {
|
|
192
|
+
writeParityResult(worker.resultPath);
|
|
193
|
+
await mcp.tool("cw_worker_output", { cwd: workspace, runId, workerId: worker.workerId, resultPath: worker.resultPath });
|
|
194
|
+
return worker;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function verifiedWorkerCli(workspace, runId) {
|
|
198
|
+
return workerOutputCli(workspace, runId, dispatchWorkerCli(workspace, runId));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function verifiedWorkerMcp(mcp, workspace, runId) {
|
|
202
|
+
return workerOutputMcp(mcp, workspace, runId, await dispatchWorkerMcp(mcp, workspace, runId));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function resultNodeId(runId) {
|
|
206
|
+
return `${runId}:result:golden:path`;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function verifiedNodeCli(workspace, runId) {
|
|
210
|
+
verifiedWorkerCli(workspace, runId);
|
|
211
|
+
return { nodeId: resultNodeId(runId) };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function verifiedNodeMcp(mcp, workspace, runId) {
|
|
215
|
+
await verifiedWorkerMcp(mcp, workspace, runId);
|
|
216
|
+
return { nodeId: resultNodeId(runId) };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function nodeSnapshotsCli(workspace, runId) {
|
|
220
|
+
const node = verifiedNodeCli(workspace, runId);
|
|
221
|
+
const baseline = runCli(["node", "snapshot", runId, node.nodeId], workspace);
|
|
222
|
+
const candidate = runCli(["node", "snapshot", runId, node.nodeId], workspace);
|
|
223
|
+
return { ...node, baselineSnapshotId: baseline.snapshotId, candidateSnapshotId: candidate.snapshotId };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function nodeSnapshotsMcp(mcp, workspace, runId) {
|
|
227
|
+
const node = await verifiedNodeMcp(mcp, workspace, runId);
|
|
228
|
+
const baseline = await mcp.tool("cw_node_snapshot", { cwd: workspace, runId, nodeId: node.nodeId });
|
|
229
|
+
const candidate = await mcp.tool("cw_node_snapshot", { cwd: workspace, runId, nodeId: node.nodeId });
|
|
230
|
+
return { ...node, baselineSnapshotId: baseline.snapshotId, candidateSnapshotId: candidate.snapshotId };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function nodeReplayCli(workspace, runId) {
|
|
234
|
+
const snapshots = nodeSnapshotsCli(workspace, runId);
|
|
235
|
+
const replay = runCli(["node", "replay", runId, snapshots.baselineSnapshotId], workspace);
|
|
236
|
+
return { ...snapshots, replayId: replay.replayId };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async function nodeReplayMcp(mcp, workspace, runId) {
|
|
240
|
+
const snapshots = await nodeSnapshotsMcp(mcp, workspace, runId);
|
|
241
|
+
const replay = await mcp.tool("cw_node_replay", { cwd: workspace, runId, snapshotId: snapshots.baselineSnapshotId });
|
|
242
|
+
return { ...snapshots, replayId: replay.replayId };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function registerCandidateCli(workspace, runId, worker, candidateId = "parity-candidate") {
|
|
246
|
+
runCli(["candidate", "register", runId, "--id", candidateId, "--worker", worker.workerId, "--kind", "worker-output"], workspace);
|
|
247
|
+
return { candidateId, worker };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async function registerCandidateMcp(mcp, workspace, runId, worker, candidateId = "parity-candidate") {
|
|
251
|
+
await mcp.tool("cw_candidate_register", { cwd: workspace, runId, id: candidateId, worker: worker.workerId, kind: "worker-output" });
|
|
252
|
+
return { candidateId, worker };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function scoreCandidateCli(workspace, runId, candidateId) {
|
|
256
|
+
return runCli([
|
|
257
|
+
"candidate",
|
|
258
|
+
"score",
|
|
259
|
+
runId,
|
|
260
|
+
candidateId,
|
|
261
|
+
"--criterion",
|
|
262
|
+
"correctness=4",
|
|
263
|
+
"--criterion",
|
|
264
|
+
"evidence=4",
|
|
265
|
+
"--criterion",
|
|
266
|
+
"fit=2",
|
|
267
|
+
"--max",
|
|
268
|
+
"10",
|
|
269
|
+
"--evidence",
|
|
270
|
+
"README.md:1",
|
|
271
|
+
"--notes",
|
|
272
|
+
"parity score",
|
|
273
|
+
"--scorer",
|
|
274
|
+
"parity"
|
|
275
|
+
], workspace);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async function scoreCandidateMcp(mcp, workspace, runId, candidateId) {
|
|
279
|
+
return mcp.tool("cw_candidate_score", {
|
|
280
|
+
cwd: workspace,
|
|
281
|
+
runId,
|
|
282
|
+
candidateId,
|
|
283
|
+
criterion: ["correctness=4", "evidence=4", "fit=2"],
|
|
284
|
+
max: 10,
|
|
285
|
+
evidence: ["README.md:1"],
|
|
286
|
+
notes: "parity score",
|
|
287
|
+
scorer: "parity"
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function failedWorkerFeedbackCli(workspace, runId) {
|
|
292
|
+
const worker = dispatchWorkerCli(workspace, runId);
|
|
293
|
+
const failed = runCli([
|
|
294
|
+
"worker",
|
|
295
|
+
"fail",
|
|
296
|
+
runId,
|
|
297
|
+
worker.workerId,
|
|
298
|
+
"parity failure",
|
|
299
|
+
"--code",
|
|
300
|
+
"runtime-error",
|
|
301
|
+
"--path",
|
|
302
|
+
readmePath(workspace)
|
|
303
|
+
], workspace);
|
|
304
|
+
const feedbackId = failed.feedbackIds?.[0];
|
|
305
|
+
assert.ok(feedbackId, `${runId}: worker failure produced no feedback id`);
|
|
306
|
+
return { worker, feedbackId };
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async function failedWorkerFeedbackMcp(mcp, workspace, runId) {
|
|
310
|
+
const worker = await dispatchWorkerMcp(mcp, workspace, runId);
|
|
311
|
+
const failed = await mcp.tool("cw_worker_fail", {
|
|
312
|
+
cwd: workspace,
|
|
313
|
+
runId,
|
|
314
|
+
workerId: worker.workerId,
|
|
315
|
+
message: "parity failure",
|
|
316
|
+
code: "runtime-error",
|
|
317
|
+
path: readmePath(workspace)
|
|
318
|
+
});
|
|
319
|
+
const feedbackId = failed.feedbackIds?.[0];
|
|
320
|
+
assert.ok(feedbackId, `${runId}: MCP worker failure produced no feedback id`);
|
|
321
|
+
return { worker, feedbackId };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function seedFailedNode(workspace, runId) {
|
|
325
|
+
const run = loadRunFromCwd(runId, workspace);
|
|
326
|
+
const nodeId = `${run.id}:parity:failed-node`;
|
|
327
|
+
if (!(run.nodes || []).some((node) => node.id === nodeId)) {
|
|
328
|
+
appendRunNode(run, createStateNode({
|
|
329
|
+
id: nodeId,
|
|
330
|
+
kind: "error",
|
|
331
|
+
status: "failed",
|
|
332
|
+
loopStage: "adjust",
|
|
333
|
+
errors: [{
|
|
334
|
+
code: "runtime-error",
|
|
335
|
+
message: "parity collect failure",
|
|
336
|
+
at: "2020-01-01T00:00:00.000Z",
|
|
337
|
+
path: readmePath(workspace),
|
|
338
|
+
retryable: false
|
|
339
|
+
}],
|
|
340
|
+
metadata: { pipelineStage: "parity" }
|
|
341
|
+
}));
|
|
342
|
+
saveCheckpoint(run);
|
|
343
|
+
}
|
|
344
|
+
return nodeId;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function seedVerifiedNode(workspace, runId) {
|
|
348
|
+
const run = loadRunFromCwd(runId, workspace);
|
|
349
|
+
const nodeId = `${run.id}:parity:verified-node`;
|
|
350
|
+
if (!(run.nodes || []).some((node) => node.id === nodeId)) {
|
|
351
|
+
appendRunNode(run, createStateNode({
|
|
352
|
+
id: nodeId,
|
|
353
|
+
kind: "verifier",
|
|
354
|
+
status: "verified",
|
|
355
|
+
loopStage: "adjust",
|
|
356
|
+
evidence: [{ id: "parity:verified", source: "test", locator: "README.md:1" }]
|
|
357
|
+
}));
|
|
358
|
+
saveCheckpoint(run);
|
|
359
|
+
}
|
|
360
|
+
return nodeId;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function sandboxProfileFile(workspace) {
|
|
364
|
+
const profileFile = path.join(workspace, "parity-sandbox-profile.json");
|
|
365
|
+
const profile = {
|
|
366
|
+
schemaVersion: 1,
|
|
367
|
+
id: "parity-readonly",
|
|
368
|
+
title: "Parity Readonly",
|
|
369
|
+
description: "Local sandbox profile fixture for CLI/MCP parity.",
|
|
370
|
+
readPaths: ["$cwd"],
|
|
371
|
+
writePaths: [],
|
|
372
|
+
workerOutput: { result: true, artifacts: true, logs: true },
|
|
373
|
+
execute: { mode: "none" },
|
|
374
|
+
network: { mode: "none" },
|
|
375
|
+
env: { inherit: false, expose: [] },
|
|
376
|
+
hostInstructions: ["Used only by the parity check."]
|
|
377
|
+
};
|
|
378
|
+
fs.writeFileSync(profileFile, `${JSON.stringify(profile, null, 2)}\n`, "utf8");
|
|
379
|
+
return profileFile;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function applyTopologyCli(workspace, runId) {
|
|
383
|
+
return runCli([
|
|
384
|
+
"topology",
|
|
385
|
+
"apply",
|
|
386
|
+
runId,
|
|
387
|
+
"map-reduce",
|
|
388
|
+
"--id",
|
|
389
|
+
"parity-map",
|
|
390
|
+
"--mapper-count",
|
|
391
|
+
"2",
|
|
392
|
+
"--task",
|
|
393
|
+
"map:server-api",
|
|
394
|
+
"--task",
|
|
395
|
+
"map:web-client"
|
|
396
|
+
], workspace);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
async function applyTopologyMcp(mcp, workspace, runId) {
|
|
400
|
+
return mcp.tool("cw_topology_apply", {
|
|
401
|
+
cwd: workspace,
|
|
402
|
+
runId,
|
|
403
|
+
topologyId: "map-reduce",
|
|
404
|
+
id: "parity-map",
|
|
405
|
+
mapperCount: 2,
|
|
406
|
+
task: ["map:server-api", "map:web-client"]
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function refreshSummaryCli(workspace, runId) {
|
|
411
|
+
return runCli(["summary", "refresh", runId, "--json"], workspace);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
async function refreshSummaryMcp(mcp, workspace, runId) {
|
|
415
|
+
return mcp.tool("cw_summary_refresh", { cwd: workspace, runId });
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function scenarioWorkspacePair(capability) {
|
|
419
|
+
const slug = capability.replace(/[^a-z0-9]+/gi, "-");
|
|
420
|
+
return {
|
|
421
|
+
cliWorkspace: makeWorkspace(`${slug}-cli`),
|
|
422
|
+
mcpWorkspace: makeWorkspace(`${slug}-mcp`)
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const RUNLESS_SCENARIOS = new Set([
|
|
427
|
+
"plan",
|
|
428
|
+
"app.show",
|
|
429
|
+
"app.validate",
|
|
430
|
+
"app.package",
|
|
431
|
+
"topology.show",
|
|
432
|
+
"topology.validate",
|
|
433
|
+
"sandbox.show",
|
|
434
|
+
"sandbox.validate",
|
|
435
|
+
"sandbox.choose",
|
|
436
|
+
"sandbox.resolve"
|
|
437
|
+
]);
|
|
438
|
+
|
|
439
|
+
const TOPOLOGY_SCENARIOS = new Set(["topology.apply", "topology.summary", "topology.graph"]);
|
|
440
|
+
|
|
441
|
+
function prepareRegisteredCandidateCli(workspace, runId, candidateId = "parity-candidate") {
|
|
442
|
+
return registerCandidateCli(workspace, runId, verifiedWorkerCli(workspace, runId), candidateId);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
async function prepareRegisteredCandidateMcp(mcp, workspace, runId, candidateId = "parity-candidate") {
|
|
446
|
+
return registerCandidateMcp(mcp, workspace, runId, await verifiedWorkerMcp(mcp, workspace, runId), candidateId);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function prepareScoredCandidateCli(workspace, runId, candidateId = "parity-candidate") {
|
|
450
|
+
const candidate = prepareRegisteredCandidateCli(workspace, runId, candidateId);
|
|
451
|
+
scoreCandidateCli(workspace, runId, candidate.candidateId);
|
|
452
|
+
return candidate;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
async function prepareScoredCandidateMcp(mcp, workspace, runId, candidateId = "parity-candidate") {
|
|
456
|
+
const candidate = await prepareRegisteredCandidateMcp(mcp, workspace, runId, candidateId);
|
|
457
|
+
await scoreCandidateMcp(mcp, workspace, runId, candidate.candidateId);
|
|
458
|
+
return candidate;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function prepareScenarioCli(capability, workspace, runId) {
|
|
462
|
+
switch (capability) {
|
|
463
|
+
case "node.show":
|
|
464
|
+
case "node.snapshot":
|
|
465
|
+
return verifiedNodeCli(workspace, runId);
|
|
466
|
+
case "node.diff":
|
|
467
|
+
return nodeSnapshotsCli(workspace, runId);
|
|
468
|
+
case "node.replay":
|
|
469
|
+
return { snapshotId: nodeSnapshotsCli(workspace, runId).baselineSnapshotId };
|
|
470
|
+
case "node.replay.verify":
|
|
471
|
+
return nodeReplayCli(workspace, runId);
|
|
472
|
+
case "worker.list":
|
|
473
|
+
case "worker.show":
|
|
474
|
+
case "worker.manifest":
|
|
475
|
+
case "worker.validate":
|
|
476
|
+
case "worker.fail":
|
|
477
|
+
return { worker: dispatchWorkerCli(workspace, runId) };
|
|
478
|
+
case "worker.output": {
|
|
479
|
+
const worker = dispatchWorkerCli(workspace, runId);
|
|
480
|
+
writeParityResult(worker.resultPath);
|
|
481
|
+
return { worker };
|
|
482
|
+
}
|
|
483
|
+
case "candidate.register":
|
|
484
|
+
return { worker: verifiedWorkerCli(workspace, runId), candidateId: "parity-candidate" };
|
|
485
|
+
case "candidate.list":
|
|
486
|
+
case "candidate.show":
|
|
487
|
+
case "candidate.score":
|
|
488
|
+
case "candidate.reject":
|
|
489
|
+
return prepareRegisteredCandidateCli(workspace, runId);
|
|
490
|
+
case "candidate.rank":
|
|
491
|
+
case "candidate.select":
|
|
492
|
+
return prepareScoredCandidateCli(workspace, runId);
|
|
493
|
+
case "feedback.collect":
|
|
494
|
+
return { failedNodeId: seedFailedNode(workspace, runId) };
|
|
495
|
+
case "feedback.list":
|
|
496
|
+
case "feedback.show":
|
|
497
|
+
case "feedback.task":
|
|
498
|
+
return failedWorkerFeedbackCli(workspace, runId);
|
|
499
|
+
case "feedback.resolve": {
|
|
500
|
+
const feedback = failedWorkerFeedbackCli(workspace, runId);
|
|
501
|
+
return { ...feedback, verifierNodeId: seedVerifiedNode(workspace, runId) };
|
|
502
|
+
}
|
|
503
|
+
default:
|
|
504
|
+
return {};
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
async function prepareScenarioMcp(capability, mcp, workspace, runId) {
|
|
509
|
+
switch (capability) {
|
|
510
|
+
case "node.show":
|
|
511
|
+
case "node.snapshot":
|
|
512
|
+
return verifiedNodeMcp(mcp, workspace, runId);
|
|
513
|
+
case "node.diff":
|
|
514
|
+
return nodeSnapshotsMcp(mcp, workspace, runId);
|
|
515
|
+
case "node.replay": {
|
|
516
|
+
const snapshots = await nodeSnapshotsMcp(mcp, workspace, runId);
|
|
517
|
+
return { snapshotId: snapshots.baselineSnapshotId };
|
|
518
|
+
}
|
|
519
|
+
case "node.replay.verify":
|
|
520
|
+
return nodeReplayMcp(mcp, workspace, runId);
|
|
521
|
+
case "worker.list":
|
|
522
|
+
case "worker.show":
|
|
523
|
+
case "worker.manifest":
|
|
524
|
+
case "worker.validate":
|
|
525
|
+
case "worker.fail":
|
|
526
|
+
return { worker: await dispatchWorkerMcp(mcp, workspace, runId) };
|
|
527
|
+
case "worker.output": {
|
|
528
|
+
const worker = await dispatchWorkerMcp(mcp, workspace, runId);
|
|
529
|
+
writeParityResult(worker.resultPath);
|
|
530
|
+
return { worker };
|
|
531
|
+
}
|
|
532
|
+
case "candidate.register":
|
|
533
|
+
return { worker: await verifiedWorkerMcp(mcp, workspace, runId), candidateId: "parity-candidate" };
|
|
534
|
+
case "candidate.list":
|
|
535
|
+
case "candidate.show":
|
|
536
|
+
case "candidate.score":
|
|
537
|
+
case "candidate.reject":
|
|
538
|
+
return prepareRegisteredCandidateMcp(mcp, workspace, runId);
|
|
539
|
+
case "candidate.rank":
|
|
540
|
+
case "candidate.select":
|
|
541
|
+
return prepareScoredCandidateMcp(mcp, workspace, runId);
|
|
542
|
+
case "feedback.collect":
|
|
543
|
+
return { failedNodeId: seedFailedNode(workspace, runId) };
|
|
544
|
+
case "feedback.list":
|
|
545
|
+
case "feedback.show":
|
|
546
|
+
case "feedback.task":
|
|
547
|
+
return failedWorkerFeedbackMcp(mcp, workspace, runId);
|
|
548
|
+
case "feedback.resolve": {
|
|
549
|
+
const feedback = await failedWorkerFeedbackMcp(mcp, workspace, runId);
|
|
550
|
+
return { ...feedback, verifierNodeId: seedVerifiedNode(workspace, runId) };
|
|
551
|
+
}
|
|
552
|
+
default:
|
|
553
|
+
return {};
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function runScenarioCli(capability, workspace, runId, context = {}) {
|
|
558
|
+
switch (capability) {
|
|
559
|
+
case "plan":
|
|
560
|
+
return runCli(["plan", "end-to-end-golden-path", "--question", "CLI/MCP scenario payload parity"], workspace);
|
|
561
|
+
case "app.show":
|
|
562
|
+
return runCli(["app", "show", "end-to-end-golden-path"], workspace);
|
|
563
|
+
case "app.validate":
|
|
564
|
+
return runCli(["app", "validate", "end-to-end-golden-path"], workspace);
|
|
565
|
+
case "app.package":
|
|
566
|
+
return runCli(["app", "package", "end-to-end-golden-path"], workspace);
|
|
567
|
+
case "topology.show":
|
|
568
|
+
return runCli(["topology", "show", "map-reduce"], workspace);
|
|
569
|
+
case "topology.validate":
|
|
570
|
+
return runCli(["topology", "validate", "map-reduce"], workspace);
|
|
571
|
+
case "topology.apply":
|
|
572
|
+
return applyTopologyCli(workspace, runId);
|
|
573
|
+
case "topology.summary":
|
|
574
|
+
return runCli(["topology", "summary", runId, "--json"], workspace);
|
|
575
|
+
case "topology.graph":
|
|
576
|
+
return runCli(["topology", "graph", runId, "--json"], workspace);
|
|
577
|
+
case "summary.refresh":
|
|
578
|
+
return refreshSummaryCli(workspace, runId);
|
|
579
|
+
case "summary.show":
|
|
580
|
+
return runCli(["summary", "show", runId, "--json"], workspace);
|
|
581
|
+
case "sandbox.show":
|
|
582
|
+
return runCli(["sandbox", "show", "readonly"], workspace);
|
|
583
|
+
case "sandbox.validate":
|
|
584
|
+
return runCli(["sandbox", "validate", sandboxProfileFile(workspace)], workspace);
|
|
585
|
+
case "sandbox.choose":
|
|
586
|
+
return runCli(["sandbox", "choose", "readonly"], workspace);
|
|
587
|
+
case "sandbox.resolve":
|
|
588
|
+
return runCli(["sandbox", "resolve", "readonly"], workspace);
|
|
589
|
+
case "approve":
|
|
590
|
+
return runCli(["approve", "run", runId, runId, "--actor", "parity-operator", "--role", "reviewer", "--rationale", "scenario approval"], workspace);
|
|
591
|
+
case "reject":
|
|
592
|
+
return runCli(["reject", "run", runId, runId, "--actor", "parity-operator", "--role", "reviewer", "--reason", "scenario rejection"], workspace);
|
|
593
|
+
case "comment.add":
|
|
594
|
+
return runCli(["comment", "add", "run", runId, runId, "--body", "scenario comment", "--actor", "parity-operator", "--role", "reviewer"], workspace);
|
|
595
|
+
case "handoff":
|
|
596
|
+
return runCli(["handoff", "run", runId, runId, "--from", "parity-operator", "--to", "parity-peer", "--reason", "scenario handoff"], workspace);
|
|
597
|
+
case "review.policy":
|
|
598
|
+
return runCli([
|
|
599
|
+
"review",
|
|
600
|
+
"policy",
|
|
601
|
+
runId,
|
|
602
|
+
"--required-approvals",
|
|
603
|
+
"2",
|
|
604
|
+
"--authorized-roles",
|
|
605
|
+
"reviewer,lead",
|
|
606
|
+
"--applies-to",
|
|
607
|
+
"commit,selection",
|
|
608
|
+
"--allow-self-approval"
|
|
609
|
+
], workspace);
|
|
610
|
+
case "node.show":
|
|
611
|
+
return runCli(["node", "show", runId, context.nodeId], workspace);
|
|
612
|
+
case "node.snapshot":
|
|
613
|
+
return runCli(["node", "snapshot", runId, context.nodeId], workspace);
|
|
614
|
+
case "node.diff":
|
|
615
|
+
return runCli(["node", "diff", runId, context.baselineSnapshotId, context.candidateSnapshotId], workspace);
|
|
616
|
+
case "node.replay":
|
|
617
|
+
return runCli(["node", "replay", runId, context.snapshotId], workspace);
|
|
618
|
+
case "node.replay.verify":
|
|
619
|
+
return runCli(["node", "verify", runId, context.replayId], workspace);
|
|
620
|
+
case "worker.list":
|
|
621
|
+
return runCli(["worker", "list", runId], workspace);
|
|
622
|
+
case "worker.show":
|
|
623
|
+
return runCli(["worker", "show", runId, context.worker.workerId], workspace);
|
|
624
|
+
case "worker.manifest":
|
|
625
|
+
return runCli(["worker", "manifest", runId, context.worker.workerId], workspace);
|
|
626
|
+
case "worker.output":
|
|
627
|
+
return runCli(["worker", "output", runId, context.worker.workerId, context.worker.resultPath], workspace);
|
|
628
|
+
case "worker.fail":
|
|
629
|
+
return runCli(["worker", "fail", runId, context.worker.workerId, "parity failure", "--code", "runtime-error", "--path", readmePath(workspace)], workspace);
|
|
630
|
+
case "worker.validate":
|
|
631
|
+
return runCli(["worker", "validate", runId, context.worker.workerId, context.worker.resultPath], workspace);
|
|
632
|
+
case "candidate.list":
|
|
633
|
+
return runCli(["candidate", "list", runId], workspace);
|
|
634
|
+
case "candidate.show":
|
|
635
|
+
return runCli(["candidate", "show", runId, context.candidateId], workspace);
|
|
636
|
+
case "candidate.register":
|
|
637
|
+
return runCli(["candidate", "register", runId, "--id", context.candidateId, "--worker", context.worker.workerId, "--kind", "worker-output"], workspace);
|
|
638
|
+
case "candidate.score":
|
|
639
|
+
return scoreCandidateCli(workspace, runId, context.candidateId);
|
|
640
|
+
case "candidate.rank":
|
|
641
|
+
return runCli(["candidate", "rank", runId], workspace);
|
|
642
|
+
case "candidate.select":
|
|
643
|
+
return runCli(["candidate", "select", runId, context.candidateId, "--reason", "parity selected", "--selectedBy", "parity"], workspace);
|
|
644
|
+
case "candidate.reject":
|
|
645
|
+
return runCli(["candidate", "reject", runId, context.candidateId, "--reason", "parity rejected"], workspace);
|
|
646
|
+
case "feedback.list":
|
|
647
|
+
return runCli(["feedback", "list", runId], workspace);
|
|
648
|
+
case "feedback.show":
|
|
649
|
+
return runCli(["feedback", "show", runId, context.feedbackId], workspace);
|
|
650
|
+
case "feedback.collect":
|
|
651
|
+
return runCli(["feedback", "collect", runId], workspace);
|
|
652
|
+
case "feedback.task":
|
|
653
|
+
return runCli(["feedback", "task", runId, context.feedbackId, "--verify", "npm test"], workspace);
|
|
654
|
+
case "feedback.resolve":
|
|
655
|
+
return runCli(["feedback", "resolve", runId, context.feedbackId, "--status", "resolved", "--node", context.verifierNodeId, "--message", "parity resolved"], workspace);
|
|
656
|
+
default:
|
|
657
|
+
throw new Error(`No CLI payload scenario for ${capability}`);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
async function runScenarioMcp(capability, mcp, workspace, runId, context = {}) {
|
|
662
|
+
switch (capability) {
|
|
663
|
+
case "plan":
|
|
664
|
+
return mcp.tool("cw_plan", { cwd: workspace, workflowId: "end-to-end-golden-path", question: "CLI/MCP scenario payload parity" });
|
|
665
|
+
case "app.show":
|
|
666
|
+
return mcp.tool("cw_app_show", { cwd: workspace, appId: "end-to-end-golden-path" });
|
|
667
|
+
case "app.validate":
|
|
668
|
+
return mcp.tool("cw_app_validate", { cwd: workspace, appId: "end-to-end-golden-path" });
|
|
669
|
+
case "app.package":
|
|
670
|
+
return mcp.tool("cw_app_package", { cwd: workspace, appId: "end-to-end-golden-path" });
|
|
671
|
+
case "topology.show":
|
|
672
|
+
return mcp.tool("cw_topology_show", { cwd: workspace, topologyId: "map-reduce" });
|
|
673
|
+
case "topology.validate":
|
|
674
|
+
return mcp.tool("cw_topology_validate", { cwd: workspace, topologyId: "map-reduce" });
|
|
675
|
+
case "topology.apply":
|
|
676
|
+
return applyTopologyMcp(mcp, workspace, runId);
|
|
677
|
+
case "topology.summary":
|
|
678
|
+
return mcp.tool("cw_topology_summary", { cwd: workspace, runId });
|
|
679
|
+
case "topology.graph":
|
|
680
|
+
return mcp.tool("cw_topology_graph", { cwd: workspace, runId });
|
|
681
|
+
case "summary.refresh":
|
|
682
|
+
return refreshSummaryMcp(mcp, workspace, runId);
|
|
683
|
+
case "summary.show":
|
|
684
|
+
return mcp.tool("cw_summary_show", { cwd: workspace, runId });
|
|
685
|
+
case "sandbox.show":
|
|
686
|
+
return mcp.tool("cw_sandbox_show", { cwd: workspace, profileId: "readonly" });
|
|
687
|
+
case "sandbox.validate":
|
|
688
|
+
return mcp.tool("cw_sandbox_validate", { cwd: workspace, profileFile: sandboxProfileFile(workspace) });
|
|
689
|
+
case "sandbox.choose":
|
|
690
|
+
return mcp.tool("cw_sandbox_choose", { cwd: workspace, profileId: "readonly" });
|
|
691
|
+
case "sandbox.resolve":
|
|
692
|
+
return mcp.tool("cw_sandbox_resolve", { cwd: workspace, profileId: "readonly" });
|
|
693
|
+
case "approve":
|
|
694
|
+
return mcp.tool("cw_approve", { cwd: workspace, runId, targetKind: "run", targetId: runId, actor: "parity-operator", role: "reviewer", rationale: "scenario approval" });
|
|
695
|
+
case "reject":
|
|
696
|
+
return mcp.tool("cw_reject", { cwd: workspace, runId, targetKind: "run", targetId: runId, actor: "parity-operator", role: "reviewer", reason: "scenario rejection" });
|
|
697
|
+
case "comment.add":
|
|
698
|
+
return mcp.tool("cw_comment_add", { cwd: workspace, runId, targetKind: "run", targetId: runId, body: "scenario comment", actor: "parity-operator", role: "reviewer" });
|
|
699
|
+
case "handoff":
|
|
700
|
+
return mcp.tool("cw_handoff", { cwd: workspace, runId, targetKind: "run", targetId: runId, from: "parity-operator", to: "parity-peer", reason: "scenario handoff" });
|
|
701
|
+
case "review.policy":
|
|
702
|
+
return mcp.tool("cw_review_policy", {
|
|
703
|
+
cwd: workspace,
|
|
704
|
+
runId,
|
|
705
|
+
requiredApprovals: 2,
|
|
706
|
+
authorizedRoles: "reviewer,lead",
|
|
707
|
+
appliesTo: "commit,selection",
|
|
708
|
+
allowSelfApproval: true
|
|
709
|
+
});
|
|
710
|
+
case "node.show":
|
|
711
|
+
return mcp.tool("cw_node_show", { cwd: workspace, runId, nodeId: context.nodeId });
|
|
712
|
+
case "node.snapshot":
|
|
713
|
+
return mcp.tool("cw_node_snapshot", { cwd: workspace, runId, nodeId: context.nodeId });
|
|
714
|
+
case "node.diff":
|
|
715
|
+
return mcp.tool("cw_node_diff", { cwd: workspace, runId, baselineSnapshotId: context.baselineSnapshotId, candidateSnapshotId: context.candidateSnapshotId });
|
|
716
|
+
case "node.replay":
|
|
717
|
+
return mcp.tool("cw_node_replay", { cwd: workspace, runId, snapshotId: context.snapshotId });
|
|
718
|
+
case "node.replay.verify":
|
|
719
|
+
return mcp.tool("cw_node_replay_verify", { cwd: workspace, runId, replayId: context.replayId });
|
|
720
|
+
case "worker.list":
|
|
721
|
+
return mcp.tool("cw_worker_list", { cwd: workspace, runId });
|
|
722
|
+
case "worker.show":
|
|
723
|
+
return mcp.tool("cw_worker_show", { cwd: workspace, runId, workerId: context.worker.workerId });
|
|
724
|
+
case "worker.manifest":
|
|
725
|
+
return mcp.tool("cw_worker_manifest", { cwd: workspace, runId, workerId: context.worker.workerId });
|
|
726
|
+
case "worker.output":
|
|
727
|
+
return mcp.tool("cw_worker_output", { cwd: workspace, runId, workerId: context.worker.workerId, resultPath: context.worker.resultPath });
|
|
728
|
+
case "worker.fail":
|
|
729
|
+
return mcp.tool("cw_worker_fail", { cwd: workspace, runId, workerId: context.worker.workerId, message: "parity failure", code: "runtime-error", path: readmePath(workspace) });
|
|
730
|
+
case "worker.validate":
|
|
731
|
+
return mcp.tool("cw_worker_validate", { cwd: workspace, runId, workerId: context.worker.workerId, resultPath: context.worker.resultPath });
|
|
732
|
+
case "candidate.list":
|
|
733
|
+
return mcp.tool("cw_candidate_list", { cwd: workspace, runId });
|
|
734
|
+
case "candidate.show":
|
|
735
|
+
return mcp.tool("cw_candidate_show", { cwd: workspace, runId, candidateId: context.candidateId });
|
|
736
|
+
case "candidate.register":
|
|
737
|
+
return mcp.tool("cw_candidate_register", { cwd: workspace, runId, id: context.candidateId, worker: context.worker.workerId, kind: "worker-output" });
|
|
738
|
+
case "candidate.score":
|
|
739
|
+
return scoreCandidateMcp(mcp, workspace, runId, context.candidateId);
|
|
740
|
+
case "candidate.rank":
|
|
741
|
+
return mcp.tool("cw_candidate_rank", { cwd: workspace, runId });
|
|
742
|
+
case "candidate.select":
|
|
743
|
+
return mcp.tool("cw_candidate_select", { cwd: workspace, runId, candidateId: context.candidateId, reason: "parity selected", selectedBy: "parity" });
|
|
744
|
+
case "candidate.reject":
|
|
745
|
+
return mcp.tool("cw_candidate_reject", { cwd: workspace, runId, candidateId: context.candidateId, reason: "parity rejected" });
|
|
746
|
+
case "feedback.list":
|
|
747
|
+
return mcp.tool("cw_feedback_list", { cwd: workspace, runId });
|
|
748
|
+
case "feedback.show":
|
|
749
|
+
return mcp.tool("cw_feedback_show", { cwd: workspace, runId, feedbackId: context.feedbackId });
|
|
750
|
+
case "feedback.collect":
|
|
751
|
+
return mcp.tool("cw_feedback_collect", { cwd: workspace, runId });
|
|
752
|
+
case "feedback.task":
|
|
753
|
+
return mcp.tool("cw_feedback_task", { cwd: workspace, runId, feedbackId: context.feedbackId, verify: "npm test" });
|
|
754
|
+
case "feedback.resolve":
|
|
755
|
+
return mcp.tool("cw_feedback_resolve", { cwd: workspace, runId, feedbackId: context.feedbackId, status: "resolved", node: context.verifierNodeId, message: "parity resolved" });
|
|
756
|
+
default:
|
|
757
|
+
throw new Error(`No MCP payload scenario for ${capability}`);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
async function executeScenario(target, mcp) {
|
|
762
|
+
const { cliWorkspace, mcpWorkspace } = scenarioWorkspacePair(target.capability);
|
|
763
|
+
if (RUNLESS_SCENARIOS.has(target.capability)) {
|
|
764
|
+
const cliOut = runScenarioCli(target.capability, cliWorkspace);
|
|
765
|
+
const mcpOut = await runScenarioMcp(target.capability, mcp, mcpWorkspace);
|
|
766
|
+
return {
|
|
767
|
+
cliPayload: canonicalScenario(cliOut, {
|
|
768
|
+
workspaceRoots: [cliWorkspace],
|
|
769
|
+
runIds: cliOut.runId ? [cliOut.runId] : []
|
|
770
|
+
}),
|
|
771
|
+
mcpPayload: canonicalScenario(mcpOut, {
|
|
772
|
+
workspaceRoots: [mcpWorkspace],
|
|
773
|
+
runIds: mcpOut.runId ? [mcpOut.runId] : []
|
|
774
|
+
})
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
const cliPlan = TOPOLOGY_SCENARIOS.has(target.capability) ? bootstrapTopologyRun(cliWorkspace) : bootstrapRun(cliWorkspace);
|
|
779
|
+
const mcpPlan = TOPOLOGY_SCENARIOS.has(target.capability) ? bootstrapTopologyRun(mcpWorkspace) : bootstrapRun(mcpWorkspace);
|
|
780
|
+
if (target.capability === "topology.summary" || target.capability === "topology.graph") {
|
|
781
|
+
applyTopologyCli(cliWorkspace, cliPlan.runId);
|
|
782
|
+
await applyTopologyMcp(mcp, mcpWorkspace, mcpPlan.runId);
|
|
783
|
+
}
|
|
784
|
+
if (target.capability === "summary.show") {
|
|
785
|
+
refreshSummaryCli(cliWorkspace, cliPlan.runId);
|
|
786
|
+
await refreshSummaryMcp(mcp, mcpWorkspace, mcpPlan.runId);
|
|
787
|
+
}
|
|
788
|
+
const cliContext = prepareScenarioCli(target.capability, cliWorkspace, cliPlan.runId);
|
|
789
|
+
const mcpContext = await prepareScenarioMcp(target.capability, mcp, mcpWorkspace, mcpPlan.runId);
|
|
790
|
+
const cliOut = runScenarioCli(target.capability, cliWorkspace, cliPlan.runId, cliContext);
|
|
791
|
+
const mcpOut = await runScenarioMcp(target.capability, mcp, mcpWorkspace, mcpPlan.runId, mcpContext);
|
|
792
|
+
return {
|
|
793
|
+
cliPayload: canonicalScenario(cliOut, {
|
|
794
|
+
workspaceRoots: [cliWorkspace],
|
|
795
|
+
runIds: [cliPlan.runId]
|
|
796
|
+
}),
|
|
797
|
+
mcpPayload: canonicalScenario(mcpOut, {
|
|
798
|
+
workspaceRoots: [mcpWorkspace],
|
|
799
|
+
runIds: [mcpPlan.runId]
|
|
800
|
+
})
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
|
|
76
804
|
function openMcp() {
|
|
77
805
|
const server = spawn(node, [mcpServer], { cwd: pluginRoot, stdio: ["pipe", "pipe", "pipe"] });
|
|
78
806
|
const lines = readline.createInterface({ input: server.stdout });
|
|
@@ -123,15 +851,22 @@ async function payloadParity() {
|
|
|
123
851
|
await mcp.rpc("initialize", {});
|
|
124
852
|
for (const target of registry.payloadProbeTargets()) {
|
|
125
853
|
const cap = capById(target.capability);
|
|
854
|
+
if (target.kind === "scenario") {
|
|
855
|
+
const result = await executeScenario(target, mcp);
|
|
856
|
+
checked.push(target.capability);
|
|
857
|
+
if (result.cliPayload !== result.mcpPayload) mismatches.push(target.capability);
|
|
858
|
+
continue;
|
|
859
|
+
}
|
|
126
860
|
// jsonMode is the single source for the CLI's --json policy; this probe only
|
|
127
861
|
// appends --json for "flag" verbs and JSON.parse-es the result. The human
|
|
128
862
|
// rendering and "default"-verb no-flag JSON are pinned to cap.cli.jsonMode by
|
|
129
863
|
// the companion test/cli-jsonmode-parity-smoke.js, so cli.ts can't silently
|
|
130
864
|
// re-encode that policy by hand and drift from this registry data.
|
|
865
|
+
if (target.kind !== "global" && target.kind !== "run") throw new Error(`Unknown payload probe target kind: ${target.kind}`);
|
|
131
866
|
const cliArgv = target.kind === "run"
|
|
132
867
|
? [...cap.cli.path, runId, ...jsonFlag(cap)]
|
|
133
868
|
: [...cap.cli.path, ...jsonFlag(cap)];
|
|
134
|
-
const cliOut =
|
|
869
|
+
const cliOut = runCli(cliArgv, workspace);
|
|
135
870
|
const mcpArgs = target.kind === "run" ? { cwd: workspace, runId } : { cwd: workspace };
|
|
136
871
|
const mcpOut = await mcp.tool(cap.mcp.tool, mcpArgs);
|
|
137
872
|
checked.push(target.capability);
|
|
@@ -147,7 +882,8 @@ async function main() {
|
|
|
147
882
|
const check = process.argv.includes("--check");
|
|
148
883
|
const tools = liveMcpTools();
|
|
149
884
|
const tokens = cliDispatchTokens();
|
|
150
|
-
const
|
|
885
|
+
const helpTokens = cliHelpTokens();
|
|
886
|
+
const report = registry.buildParityReport({ mcpTools: tools, cliTokens: tokens, helpTokens });
|
|
151
887
|
const payload = await payloadParity();
|
|
152
888
|
|
|
153
889
|
const ok = report.ok && payload.mismatches.length === 0;
|
|
@@ -170,6 +906,8 @@ async function main() {
|
|
|
170
906
|
if (report.undeclaredMcpTools.length) lines.push(` - server exposes MCP tools not declared in the registry: ${report.undeclaredMcpTools.join(", ")}`);
|
|
171
907
|
if (report.missingCliTokens.length) lines.push(` - registry declares CLI tokens absent from dist/cli.js: ${report.missingCliTokens.join(", ")}`);
|
|
172
908
|
if (report.undeclaredCliTokens.length) lines.push(` - dist/cli.js dispatches tokens not declared in the registry: ${report.undeclaredCliTokens.join(", ")}`);
|
|
909
|
+
if (report.helpMissingCliTokens.length) lines.push(` - registry declares CLI commands absent from cw help: ${report.helpMissingCliTokens.join(", ")}`);
|
|
910
|
+
if (report.helpUndeclaredCliTokens.length) lines.push(` - cw help lists commands not declared in the registry: ${report.helpUndeclaredCliTokens.join(", ")}`);
|
|
173
911
|
if (report.reasonlessExceptions.length) lines.push(` - surface-specific / payload-divergent capabilities missing a recorded reason: ${report.reasonlessExceptions.join(", ")}`);
|
|
174
912
|
if (report.payloadProbeUnclassified.length) lines.push(` - payload-identical capabilities neither probed nor deferred: ${report.payloadProbeUnclassified.join(", ")}`);
|
|
175
913
|
if (report.payloadProbeDuplicateClassifications.length) lines.push(` - payload probe duplicate classifications: ${report.payloadProbeDuplicateClassifications.join(", ")}`);
|