cool-workflow 0.1.92 → 0.1.94
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 +104 -129
- package/apps/architecture-review/app.json +1 -1
- package/apps/architecture-review-fast/app.json +1 -1
- package/apps/architecture-review-fast/workflow.js +15 -2
- 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 +47 -0
- package/dist/capability-registry.js +2 -0
- package/dist/cli/command-surface.js +165 -1352
- package/dist/cli/format.js +56 -0
- package/dist/cli/handlers/audit.js +82 -0
- package/dist/cli/handlers/blackboard.js +81 -0
- package/dist/cli/handlers/candidate.js +40 -0
- package/dist/cli/handlers/clones.js +34 -0
- package/dist/cli/handlers/collaboration.js +61 -0
- package/dist/cli/handlers/eval.js +40 -0
- package/dist/cli/handlers/maintenance.js +107 -0
- package/dist/cli/handlers/multi-agent.js +165 -0
- package/dist/cli/handlers/node.js +41 -0
- package/dist/cli/handlers/operational.js +155 -0
- package/dist/cli/handlers/operator.js +146 -0
- package/dist/cli/handlers/registry.js +68 -0
- package/dist/cli/handlers/run.js +153 -0
- package/dist/cli/handlers/scheduling.js +126 -0
- package/dist/cli/handlers/workbench.js +41 -0
- package/dist/cli/handlers/worker.js +45 -0
- package/dist/cli/io.js +27 -0
- package/dist/cli/run-summary.js +45 -0
- package/dist/commit.js +0 -5
- package/dist/execution-backend.js +0 -11
- package/dist/mcp/tool-call.js +2 -0
- package/dist/mcp/tool-definitions.js +8 -0
- package/dist/orchestrator/app-operations.js +205 -0
- package/dist/orchestrator.js +41 -153
- package/dist/state-explosion.js +0 -7
- package/dist/term.js +0 -18
- package/dist/validation.js +0 -21
- package/dist/version.js +1 -1
- package/docs/agent-delegation-drive.7.md +13 -7
- package/docs/cli-mcp-parity.7.md +19 -2
- package/docs/contract-migration-tooling.7.md +3 -1
- package/docs/control-plane-scheduling.7.md +3 -1
- package/docs/durable-state-and-locking.7.md +3 -1
- package/docs/evidence-adoption-reasoning-chain.7.md +3 -1
- package/docs/execution-backends.7.md +3 -1
- package/docs/mcp-app-surface.7.md +12 -0
- package/docs/multi-agent-cli-mcp-surface.7.md +3 -1
- package/docs/multi-agent-eval-replay-harness.7.md +3 -1
- package/docs/multi-agent-operator-ux.7.md +3 -1
- package/docs/node-snapshot-diff-replay.7.md +3 -1
- package/docs/observability-cost-accounting.7.md +3 -1
- package/docs/project-index.md +16 -3
- package/docs/real-execution-backends.7.md +3 -1
- package/docs/release-and-migration.7.md +3 -1
- package/docs/release-tooling.7.md +3 -1
- package/docs/run-registry-control-plane.7.md +27 -3
- package/docs/run-retention-reclamation.7.md +3 -1
- package/docs/state-explosion-management.7.md +3 -1
- package/docs/team-collaboration.7.md +3 -1
- package/docs/web-desktop-workbench.7.md +3 -1
- package/manifest/plugin.manifest.json +1 -1
- package/package.json +1 -1
- package/scripts/agents/builtin-templates.json +3 -2
- package/scripts/agents/claude-p-agent.js +6 -6
- package/scripts/agents/codex-agent.js +17 -3
- package/scripts/agents/deepseek-agent.js +23 -0
- package/scripts/agents/gemini-opencode-agent.js +25 -0
- package/scripts/agents/opencode-agent.js +57 -8
- package/scripts/architecture-review-fast.js +19 -5
- package/scripts/bump-version.js +16 -0
- package/scripts/canonical-apps.js +4 -4
- package/scripts/dogfood-release.js +1 -1
- package/scripts/golden-path.js +4 -4
- package/scripts/parity-check.js +9 -1
- package/scripts/release-flow.js +35 -0
- package/scripts/vendor-preflight.js +127 -0
- package/scripts/version-sync-check.js +5 -0
|
@@ -0,0 +1,205 @@
|
|
|
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.loadWorkflowFiles = loadWorkflowFiles;
|
|
7
|
+
exports.loadAppManifestFiles = loadAppManifestFiles;
|
|
8
|
+
exports.loadWorkflowApps = loadWorkflowApps;
|
|
9
|
+
exports.loadWorkflowAppById = loadWorkflowAppById;
|
|
10
|
+
exports.loadWorkflowAppTarget = loadWorkflowAppTarget;
|
|
11
|
+
exports.listWorkflows = listWorkflows;
|
|
12
|
+
exports.listApps = listApps;
|
|
13
|
+
exports.showApp = showApp;
|
|
14
|
+
exports.validateApp = validateApp;
|
|
15
|
+
exports.initApp = initApp;
|
|
16
|
+
exports.packageApp = packageApp;
|
|
17
|
+
// Workflow-app management domain operations (v0.1.40 self-audit router pattern).
|
|
18
|
+
// Carved out of CoolWorkflowRunner; pure functions (no instance state). Behavior
|
|
19
|
+
// is identical to the inline versions — the runner-owned calls (resolveFromBase,
|
|
20
|
+
// validateApp) are passed as callbacks so the bodies stay byte-identical.
|
|
21
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
22
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
23
|
+
const workflow_api_1 = require("../workflow-api");
|
|
24
|
+
const state_1 = require("../state");
|
|
25
|
+
const workflow_app_framework_1 = require("../workflow-app-framework");
|
|
26
|
+
const cli_options_1 = require("./cli-options");
|
|
27
|
+
function loadWorkflowFiles(workflowsDir) {
|
|
28
|
+
if (!node_fs_1.default.existsSync(workflowsDir))
|
|
29
|
+
return [];
|
|
30
|
+
return node_fs_1.default
|
|
31
|
+
.readdirSync(workflowsDir)
|
|
32
|
+
.filter((file) => file.endsWith(".workflow.js"))
|
|
33
|
+
.sort()
|
|
34
|
+
.map((file) => node_path_1.default.join(workflowsDir, file));
|
|
35
|
+
}
|
|
36
|
+
function loadAppManifestFiles(appsDir) {
|
|
37
|
+
if (!node_fs_1.default.existsSync(appsDir))
|
|
38
|
+
return [];
|
|
39
|
+
return node_fs_1.default
|
|
40
|
+
.readdirSync(appsDir, { withFileTypes: true })
|
|
41
|
+
.filter((entry) => entry.isDirectory())
|
|
42
|
+
.map((entry) => node_path_1.default.join(appsDir, entry.name, "app.json"))
|
|
43
|
+
.filter((file) => node_fs_1.default.existsSync(file))
|
|
44
|
+
.sort();
|
|
45
|
+
}
|
|
46
|
+
function loadWorkflowApps(workflowsDir, appsDir) {
|
|
47
|
+
const records = [
|
|
48
|
+
...loadWorkflowFiles(workflowsDir).map((file) => (0, workflow_app_framework_1.loadWorkflowAppFromEntrypoint)(file)),
|
|
49
|
+
...loadAppManifestFiles(appsDir).map((file) => (0, workflow_app_framework_1.loadWorkflowAppFromManifest)(file))
|
|
50
|
+
].sort((left, right) => {
|
|
51
|
+
const byId = left.app.id.localeCompare(right.app.id);
|
|
52
|
+
if (byId)
|
|
53
|
+
return byId;
|
|
54
|
+
return (left.source.manifestPath || left.source.entrypointPath || left.source.path)
|
|
55
|
+
.localeCompare(right.source.manifestPath || right.source.entrypointPath || right.source.path);
|
|
56
|
+
});
|
|
57
|
+
const seen = new Map();
|
|
58
|
+
for (const record of records) {
|
|
59
|
+
const previous = seen.get(record.app.id);
|
|
60
|
+
if (previous) {
|
|
61
|
+
throw new Error(`Duplicate workflow app id ${record.app.id}: ${previous.source.manifestPath || previous.source.entrypointPath || previous.source.path} and ${record.source.manifestPath || record.source.entrypointPath || record.source.path}`);
|
|
62
|
+
}
|
|
63
|
+
seen.set(record.app.id, record);
|
|
64
|
+
}
|
|
65
|
+
return records;
|
|
66
|
+
}
|
|
67
|
+
function loadWorkflowAppById(workflowsDir, appsDir, appId) {
|
|
68
|
+
const record = loadWorkflowApps(workflowsDir, appsDir).find((candidate) => candidate.app.id === appId);
|
|
69
|
+
if (!record)
|
|
70
|
+
throw new Error(`Workflow app not found: ${appId}`);
|
|
71
|
+
return record;
|
|
72
|
+
}
|
|
73
|
+
// The runner pre-resolves `target` via resolveFromBase and passes the resolved
|
|
74
|
+
// path; the original `target` is still used for the id-fallback lookup.
|
|
75
|
+
function loadWorkflowAppTarget(workflowsDir, appsDir, resolved, target) {
|
|
76
|
+
if (!target)
|
|
77
|
+
throw new Error("Missing workflow app path or id");
|
|
78
|
+
if (node_fs_1.default.existsSync(resolved)) {
|
|
79
|
+
const stat = node_fs_1.default.statSync(resolved);
|
|
80
|
+
if (stat.isDirectory())
|
|
81
|
+
return (0, workflow_app_framework_1.loadWorkflowAppFromManifest)(node_path_1.default.join(resolved, "app.json"));
|
|
82
|
+
if (node_path_1.default.basename(resolved) === "app.json" || resolved.endsWith(".json"))
|
|
83
|
+
return (0, workflow_app_framework_1.loadWorkflowAppFromManifest)(resolved);
|
|
84
|
+
return (0, workflow_app_framework_1.loadWorkflowAppFromEntrypoint)(resolved);
|
|
85
|
+
}
|
|
86
|
+
return loadWorkflowAppById(workflowsDir, appsDir, target);
|
|
87
|
+
}
|
|
88
|
+
function listWorkflows(workflowsDir, appsDir) {
|
|
89
|
+
return loadWorkflowApps(workflowsDir, appsDir).map((record) => {
|
|
90
|
+
const summary = (0, workflow_app_framework_1.summarizeWorkflowApp)(record);
|
|
91
|
+
return {
|
|
92
|
+
id: summary.id,
|
|
93
|
+
title: summary.title,
|
|
94
|
+
summary: summary.summary,
|
|
95
|
+
file: summary.file
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
function listApps(workflowsDir, appsDir) {
|
|
100
|
+
return loadWorkflowApps(workflowsDir, appsDir).map((record) => (0, workflow_app_framework_1.summarizeWorkflowApp)(record));
|
|
101
|
+
}
|
|
102
|
+
function showApp(workflowsDir, appsDir, appId) {
|
|
103
|
+
const record = loadWorkflowAppById(workflowsDir, appsDir, appId);
|
|
104
|
+
const summary = (0, workflow_app_framework_1.summarizeWorkflowApp)(record);
|
|
105
|
+
return {
|
|
106
|
+
...summary,
|
|
107
|
+
source: record.source,
|
|
108
|
+
app: {
|
|
109
|
+
schemaVersion: record.app.schemaVersion,
|
|
110
|
+
id: record.app.id,
|
|
111
|
+
title: record.app.title,
|
|
112
|
+
summary: record.app.summary || "",
|
|
113
|
+
version: record.app.version,
|
|
114
|
+
author: record.app.author,
|
|
115
|
+
inputs: record.app.inputs || record.app.workflow.inputs,
|
|
116
|
+
sandboxProfiles: record.app.sandboxProfiles || record.app.workflow.sandboxProfiles || [],
|
|
117
|
+
compatibility: record.app.compatibility,
|
|
118
|
+
metadata: record.app.metadata || {}
|
|
119
|
+
},
|
|
120
|
+
workflow: {
|
|
121
|
+
id: record.app.workflow.id,
|
|
122
|
+
title: record.app.workflow.title,
|
|
123
|
+
summary: record.app.workflow.summary || "",
|
|
124
|
+
limits: record.app.workflow.limits,
|
|
125
|
+
inputs: record.app.workflow.inputs,
|
|
126
|
+
sandboxProfiles: record.app.workflow.sandboxProfiles || [],
|
|
127
|
+
phases: record.app.workflow.phases.map((phase) => ({
|
|
128
|
+
id: phase.id,
|
|
129
|
+
name: phase.name,
|
|
130
|
+
status: phase.status,
|
|
131
|
+
tasks: phase.tasks.map((task) => ({
|
|
132
|
+
id: task.id,
|
|
133
|
+
kind: task.kind,
|
|
134
|
+
requiresEvidence: Boolean(task.requiresEvidence),
|
|
135
|
+
sandboxProfileId: task.sandboxProfileId
|
|
136
|
+
}))
|
|
137
|
+
}))
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
// The runner pre-resolves `target` via resolveFromBase and passes the resolved
|
|
142
|
+
// path; both the try-loader and the catch-branch appPath use resolvedTarget.
|
|
143
|
+
function validateApp(workflowsDir, appsDir, target, resolvedTarget) {
|
|
144
|
+
try {
|
|
145
|
+
const record = loadWorkflowAppTarget(workflowsDir, appsDir, resolvedTarget, target);
|
|
146
|
+
const result = (0, workflow_app_framework_1.validateWorkflowApp)(record.app, {
|
|
147
|
+
appPath: record.source.manifestPath || record.source.entrypointPath || record.source.path
|
|
148
|
+
});
|
|
149
|
+
return {
|
|
150
|
+
...result,
|
|
151
|
+
summary: (0, workflow_app_framework_1.summarizeWorkflowApp)(record)
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
const issues = (0, cli_options_1.validationIssuesFromError)(error);
|
|
156
|
+
return {
|
|
157
|
+
valid: false,
|
|
158
|
+
appId: target,
|
|
159
|
+
appPath: resolvedTarget,
|
|
160
|
+
issues
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// resolveFromBase and validateApp are passed as callbacks so the body stays
|
|
165
|
+
// byte-identical to the runner method (resolveFromBase(...) and validateApp(manifestPath)).
|
|
166
|
+
function initApp(appsDir, appId, options, resolveFromBase, validateApp) {
|
|
167
|
+
const id = (0, workflow_api_1.slugify)(appId);
|
|
168
|
+
if (!id)
|
|
169
|
+
throw new Error("App id must include at least one letter or digit");
|
|
170
|
+
const title = String(options.title || titleize(id));
|
|
171
|
+
const destinationDir = resolveFromBase(String(options.directory || options.output || node_path_1.default.join(appsDir, id)));
|
|
172
|
+
const manifestPath = node_path_1.default.join(destinationDir, "app.json");
|
|
173
|
+
const entrypointPath = node_path_1.default.join(destinationDir, "workflow.js");
|
|
174
|
+
if (!options.force && (node_fs_1.default.existsSync(manifestPath) || node_fs_1.default.existsSync(entrypointPath))) {
|
|
175
|
+
throw new Error(`Refusing to overwrite existing workflow app: ${destinationDir}`);
|
|
176
|
+
}
|
|
177
|
+
node_fs_1.default.mkdirSync(destinationDir, { recursive: true });
|
|
178
|
+
node_fs_1.default.writeFileSync(manifestPath, (0, workflow_app_framework_1.renderWorkflowAppManifestTemplate)(id, title), "utf8");
|
|
179
|
+
node_fs_1.default.writeFileSync(entrypointPath, (0, workflow_app_framework_1.renderWorkflowAppEntrypointTemplate)(id, title), "utf8");
|
|
180
|
+
const validation = validateApp(manifestPath);
|
|
181
|
+
if (!validation.valid) {
|
|
182
|
+
throw new workflow_app_framework_1.WorkflowAppValidationError("Generated workflow app is invalid", validation.issues);
|
|
183
|
+
}
|
|
184
|
+
return { id, manifestPath, entrypointPath };
|
|
185
|
+
}
|
|
186
|
+
// resolveFromBase is passed as a callback so the body stays byte-identical.
|
|
187
|
+
function packageApp(workflowsDir, appsDir, appId, options, resolveFromBase) {
|
|
188
|
+
const record = loadWorkflowAppById(workflowsDir, appsDir, appId);
|
|
189
|
+
const destination = resolveFromBase(String(options.output || node_path_1.default.join(".cw", "packages", `${record.app.id}-${record.app.version}.cwapp.json`)));
|
|
190
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(destination), { recursive: true });
|
|
191
|
+
(0, state_1.writeJson)(destination, {
|
|
192
|
+
schemaVersion: 1,
|
|
193
|
+
app: (0, workflow_app_framework_1.workflowAppRunMetadata)(record),
|
|
194
|
+
workflow: record.app.workflow,
|
|
195
|
+
packagedAt: new Date().toISOString()
|
|
196
|
+
});
|
|
197
|
+
return { id: record.app.id, version: record.app.version, path: destination };
|
|
198
|
+
}
|
|
199
|
+
function titleize(value) {
|
|
200
|
+
return value
|
|
201
|
+
.split("-")
|
|
202
|
+
.filter(Boolean)
|
|
203
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
204
|
+
.join(" ");
|
|
205
|
+
}
|
package/dist/orchestrator.js
CHANGED
|
@@ -42,9 +42,11 @@ exports.suggestCommand = suggestCommand;
|
|
|
42
42
|
exports.formatSearchResults = formatSearchResults;
|
|
43
43
|
exports.formatInfo = formatInfo;
|
|
44
44
|
exports.formatHelp = formatHelp;
|
|
45
|
+
exports.formatCommandHelp = formatCommandHelp;
|
|
45
46
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
46
47
|
const node_path_1 = __importDefault(require("node:path"));
|
|
47
48
|
const workflow_api_1 = require("./workflow-api");
|
|
49
|
+
const capability_registry_1 = require("./capability-registry");
|
|
48
50
|
const workflow_app_framework_1 = require("./workflow-app-framework");
|
|
49
51
|
const dispatch_1 = require("./dispatch");
|
|
50
52
|
const state_1 = require("./state");
|
|
@@ -62,6 +64,7 @@ const state_explosion_1 = require("./state-explosion");
|
|
|
62
64
|
const evidence_reasoning_1 = require("./evidence-reasoning");
|
|
63
65
|
const report_1 = require("./orchestrator/report");
|
|
64
66
|
const cli_options_1 = require("./orchestrator/cli-options");
|
|
67
|
+
const appOps = __importStar(require("./orchestrator/app-operations"));
|
|
65
68
|
const auditOps = __importStar(require("./orchestrator/audit-operations"));
|
|
66
69
|
const candidateOps = __importStar(require("./orchestrator/candidate-operations"));
|
|
67
70
|
const collaborationOps = __importStar(require("./orchestrator/collaboration-operations"));
|
|
@@ -114,110 +117,22 @@ class CoolWorkflowRunner {
|
|
|
114
117
|
return new CoolWorkflowRunner({ pluginRoot: this.pluginRoot, baseDir: resolved });
|
|
115
118
|
}
|
|
116
119
|
listWorkflows() {
|
|
117
|
-
return
|
|
118
|
-
const summary = (0, workflow_app_framework_1.summarizeWorkflowApp)(record);
|
|
119
|
-
return {
|
|
120
|
-
id: summary.id,
|
|
121
|
-
title: summary.title,
|
|
122
|
-
summary: summary.summary,
|
|
123
|
-
file: summary.file
|
|
124
|
-
};
|
|
125
|
-
});
|
|
120
|
+
return appOps.listWorkflows(this.workflowsDir, this.appsDir);
|
|
126
121
|
}
|
|
127
122
|
listApps() {
|
|
128
|
-
return
|
|
123
|
+
return appOps.listApps(this.workflowsDir, this.appsDir);
|
|
129
124
|
}
|
|
130
125
|
showApp(appId) {
|
|
131
|
-
|
|
132
|
-
const summary = (0, workflow_app_framework_1.summarizeWorkflowApp)(record);
|
|
133
|
-
return {
|
|
134
|
-
...summary,
|
|
135
|
-
source: record.source,
|
|
136
|
-
app: {
|
|
137
|
-
schemaVersion: record.app.schemaVersion,
|
|
138
|
-
id: record.app.id,
|
|
139
|
-
title: record.app.title,
|
|
140
|
-
summary: record.app.summary || "",
|
|
141
|
-
version: record.app.version,
|
|
142
|
-
author: record.app.author,
|
|
143
|
-
inputs: record.app.inputs || record.app.workflow.inputs,
|
|
144
|
-
sandboxProfiles: record.app.sandboxProfiles || record.app.workflow.sandboxProfiles || [],
|
|
145
|
-
compatibility: record.app.compatibility,
|
|
146
|
-
metadata: record.app.metadata || {}
|
|
147
|
-
},
|
|
148
|
-
workflow: {
|
|
149
|
-
id: record.app.workflow.id,
|
|
150
|
-
title: record.app.workflow.title,
|
|
151
|
-
summary: record.app.workflow.summary || "",
|
|
152
|
-
limits: record.app.workflow.limits,
|
|
153
|
-
inputs: record.app.workflow.inputs,
|
|
154
|
-
sandboxProfiles: record.app.workflow.sandboxProfiles || [],
|
|
155
|
-
phases: record.app.workflow.phases.map((phase) => ({
|
|
156
|
-
id: phase.id,
|
|
157
|
-
name: phase.name,
|
|
158
|
-
status: phase.status,
|
|
159
|
-
tasks: phase.tasks.map((task) => ({
|
|
160
|
-
id: task.id,
|
|
161
|
-
kind: task.kind,
|
|
162
|
-
requiresEvidence: Boolean(task.requiresEvidence),
|
|
163
|
-
sandboxProfileId: task.sandboxProfileId
|
|
164
|
-
}))
|
|
165
|
-
}))
|
|
166
|
-
}
|
|
167
|
-
};
|
|
126
|
+
return appOps.showApp(this.workflowsDir, this.appsDir, appId);
|
|
168
127
|
}
|
|
169
128
|
validateApp(target) {
|
|
170
|
-
|
|
171
|
-
const record = this.loadWorkflowAppTarget(target);
|
|
172
|
-
const result = (0, workflow_app_framework_1.validateWorkflowApp)(record.app, {
|
|
173
|
-
appPath: record.source.manifestPath || record.source.entrypointPath || record.source.path
|
|
174
|
-
});
|
|
175
|
-
return {
|
|
176
|
-
...result,
|
|
177
|
-
summary: (0, workflow_app_framework_1.summarizeWorkflowApp)(record)
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
catch (error) {
|
|
181
|
-
const issues = (0, cli_options_1.validationIssuesFromError)(error);
|
|
182
|
-
return {
|
|
183
|
-
valid: false,
|
|
184
|
-
appId: target,
|
|
185
|
-
appPath: this.resolveFromBase(target),
|
|
186
|
-
issues
|
|
187
|
-
};
|
|
188
|
-
}
|
|
129
|
+
return appOps.validateApp(this.workflowsDir, this.appsDir, target, this.resolveFromBase(target));
|
|
189
130
|
}
|
|
190
131
|
initApp(appId, options) {
|
|
191
|
-
|
|
192
|
-
if (!id)
|
|
193
|
-
throw new Error("App id must include at least one letter or digit");
|
|
194
|
-
const title = String(options.title || titleize(id));
|
|
195
|
-
const destinationDir = this.resolveFromBase(String(options.directory || options.output || node_path_1.default.join(this.appsDir, id)));
|
|
196
|
-
const manifestPath = node_path_1.default.join(destinationDir, "app.json");
|
|
197
|
-
const entrypointPath = node_path_1.default.join(destinationDir, "workflow.js");
|
|
198
|
-
if (!options.force && (node_fs_1.default.existsSync(manifestPath) || node_fs_1.default.existsSync(entrypointPath))) {
|
|
199
|
-
throw new Error(`Refusing to overwrite existing workflow app: ${destinationDir}`);
|
|
200
|
-
}
|
|
201
|
-
node_fs_1.default.mkdirSync(destinationDir, { recursive: true });
|
|
202
|
-
node_fs_1.default.writeFileSync(manifestPath, (0, workflow_app_framework_1.renderWorkflowAppManifestTemplate)(id, title), "utf8");
|
|
203
|
-
node_fs_1.default.writeFileSync(entrypointPath, (0, workflow_app_framework_1.renderWorkflowAppEntrypointTemplate)(id, title), "utf8");
|
|
204
|
-
const validation = this.validateApp(manifestPath);
|
|
205
|
-
if (!validation.valid) {
|
|
206
|
-
throw new workflow_app_framework_1.WorkflowAppValidationError("Generated workflow app is invalid", validation.issues);
|
|
207
|
-
}
|
|
208
|
-
return { id, manifestPath, entrypointPath };
|
|
132
|
+
return appOps.initApp(this.appsDir, appId, options, (t) => this.resolveFromBase(t), (m) => this.validateApp(m));
|
|
209
133
|
}
|
|
210
134
|
packageApp(appId, options = {}) {
|
|
211
|
-
|
|
212
|
-
const destination = this.resolveFromBase(String(options.output || node_path_1.default.join(".cw", "packages", `${record.app.id}-${record.app.version}.cwapp.json`)));
|
|
213
|
-
node_fs_1.default.mkdirSync(node_path_1.default.dirname(destination), { recursive: true });
|
|
214
|
-
(0, state_1.writeJson)(destination, {
|
|
215
|
-
schemaVersion: 1,
|
|
216
|
-
app: (0, workflow_app_framework_1.workflowAppRunMetadata)(record),
|
|
217
|
-
workflow: record.app.workflow,
|
|
218
|
-
packagedAt: new Date().toISOString()
|
|
219
|
-
});
|
|
220
|
-
return { id: record.app.id, version: record.app.version, path: destination };
|
|
135
|
+
return appOps.packageApp(this.workflowsDir, this.appsDir, appId, options, (t) => this.resolveFromBase(t));
|
|
221
136
|
}
|
|
222
137
|
init(workflowId, options) {
|
|
223
138
|
const id = (0, workflow_api_1.slugify)(workflowId);
|
|
@@ -726,64 +641,7 @@ class CoolWorkflowRunner {
|
|
|
726
641
|
return node_path_1.default.resolve(this.invocationCwd(), target);
|
|
727
642
|
}
|
|
728
643
|
loadWorkflowAppById(appId) {
|
|
729
|
-
|
|
730
|
-
if (!record)
|
|
731
|
-
throw new Error(`Workflow app not found: ${appId}`);
|
|
732
|
-
return record;
|
|
733
|
-
}
|
|
734
|
-
loadWorkflowAppTarget(target) {
|
|
735
|
-
if (!target)
|
|
736
|
-
throw new Error("Missing workflow app path or id");
|
|
737
|
-
const resolved = this.resolveFromBase(target);
|
|
738
|
-
if (node_fs_1.default.existsSync(resolved)) {
|
|
739
|
-
const stat = node_fs_1.default.statSync(resolved);
|
|
740
|
-
if (stat.isDirectory())
|
|
741
|
-
return (0, workflow_app_framework_1.loadWorkflowAppFromManifest)(node_path_1.default.join(resolved, "app.json"));
|
|
742
|
-
if (node_path_1.default.basename(resolved) === "app.json" || resolved.endsWith(".json"))
|
|
743
|
-
return (0, workflow_app_framework_1.loadWorkflowAppFromManifest)(resolved);
|
|
744
|
-
return (0, workflow_app_framework_1.loadWorkflowAppFromEntrypoint)(resolved);
|
|
745
|
-
}
|
|
746
|
-
return this.loadWorkflowAppById(target);
|
|
747
|
-
}
|
|
748
|
-
loadWorkflowApps() {
|
|
749
|
-
const records = [
|
|
750
|
-
...this.loadWorkflowFiles().map((file) => (0, workflow_app_framework_1.loadWorkflowAppFromEntrypoint)(file)),
|
|
751
|
-
...this.loadAppManifestFiles().map((file) => (0, workflow_app_framework_1.loadWorkflowAppFromManifest)(file))
|
|
752
|
-
].sort((left, right) => {
|
|
753
|
-
const byId = left.app.id.localeCompare(right.app.id);
|
|
754
|
-
if (byId)
|
|
755
|
-
return byId;
|
|
756
|
-
return (left.source.manifestPath || left.source.entrypointPath || left.source.path)
|
|
757
|
-
.localeCompare(right.source.manifestPath || right.source.entrypointPath || right.source.path);
|
|
758
|
-
});
|
|
759
|
-
const seen = new Map();
|
|
760
|
-
for (const record of records) {
|
|
761
|
-
const previous = seen.get(record.app.id);
|
|
762
|
-
if (previous) {
|
|
763
|
-
throw new Error(`Duplicate workflow app id ${record.app.id}: ${previous.source.manifestPath || previous.source.entrypointPath || previous.source.path} and ${record.source.manifestPath || record.source.entrypointPath || record.source.path}`);
|
|
764
|
-
}
|
|
765
|
-
seen.set(record.app.id, record);
|
|
766
|
-
}
|
|
767
|
-
return records;
|
|
768
|
-
}
|
|
769
|
-
loadWorkflowFiles() {
|
|
770
|
-
if (!node_fs_1.default.existsSync(this.workflowsDir))
|
|
771
|
-
return [];
|
|
772
|
-
return node_fs_1.default
|
|
773
|
-
.readdirSync(this.workflowsDir)
|
|
774
|
-
.filter((file) => file.endsWith(".workflow.js"))
|
|
775
|
-
.sort()
|
|
776
|
-
.map((file) => node_path_1.default.join(this.workflowsDir, file));
|
|
777
|
-
}
|
|
778
|
-
loadAppManifestFiles() {
|
|
779
|
-
if (!node_fs_1.default.existsSync(this.appsDir))
|
|
780
|
-
return [];
|
|
781
|
-
return node_fs_1.default
|
|
782
|
-
.readdirSync(this.appsDir, { withFileTypes: true })
|
|
783
|
-
.filter((entry) => entry.isDirectory())
|
|
784
|
-
.map((entry) => node_path_1.default.join(this.appsDir, entry.name, "app.json"))
|
|
785
|
-
.filter((file) => node_fs_1.default.existsSync(file))
|
|
786
|
-
.sort();
|
|
644
|
+
return appOps.loadWorkflowAppById(this.workflowsDir, this.appsDir, appId);
|
|
787
645
|
}
|
|
788
646
|
}
|
|
789
647
|
exports.CoolWorkflowRunner = CoolWorkflowRunner;
|
|
@@ -973,7 +831,37 @@ function formatHelp() {
|
|
|
973
831
|
" --no-color Disable ANSI color (also honors NO_COLOR / FORCE_COLOR)",
|
|
974
832
|
"",
|
|
975
833
|
(0, term_1.bold)("More commands", out),
|
|
976
|
-
...wrapped
|
|
834
|
+
...wrapped,
|
|
835
|
+
"",
|
|
836
|
+
// 4-space indent on purpose: the CLI/MCP parity help-token check only parses
|
|
837
|
+
// 2-space command lines, so this note never registers as a bogus token.
|
|
838
|
+
" Run cw help <command> for one command's subcommands and descriptions."
|
|
839
|
+
].join("\n");
|
|
840
|
+
}
|
|
841
|
+
/** Per-command help: `cw help <verb>` (and `cw <verb> --help`) lists that verb's
|
|
842
|
+
* CLI subcommands with one-line summaries, derived from CAPABILITY_REGISTRY — the
|
|
843
|
+
* SAME table the dispatcher and the CLI/MCP parity check use, so there is no
|
|
844
|
+
* second source to drift. Additive: formatHelp()'s parity-checked token list is
|
|
845
|
+
* untouched. */
|
|
846
|
+
function formatCommandHelp(verb) {
|
|
847
|
+
const out = process.stdout;
|
|
848
|
+
const matches = capability_registry_1.CAPABILITY_REGISTRY.filter((cap) => cap.cli && cap.cli.path[0] === verb);
|
|
849
|
+
if (matches.length === 0) {
|
|
850
|
+
const lines = [`Unknown command: ${verb}`];
|
|
851
|
+
const hint = suggestCommand(verb);
|
|
852
|
+
if (hint)
|
|
853
|
+
lines.push(` Did you mean: cw ${hint}`);
|
|
854
|
+
lines.push(" Try: cw help (list all commands)");
|
|
855
|
+
return lines.join("\n");
|
|
856
|
+
}
|
|
857
|
+
const rows = matches
|
|
858
|
+
.map((cap) => ({ cmd: `cw ${cap.cli.path.join(" ")}`, summary: cap.summary }))
|
|
859
|
+
.sort((a, b) => (a.cmd < b.cmd ? -1 : a.cmd > b.cmd ? 1 : 0));
|
|
860
|
+
const width = Math.min(40, rows.reduce((max, row) => Math.max(max, row.cmd.length), 0));
|
|
861
|
+
return [
|
|
862
|
+
(0, term_1.bold)(`cw ${verb}`, out),
|
|
863
|
+
"",
|
|
864
|
+
...rows.map((row) => ` ${row.cmd.padEnd(width)} ${row.summary}`)
|
|
977
865
|
].join("\n");
|
|
978
866
|
}
|
|
979
867
|
function appendOption(options, key, value) {
|
package/dist/state-explosion.js
CHANGED
|
@@ -7,7 +7,6 @@ exports.stateExplosionReportLines = exports.formatBlackboardDigest = exports.for
|
|
|
7
7
|
exports.computeStateSize = computeStateSize;
|
|
8
8
|
exports.summarizeBlackboardDigest = summarizeBlackboardDigest;
|
|
9
9
|
exports.buildCompactGraph = buildCompactGraph;
|
|
10
|
-
exports.buildOperatorDigest = buildOperatorDigest;
|
|
11
10
|
exports.buildStateExplosionReport = buildStateExplosionReport;
|
|
12
11
|
exports.maybeCompactRun = maybeCompactRun;
|
|
13
12
|
exports.refreshStateExplosionSummaries = refreshStateExplosionSummaries;
|
|
@@ -732,12 +731,6 @@ function expansionCommandFor(run, view, key) {
|
|
|
732
731
|
return `node scripts/cw.js multi-agent graph ${run.id} --view full --json`;
|
|
733
732
|
return `node scripts/cw.js multi-agent graph ${run.id} --view full --focus ${key} --json`;
|
|
734
733
|
}
|
|
735
|
-
// ---------------------------------------------------------------------------
|
|
736
|
-
// Operator digest
|
|
737
|
-
// ---------------------------------------------------------------------------
|
|
738
|
-
function buildOperatorDigest(run, thresholds = exports.DEFAULT_STATE_EXPLOSION_THRESHOLDS) {
|
|
739
|
-
return buildOperatorDigestWithContext(run, thresholds, createStateExplosionBuildContext());
|
|
740
|
-
}
|
|
741
734
|
function buildOperatorDigestWithContext(run, thresholds, context) {
|
|
742
735
|
const stateSize = stateSizeFor(run, thresholds, context);
|
|
743
736
|
const operator = operatorFor(run, context);
|
package/dist/term.js
CHANGED
|
@@ -14,13 +14,11 @@ exports.yellow = yellow;
|
|
|
14
14
|
exports.red = red;
|
|
15
15
|
exports.cyan = cyan;
|
|
16
16
|
exports.doctorGlyph = doctorGlyph;
|
|
17
|
-
exports.cwLabel = cwLabel;
|
|
18
17
|
exports.indent = indent;
|
|
19
18
|
exports.nextHint = nextHint;
|
|
20
19
|
exports.tryHint = tryHint;
|
|
21
20
|
exports.sectionHeader = sectionHeader;
|
|
22
21
|
exports.phaseProgressLine = phaseProgressLine;
|
|
23
|
-
exports.formatDuration = formatDuration;
|
|
24
22
|
exports.printSuccessSummary = printSuccessSummary;
|
|
25
23
|
exports.stripAnsi = stripAnsi;
|
|
26
24
|
exports.visibleWidth = visibleWidth;
|
|
@@ -84,10 +82,6 @@ function doctorGlyph(status, stream) {
|
|
|
84
82
|
};
|
|
85
83
|
return color[status](`${glyph[status]}`, stream);
|
|
86
84
|
}
|
|
87
|
-
/** "cw:" prefix with bold and optional color. */
|
|
88
|
-
function cwLabel(stream) {
|
|
89
|
-
return bold("cw:", stream);
|
|
90
|
-
}
|
|
91
85
|
/** Render a multi-line block with consistent 2-space indentation. */
|
|
92
86
|
function indent(text, spaces = 2) {
|
|
93
87
|
const prefix = " ".repeat(spaces);
|
|
@@ -113,18 +107,6 @@ function phaseProgressLine(name, done, total, mode, stream) {
|
|
|
113
107
|
const count = total > 0 ? ` (${done}/${total})` : "";
|
|
114
108
|
return `${sectionHeader(name, stream)} ${glyph}${count}`;
|
|
115
109
|
}
|
|
116
|
-
/** Format a DURATION in ms as `850ms` / `5.2s` / `1m02s`. Pure (no clock) — the
|
|
117
|
-
* caller measures elapsed via process.hrtime, so this never reads wall-clock time. */
|
|
118
|
-
function formatDuration(ms) {
|
|
119
|
-
if (ms < 1000)
|
|
120
|
-
return `${Math.max(0, Math.round(ms))}ms`;
|
|
121
|
-
const s = Math.round(ms / 100) / 10;
|
|
122
|
-
if (s < 60)
|
|
123
|
-
return `${s}s`;
|
|
124
|
-
const m = Math.floor(s / 60);
|
|
125
|
-
const rem = Math.round(s % 60);
|
|
126
|
-
return `${m}m${String(rem).padStart(2, "0")}s`;
|
|
127
|
-
}
|
|
128
110
|
/** Print a success summary to stderr (TTY-gated). Shows the report path, a one-line
|
|
129
111
|
* status (with N/N worker counts when known), and a copy-pasteable next/recovery
|
|
130
112
|
* command. Pipe-friendly: silent when stderr is not a TTY, so it never pollutes
|
package/dist/validation.js
CHANGED
|
@@ -19,15 +19,11 @@
|
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
20
|
exports.RecordValidationError = void 0;
|
|
21
21
|
exports.validateWorkerScope = validateWorkerScope;
|
|
22
|
-
exports.tryValidateWorkerScope = tryValidateWorkerScope;
|
|
23
22
|
exports.validateNodeSnapshot = validateNodeSnapshot;
|
|
24
|
-
exports.tryValidateNodeSnapshot = tryValidateNodeSnapshot;
|
|
25
23
|
exports.validateNodeReplayRun = validateNodeReplayRun;
|
|
26
|
-
exports.tryValidateNodeReplayRun = tryValidateNodeReplayRun;
|
|
27
24
|
exports.validateCandidateScore = validateCandidateScore;
|
|
28
25
|
exports.tryValidateCandidateScore = tryValidateCandidateScore;
|
|
29
26
|
exports.validateCandidateRecord = validateCandidateRecord;
|
|
30
|
-
exports.tryValidateCandidateRecord = tryValidateCandidateRecord;
|
|
31
27
|
// ---------------------------------------------------------------------------
|
|
32
28
|
// Primitive predicates — small, total, never throw.
|
|
33
29
|
// ---------------------------------------------------------------------------
|
|
@@ -126,10 +122,6 @@ function validateWorkerScope(value) {
|
|
|
126
122
|
throw new RecordValidationError("WorkerScope", problem.reason, problem.field);
|
|
127
123
|
return value;
|
|
128
124
|
}
|
|
129
|
-
/** Best-effort variant: returns null on mismatch (caller skips the record). */
|
|
130
|
-
function tryValidateWorkerScope(value) {
|
|
131
|
-
return workerScopeReason(value) ? null : value;
|
|
132
|
-
}
|
|
133
125
|
// ---------------------------------------------------------------------------
|
|
134
126
|
// NodeSnapshotBody — shared by NodeSnapshot.body and NodeReplayRun.body.
|
|
135
127
|
// Required (per src/types/state-node.ts): id, kind, status, loopStage strings;
|
|
@@ -184,10 +176,6 @@ function validateNodeSnapshot(value) {
|
|
|
184
176
|
throw new RecordValidationError("NodeSnapshot", problem.reason, problem.field);
|
|
185
177
|
return value;
|
|
186
178
|
}
|
|
187
|
-
/** Best-effort variant: returns null on mismatch. */
|
|
188
|
-
function tryValidateNodeSnapshot(value) {
|
|
189
|
-
return nodeSnapshotReason(value) ? null : value;
|
|
190
|
-
}
|
|
191
179
|
// ---------------------------------------------------------------------------
|
|
192
180
|
// NodeReplayRun — node-snapshot.ts:133
|
|
193
181
|
// Required: schemaVersion===1, replayId, runId, nodeId, snapshotId, replayedAt,
|
|
@@ -226,10 +214,6 @@ function validateNodeReplayRun(value) {
|
|
|
226
214
|
throw new RecordValidationError("NodeReplayRun", problem.reason, problem.field);
|
|
227
215
|
return value;
|
|
228
216
|
}
|
|
229
|
-
/** Best-effort variant: returns null on mismatch. */
|
|
230
|
-
function tryValidateNodeReplayRun(value) {
|
|
231
|
-
return nodeReplayRunReason(value) ? null : value;
|
|
232
|
-
}
|
|
233
217
|
// ---------------------------------------------------------------------------
|
|
234
218
|
// CandidateScore — multi-agent-operator-ux.ts:502 / evidence-reasoning.ts:750
|
|
235
219
|
// Required (per src/types/candidate.ts): schemaVersion===1, id, candidateId,
|
|
@@ -321,8 +305,3 @@ function validateCandidateRecord(value) {
|
|
|
321
305
|
throw new RecordValidationError("CandidateRecord", problem.reason, problem.field);
|
|
322
306
|
return value;
|
|
323
307
|
}
|
|
324
|
-
/** Best-effort variant: returns null on mismatch (caller skips the record so the
|
|
325
|
-
* downstream selection gate fails closed on its absence). */
|
|
326
|
-
function tryValidateCandidateRecord(value) {
|
|
327
|
-
return candidateRecordReason(value) ? null : value;
|
|
328
|
-
}
|
package/dist/version.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MIN_SUPPORTED_RUN_STATE_SCHEMA_VERSION = exports.LEGACY_RUN_STATE_SCHEMA_VERSION = exports.CURRENT_RUN_STATE_SCHEMA_VERSION = exports.WORKFLOW_APP_SCHEMA_VERSION = exports.CURRENT_COOL_WORKFLOW_VERSION = void 0;
|
|
4
|
-
exports.CURRENT_COOL_WORKFLOW_VERSION = "0.1.
|
|
4
|
+
exports.CURRENT_COOL_WORKFLOW_VERSION = "0.1.94";
|
|
5
5
|
exports.WORKFLOW_APP_SCHEMA_VERSION = 1;
|
|
6
6
|
exports.CURRENT_RUN_STATE_SCHEMA_VERSION = 1;
|
|
7
7
|
exports.LEGACY_RUN_STATE_SCHEMA_VERSION = 0;
|
|
@@ -239,14 +239,18 @@ never polluted.
|
|
|
239
239
|
The built-in templates are:
|
|
240
240
|
|
|
241
241
|
```text
|
|
242
|
-
--agent-command builtin:claude
|
|
243
|
-
--agent-command builtin:codex
|
|
242
|
+
--agent-command builtin:claude # claude -p (native)
|
|
243
|
+
--agent-command builtin:codex # codex exec (native)
|
|
244
|
+
--agent-command builtin:gemini # Gemini via opencode (google/gemini-3.5-flash)
|
|
245
|
+
--agent-command builtin:gemini-cli # native gemini CLI (needs GEMINI_API_KEY)
|
|
246
|
+
--agent-command builtin:opencode # opencode (its configured default model)
|
|
247
|
+
--agent-command builtin:deepseek # DeepSeek via opencode (deepseek/deepseek-chat)
|
|
244
248
|
```
|
|
245
249
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
+
claude and codex run their own CLIs; gemini and deepseek route through opencode
|
|
251
|
+
(where their keys live), each proven by a local, deterministic wrapper smoke
|
|
252
|
+
(override the model with CW_GEMINI_MODEL / CW_DEEPSEEK_MODEL). GLM stays an
|
|
253
|
+
external agent command or HTTP endpoint. CW still imports no model SDK.
|
|
250
254
|
|
|
251
255
|
## Compatibility
|
|
252
256
|
|
|
@@ -337,4 +341,6 @@ The one-command `cw -q` headline now routes the question and defaults the repo t
|
|
|
337
341
|
|
|
338
342
|
0.1.91
|
|
339
343
|
|
|
340
|
-
0.1.
|
|
344
|
+
0.1.93
|
|
345
|
+
|
|
346
|
+
0.1.94
|
package/docs/cli-mcp-parity.7.md
CHANGED
|
@@ -82,7 +82,7 @@ relationship. `identical` means `cw <cmd> --json` is equal to the `cw_<tool>`
|
|
|
82
82
|
payload; `projected` means a declared divergence with a reason; `cli-only` marks
|
|
83
83
|
a surface-specific capability with a recorded reason. The matrix is
|
|
84
84
|
<!-- gen:parity:count -->
|
|
85
|
-
machine-complete by design:
|
|
85
|
+
machine-complete by design: 202 capabilities, 189 MCP tools.
|
|
86
86
|
<!-- /gen:parity:count -->
|
|
87
87
|
|
|
88
88
|
<!-- gen:parity:table -->
|
|
@@ -252,6 +252,7 @@ machine-complete by design: 201 capabilities, 188 MCP tools.
|
|
|
252
252
|
| `run.import` | `cw run import` | `cw_run_import` | `runImportArchive` | both | identical |
|
|
253
253
|
| `run.verify-import` | `cw run verify-import` | `cw_run_verify_import` | `runVerifyImport` | both | identical |
|
|
254
254
|
| `run.inspect-archive` | `cw run inspect-archive` | `cw_run_inspect_archive` | `runInspectArchive` | both | identical |
|
|
255
|
+
| `run.restore` | `cw run restore` | `cw_run_restore` | `runRestoreArchive` | both | identical |
|
|
255
256
|
| `report.verify-bundle` | `cw report verify-bundle` | `cw_report_verify_bundle` | `runVerifyReportBundle` | both | identical |
|
|
256
257
|
| `report.bundle` | `cw report bundle` | `cw_report_bundle` | `reportBundle` | both | identical |
|
|
257
258
|
| `run.drive` | `cw run drive` | `cw_run_drive` | `runDrivePreview` | both | identical |
|
|
@@ -353,6 +354,20 @@ Parity is checked by `scripts/parity-check.js --check`, run by
|
|
|
353
354
|
the registry, lists the live CLI commands and MCP tools, and fails closed on any
|
|
354
355
|
of the rules above.
|
|
355
356
|
|
|
357
|
+
The CLI dispatcher (`src/cli/command-surface.ts`) is being decomposed out of one
|
|
358
|
+
large `switch` into per-command handler modules under `src/cli/handlers/`
|
|
359
|
+
(`handle<Group>(args, runner)`), with `command-surface.ts` left as the thin
|
|
360
|
+
router; shared helpers live in `src/cli/io.ts` (arg/JSON) and `src/cli/format.ts`
|
|
361
|
+
(render). Carved so far: `workbench`, `clones`, `audit`, `worker`, `schedule`, `routine`,
|
|
362
|
+
`sched`, `registry`, `queue`, `history`, `report`, `operator`, `graph`,
|
|
363
|
+
`topology`, `summary`, `multi-agent`, `run`, `approve`, `reject`, `comment`,
|
|
364
|
+
`handoff`, `review`, `blackboard`, `coordinator`, `eval`, `node`, `gc`,
|
|
365
|
+
`telemetry`, `demo`, `feedback`, `metrics`, `migration`, `sandbox`, `backend`,
|
|
366
|
+
`contract`, `candidate`. This is a pure code-move
|
|
367
|
+
— the command surface is unchanged — and the parity scanner reads
|
|
368
|
+
`dist/cli/handlers/*` so a subcommand `case` in a handler module still counts as a
|
|
369
|
+
live CLI token.
|
|
370
|
+
|
|
356
371
|
`test/cli-mcp-parity-smoke.js` proves the contract from end to end. It checks
|
|
357
372
|
registry ⇄ CLI ⇄ MCP coverage (every declared capability is found on its declared
|
|
358
373
|
surfaces and nothing live is undeclared), makes sure `--json` output is equal to
|
|
@@ -529,4 +544,6 @@ CLI golden-path fixes: `cw -q "…"` routes the question (was read as an app id
|
|
|
529
544
|
|
|
530
545
|
0.1.91
|
|
531
546
|
|
|
532
|
-
0.1.
|
|
547
|
+
0.1.93
|
|
548
|
+
|
|
549
|
+
0.1.94
|