cool-workflow 0.1.91 → 0.1.93

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 (84) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/README.md +111 -434
  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/apps/research-synthesis/workflow.js +1 -1
  12. package/dist/capability-core.js +47 -0
  13. package/dist/capability-registry.js +2 -0
  14. package/dist/cli/command-surface.js +165 -1352
  15. package/dist/cli/format.js +56 -0
  16. package/dist/cli/handlers/audit.js +82 -0
  17. package/dist/cli/handlers/blackboard.js +81 -0
  18. package/dist/cli/handlers/candidate.js +40 -0
  19. package/dist/cli/handlers/clones.js +34 -0
  20. package/dist/cli/handlers/collaboration.js +61 -0
  21. package/dist/cli/handlers/eval.js +40 -0
  22. package/dist/cli/handlers/maintenance.js +107 -0
  23. package/dist/cli/handlers/multi-agent.js +165 -0
  24. package/dist/cli/handlers/node.js +41 -0
  25. package/dist/cli/handlers/operational.js +155 -0
  26. package/dist/cli/handlers/operator.js +146 -0
  27. package/dist/cli/handlers/registry.js +68 -0
  28. package/dist/cli/handlers/run.js +153 -0
  29. package/dist/cli/handlers/scheduling.js +126 -0
  30. package/dist/cli/handlers/workbench.js +41 -0
  31. package/dist/cli/handlers/worker.js +45 -0
  32. package/dist/cli/io.js +27 -0
  33. package/dist/cli/run-summary.js +45 -0
  34. package/dist/commit.js +0 -5
  35. package/dist/doctor.js +1 -0
  36. package/dist/execution-backend.js +0 -11
  37. package/dist/mcp/tool-call.js +2 -0
  38. package/dist/mcp/tool-definitions.js +8 -0
  39. package/dist/orchestrator/app-operations.js +205 -0
  40. package/dist/orchestrator/report.js +6 -1
  41. package/dist/orchestrator.js +41 -153
  42. package/dist/state-explosion.js +0 -7
  43. package/dist/term.js +0 -18
  44. package/dist/validation.js +0 -21
  45. package/dist/version.js +1 -1
  46. package/docs/agent-delegation-drive.7.md +2 -0
  47. package/docs/canonical-workflow-apps.7.md +12 -0
  48. package/docs/cli-mcp-parity.7.md +18 -1
  49. package/docs/contract-migration-tooling.7.md +2 -0
  50. package/docs/control-plane-scheduling.7.md +2 -0
  51. package/docs/durable-state-and-locking.7.md +2 -0
  52. package/docs/evidence-adoption-reasoning-chain.7.md +2 -0
  53. package/docs/execution-backends.7.md +2 -0
  54. package/docs/mcp-app-surface.7.md +12 -0
  55. package/docs/multi-agent-cli-mcp-surface.7.md +2 -0
  56. package/docs/multi-agent-eval-replay-harness.7.md +2 -0
  57. package/docs/multi-agent-operator-ux.7.md +2 -0
  58. package/docs/node-snapshot-diff-replay.7.md +2 -0
  59. package/docs/observability-cost-accounting.7.md +2 -0
  60. package/docs/project-index.md +15 -3
  61. package/docs/real-execution-backends.7.md +2 -0
  62. package/docs/release-and-migration.7.md +2 -0
  63. package/docs/release-tooling.7.md +11 -0
  64. package/docs/run-registry-control-plane.7.md +26 -2
  65. package/docs/run-retention-reclamation.7.md +2 -0
  66. package/docs/state-explosion-management.7.md +2 -0
  67. package/docs/team-collaboration.7.md +2 -0
  68. package/docs/web-desktop-workbench.7.md +2 -0
  69. package/manifest/plugin.manifest.json +1 -1
  70. package/package.json +3 -1
  71. package/scripts/agents/agent-adapter-core.js +179 -28
  72. package/scripts/agents/claude-p-agent.js +24 -7
  73. package/scripts/agents/codex-agent.js +1 -1
  74. package/scripts/agents/gemini-agent.js +1 -1
  75. package/scripts/agents/opencode-agent.js +1 -1
  76. package/scripts/architecture-review-fast.js +19 -5
  77. package/scripts/bump-version.js +20 -1
  78. package/scripts/canonical-apps.js +4 -4
  79. package/scripts/dogfood-release.js +1 -1
  80. package/scripts/golden-path.js +4 -4
  81. package/scripts/parity-check.js +9 -1
  82. package/scripts/release-check.js +5 -1
  83. package/scripts/sync-readme.js +123 -0
  84. package/scripts/version-sync-check.js +10 -1
@@ -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
+ }
@@ -25,6 +25,11 @@ function writeReport(run) {
25
25
  (0, dispatch_1.updatePhaseStatuses)(run);
26
26
  const workerSummary = (0, worker_isolation_1.summarizeWorkers)(run);
27
27
  const candidateSummary = (0, candidate_scoring_1.summarizeCandidates)(run);
28
+ // A research run reads a local folder of files, not a code repo — label its source line
29
+ // "Source". Skip the relabel when the run ALSO carries a remote-provenance "- Source: <url>"
30
+ // line below (run.inputs.sourceUrl set by a --link/URL), so a report never shows two
31
+ // "- Source:" lines. Every other app keeps the byte-identical "Repository:" (POLA).
32
+ const sourceLabel = run.workflow.app?.metadata?.domain === "research" && !run.inputs.sourceUrl ? "Source" : "Repository";
28
33
  const report = [
29
34
  `# ${run.workflow.title}`,
30
35
  "",
@@ -38,7 +43,7 @@ function writeReport(run) {
38
43
  : []),
39
44
  `- Created: ${run.createdAt}`,
40
45
  `- Updated: ${run.updatedAt}`,
41
- `- Repository: ${String(run.inputs.repo || run.cwd)}`,
46
+ `- ${sourceLabel}: ${String(run.inputs.repo || run.cwd)}`,
42
47
  // Remote provenance (v0.1.91): when the repo was materialized from a --link/URL, record
43
48
  // the sanitized origin + resolved commit so the report itself says where the code came
44
49
  // from. Conditional — absent for a local-repo run, so existing reports stay byte-identical.
@@ -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.91";
4
+ exports.CURRENT_COOL_WORKFLOW_VERSION = "0.1.93";
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;
@@ -336,3 +336,5 @@ The one-command `cw -q` headline now routes the question and defaults the repo t
336
336
  0.1.90
337
337
 
338
338
  0.1.91
339
+
340
+ 0.1.93
@@ -98,6 +98,11 @@ node scripts/cw.js plan release-cut \
98
98
  Break a research question into claims, look into sources, cross-check
99
99
  the evidence, check the claims, and put together a short answer.
100
100
 
101
+ When you point it at a folder, it reads the local files there as primary
102
+ sources. So this app works over any corpus — your own docs, notes, or
103
+ papers — not only a git code repo. The working directory is the corpus,
104
+ so the agent can back its answer with your own files.
105
+
101
106
  ```bash
102
107
  node scripts/cw.js plan research-synthesis \
103
108
  --cwd /tmp/research-run \
@@ -107,6 +112,13 @@ node scripts/cw.js plan research-synthesis \
107
112
  --freshness "as of today"
108
113
  ```
109
114
 
115
+ Over a local corpus folder, point `--repo` at it:
116
+
117
+ ```bash
118
+ cw quickstart research-synthesis --repo /path/to/papers \
119
+ --question "What do these papers conclude?"
120
+ ```
121
+
110
122
  ## Validation Matrix
111
123
 
112
124
  Run the canonical app matrix from the plugin root directory: