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.
Files changed (81) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/README.md +104 -129
  4. package/apps/architecture-review/app.json +1 -1
  5. package/apps/architecture-review-fast/app.json +1 -1
  6. package/apps/architecture-review-fast/workflow.js +15 -2
  7. package/apps/end-to-end-golden-path/app.json +1 -1
  8. package/apps/pr-review-fix-ci/app.json +1 -1
  9. package/apps/release-cut/app.json +1 -1
  10. package/apps/research-synthesis/app.json +1 -1
  11. package/dist/capability-core.js +47 -0
  12. package/dist/capability-registry.js +2 -0
  13. package/dist/cli/command-surface.js +165 -1352
  14. package/dist/cli/format.js +56 -0
  15. package/dist/cli/handlers/audit.js +82 -0
  16. package/dist/cli/handlers/blackboard.js +81 -0
  17. package/dist/cli/handlers/candidate.js +40 -0
  18. package/dist/cli/handlers/clones.js +34 -0
  19. package/dist/cli/handlers/collaboration.js +61 -0
  20. package/dist/cli/handlers/eval.js +40 -0
  21. package/dist/cli/handlers/maintenance.js +107 -0
  22. package/dist/cli/handlers/multi-agent.js +165 -0
  23. package/dist/cli/handlers/node.js +41 -0
  24. package/dist/cli/handlers/operational.js +155 -0
  25. package/dist/cli/handlers/operator.js +146 -0
  26. package/dist/cli/handlers/registry.js +68 -0
  27. package/dist/cli/handlers/run.js +153 -0
  28. package/dist/cli/handlers/scheduling.js +126 -0
  29. package/dist/cli/handlers/workbench.js +41 -0
  30. package/dist/cli/handlers/worker.js +45 -0
  31. package/dist/cli/io.js +27 -0
  32. package/dist/cli/run-summary.js +45 -0
  33. package/dist/commit.js +0 -5
  34. package/dist/execution-backend.js +0 -11
  35. package/dist/mcp/tool-call.js +2 -0
  36. package/dist/mcp/tool-definitions.js +8 -0
  37. package/dist/orchestrator/app-operations.js +205 -0
  38. package/dist/orchestrator.js +41 -153
  39. package/dist/state-explosion.js +0 -7
  40. package/dist/term.js +0 -18
  41. package/dist/validation.js +0 -21
  42. package/dist/version.js +1 -1
  43. package/docs/agent-delegation-drive.7.md +13 -7
  44. package/docs/cli-mcp-parity.7.md +19 -2
  45. package/docs/contract-migration-tooling.7.md +3 -1
  46. package/docs/control-plane-scheduling.7.md +3 -1
  47. package/docs/durable-state-and-locking.7.md +3 -1
  48. package/docs/evidence-adoption-reasoning-chain.7.md +3 -1
  49. package/docs/execution-backends.7.md +3 -1
  50. package/docs/mcp-app-surface.7.md +12 -0
  51. package/docs/multi-agent-cli-mcp-surface.7.md +3 -1
  52. package/docs/multi-agent-eval-replay-harness.7.md +3 -1
  53. package/docs/multi-agent-operator-ux.7.md +3 -1
  54. package/docs/node-snapshot-diff-replay.7.md +3 -1
  55. package/docs/observability-cost-accounting.7.md +3 -1
  56. package/docs/project-index.md +16 -3
  57. package/docs/real-execution-backends.7.md +3 -1
  58. package/docs/release-and-migration.7.md +3 -1
  59. package/docs/release-tooling.7.md +3 -1
  60. package/docs/run-registry-control-plane.7.md +27 -3
  61. package/docs/run-retention-reclamation.7.md +3 -1
  62. package/docs/state-explosion-management.7.md +3 -1
  63. package/docs/team-collaboration.7.md +3 -1
  64. package/docs/web-desktop-workbench.7.md +3 -1
  65. package/manifest/plugin.manifest.json +1 -1
  66. package/package.json +1 -1
  67. package/scripts/agents/builtin-templates.json +3 -2
  68. package/scripts/agents/claude-p-agent.js +6 -6
  69. package/scripts/agents/codex-agent.js +17 -3
  70. package/scripts/agents/deepseek-agent.js +23 -0
  71. package/scripts/agents/gemini-opencode-agent.js +25 -0
  72. package/scripts/agents/opencode-agent.js +57 -8
  73. package/scripts/architecture-review-fast.js +19 -5
  74. package/scripts/bump-version.js +16 -0
  75. package/scripts/canonical-apps.js +4 -4
  76. package/scripts/dogfood-release.js +1 -1
  77. package/scripts/golden-path.js +4 -4
  78. package/scripts/parity-check.js +9 -1
  79. package/scripts/release-flow.js +35 -0
  80. package/scripts/vendor-preflight.js +127 -0
  81. 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
+ }
@@ -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 this.loadWorkflowApps().map((record) => {
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 this.loadWorkflowApps().map((record) => (0, workflow_app_framework_1.summarizeWorkflowApp)(record));
123
+ return appOps.listApps(this.workflowsDir, this.appsDir);
129
124
  }
130
125
  showApp(appId) {
131
- const record = this.loadWorkflowAppById(appId);
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
- try {
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
- const id = (0, workflow_api_1.slugify)(appId);
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
- const record = this.loadWorkflowAppById(appId);
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
- const record = this.loadWorkflowApps().find((candidate) => candidate.app.id === appId);
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) {
@@ -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
@@ -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.92";
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
- Gemini, OpenCode, DeepSeek, and GLM stay as external agent commands or HTTP
247
- endpoints until their stream format is proven by a local, deterministic wrapper
248
- smoke. DeepSeek and GLM are best reached through OpenCode or an HTTP agent
249
- adapter first; CW still imports no model SDK.
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.92
344
+ 0.1.93
345
+
346
+ 0.1.94
@@ -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: 201 capabilities, 188 MCP tools.
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.92
547
+ 0.1.93
548
+
549
+ 0.1.94
@@ -162,4 +162,6 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
162
162
 
163
163
  0.1.91
164
164
 
165
- 0.1.92
165
+ 0.1.93
166
+
167
+ 0.1.94