cool-workflow 0.1.84 → 0.1.86

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/README.md +47 -331
  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 +2 -0
  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 +220 -5
  12. package/dist/capability-registry.js +90 -51
  13. package/dist/cli/command-surface.js +47 -2
  14. package/dist/doctor.js +37 -1
  15. package/dist/mcp/tool-call.js +428 -0
  16. package/dist/mcp/tool-definitions.js +1027 -0
  17. package/dist/mcp-surface.js +5 -1416
  18. package/dist/onramp.js +421 -0
  19. package/dist/orchestrator.js +20 -15
  20. package/dist/run-export.js +139 -1
  21. package/dist/telemetry-demo.js +119 -0
  22. package/dist/types/report-bundle.js +6 -0
  23. package/dist/types.js +1 -0
  24. package/dist/version.js +1 -1
  25. package/dist/workbench-host.js +1 -8
  26. package/docs/agent-delegation-drive.7.md +23 -2
  27. package/docs/cli-mcp-parity.7.md +38 -7
  28. package/docs/contract-migration-tooling.7.md +4 -0
  29. package/docs/control-plane-scheduling.7.md +4 -0
  30. package/docs/dogfood-one-real-repo.7.md +9 -1
  31. package/docs/durable-state-and-locking.7.md +4 -0
  32. package/docs/evidence-adoption-reasoning-chain.7.md +4 -0
  33. package/docs/execution-backends.7.md +4 -0
  34. package/docs/getting-started.md +40 -2
  35. package/docs/index.md +51 -37
  36. package/docs/multi-agent-cli-mcp-surface.7.md +4 -0
  37. package/docs/multi-agent-eval-replay-harness.7.md +4 -0
  38. package/docs/multi-agent-operator-ux.7.md +4 -0
  39. package/docs/node-snapshot-diff-replay.7.md +4 -0
  40. package/docs/observability-cost-accounting.7.md +4 -0
  41. package/docs/project-index.md +23 -7
  42. package/docs/real-execution-backends.7.md +4 -0
  43. package/docs/release-and-migration.7.md +5 -1
  44. package/docs/release-history.md +342 -0
  45. package/docs/release-tooling.7.md +18 -0
  46. package/docs/report-verifiable-bundle.7.md +123 -0
  47. package/docs/run-registry-control-plane.7.md +4 -0
  48. package/docs/run-retention-reclamation.7.md +4 -0
  49. package/docs/state-explosion-management.7.md +4 -0
  50. package/docs/team-collaboration.7.md +4 -0
  51. package/docs/web-desktop-workbench.7.md +4 -0
  52. package/manifest/plugin.manifest.json +1 -1
  53. package/manifest/source-context-profiles.json +1 -1
  54. package/package.json +2 -1
  55. package/scripts/architecture-review-fast.js +44 -2
  56. package/scripts/canonical-apps.js +4 -4
  57. package/scripts/dogfood-release.js +55 -133
  58. package/scripts/golden-path.js +4 -4
  59. package/scripts/onramp-check.js +47 -0
  60. package/scripts/parity-check.js +740 -2
  61. package/scripts/release-check.js +2 -0
  62. package/scripts/source-context.js +31 -5
  63. package/scripts/version-sync-check.js +4 -2
package/dist/onramp.js ADDED
@@ -0,0 +1,421 @@
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.optionEnabled = optionEnabled;
7
+ exports.npmCommand = npmCommand;
8
+ exports.nodeSmokeCommand = nodeSmokeCommand;
9
+ exports.detectSourceCheckout = detectSourceCheckout;
10
+ exports.shellQuote = shellQuote;
11
+ exports.buildDoctorOnramp = buildDoctorOnramp;
12
+ exports.resolveChangedFiles = resolveChangedFiles;
13
+ exports.evaluateOnrampContract = evaluateOnrampContract;
14
+ exports.recommendSmokeTests = recommendSmokeTests;
15
+ const node_fs_1 = __importDefault(require("node:fs"));
16
+ const node_path_1 = __importDefault(require("node:path"));
17
+ const node_child_process_1 = require("node:child_process");
18
+ const CURATED_SMOKE_MAP = [
19
+ {
20
+ patterns: ["src/doctor.ts", "src/onramp.ts", "scripts/onramp-check.js"],
21
+ smokes: ["doctor-smoke.js", "onramp-check-smoke.js"]
22
+ },
23
+ {
24
+ patterns: ["README.md", "docs/getting-started.md", "docs/index.md"],
25
+ smokes: ["quickstart-readme-path-smoke.js", "doctor-smoke.js"]
26
+ },
27
+ {
28
+ patterns: ["src/cli.ts", "src/cli/"],
29
+ smokes: ["cli-command-surface-smoke.js", "cli-jsonmode-parity-smoke.js", "cli-mcp-parity-smoke.js"]
30
+ },
31
+ {
32
+ patterns: ["src/orchestrator.ts"],
33
+ smokes: ["cli-mcp-parity-smoke.js"]
34
+ },
35
+ {
36
+ patterns: ["src/capability-registry.ts", "scripts/parity-check.js"],
37
+ smokes: ["cli-mcp-parity-smoke.js", "cli-jsonmode-parity-smoke.js", "parity-doc-sync-smoke.js"]
38
+ },
39
+ {
40
+ patterns: ["src/mcp-server.ts", "src/mcp-surface.ts"],
41
+ smokes: ["mcp-surface-registry-smoke.js", "mcp-app-surface-smoke.js", "cli-mcp-parity-smoke.js"]
42
+ },
43
+ {
44
+ patterns: ["src/run-export.ts", "src/types/run.ts"],
45
+ smokes: ["run-export-import-smoke.js", "run-export-restore-resume-smoke.js", "run-inspect-archive-smoke.js"]
46
+ },
47
+ {
48
+ patterns: ["src/capability-core.ts", "src/drive.ts", "src/agent-config.ts"],
49
+ smokes: ["quickstart-smoke.js", "quickstart-check-smoke.js", "agent-delegation-drive-smoke.js"]
50
+ },
51
+ {
52
+ patterns: ["src/telemetry", "src/worker-accept/telemetry"],
53
+ smokes: ["telemetry-ledger-smoke.js", "telemetry-attestation-smoke.js", "telemetry-verify-signatures-smoke.js"]
54
+ },
55
+ {
56
+ patterns: ["src/workbench", "ui/workbench/"],
57
+ smokes: ["web-desktop-workbench-smoke.js"]
58
+ },
59
+ {
60
+ patterns: ["src/scheduler.ts", "src/scheduling.ts", "src/daemon.ts", "src/triggers.ts"],
61
+ smokes: ["schedule-routine-daemon-smoke.js", "sched-policy-validation-smoke.js"]
62
+ },
63
+ {
64
+ patterns: ["src/multi-agent", "src/topology.ts", "src/coordinator", "src/evidence-reasoning.ts", "src/state-explosion"],
65
+ smokes: [
66
+ "multi-agent-runtime-core-smoke.js",
67
+ "multi-agent-topologies-map-reduce-smoke.js",
68
+ "multi-agent-topologies-debate-smoke.js",
69
+ "multi-agent-topologies-judge-panel-smoke.js",
70
+ "multi-agent-cli-mcp-surface-smoke.js",
71
+ "blackboard-state-explosion-management-smoke.js"
72
+ ]
73
+ }
74
+ ];
75
+ function optionEnabled(value) {
76
+ if (value === undefined || value === false)
77
+ return false;
78
+ if (typeof value === "string" && ["", "0", "false", "no"].includes(value.toLowerCase()))
79
+ return false;
80
+ return true;
81
+ }
82
+ function npmCommand(cwd, script) {
83
+ const source = detectSourceCheckout(cwd);
84
+ const base = `npm run ${script}`;
85
+ return source ? `${source.chdir}${base}` : base;
86
+ }
87
+ function nodeSmokeCommand(cwd, smoke) {
88
+ const source = detectSourceCheckout(cwd);
89
+ const base = `node test/${smoke}`;
90
+ return source ? `${source.chdir}${base}` : base;
91
+ }
92
+ function detectSourceCheckout(cwd) {
93
+ const resolved = node_path_1.default.resolve(cwd);
94
+ const candidates = [resolved, node_path_1.default.join(resolved, "plugins", "cool-workflow")];
95
+ for (const candidate of candidates) {
96
+ try {
97
+ const pkg = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(candidate, "package.json"), "utf8"));
98
+ if (pkg.name === "cool-workflow") {
99
+ return {
100
+ packageDir: candidate,
101
+ chdir: candidate === resolved ? "" : `cd ${shellQuote(node_path_1.default.relative(resolved, candidate) || ".")} && `
102
+ };
103
+ }
104
+ }
105
+ catch {
106
+ /* keep looking */
107
+ }
108
+ }
109
+ return undefined;
110
+ }
111
+ function shellQuote(value) {
112
+ if (/^[A-Za-z0-9_./:-]+$/.test(value))
113
+ return value;
114
+ return `'${value.replace(/'/g, "'\\''")}'`;
115
+ }
116
+ function buildDoctorOnramp(options = {}) {
117
+ const cwd = node_path_1.default.resolve(options.cwd || process.cwd());
118
+ const agentCommand = "--agent-command builtin:claude";
119
+ const onramp = {
120
+ schemaVersion: 1,
121
+ summary: "start small, run the short gate while changing code, then run the full gate before release",
122
+ sections: [
123
+ {
124
+ id: "first-run",
125
+ title: "First Run",
126
+ summary: "Prove the tool, check the setup, then make one report.",
127
+ actions: [
128
+ {
129
+ id: "demo",
130
+ title: "Prove tamper checks",
131
+ command: "cw demo tamper",
132
+ reason: "Shows the core trust check without an agent or a repo."
133
+ },
134
+ {
135
+ id: "setup",
136
+ title: "Check setup",
137
+ command: "cw doctor --onramp",
138
+ reason: "Names local setup trouble before a run is made."
139
+ },
140
+ {
141
+ id: "dry-run",
142
+ title: "Check a real run",
143
+ command: `cw quickstart architecture-review --check --repo /path/to/repo --question "What are the main risks?" ${agentCommand}`,
144
+ reason: "Does no writes and no agent call; it checks inputs first."
145
+ },
146
+ {
147
+ id: "report",
148
+ title: "Make the report",
149
+ command: `cw quickstart architecture-review --repo /path/to/repo --question "What are the main risks?" ${agentCommand}`,
150
+ reason: "Runs the short user path: ask, run, verify, report."
151
+ },
152
+ {
153
+ id: "bundle",
154
+ title: "Make a bundle",
155
+ command: `cw quickstart architecture-review --repo /path/to/repo --question "What are the main risks?" ${agentCommand} --bundle`,
156
+ reason: "Seals a completed report into a portable file the receiver can check offline."
157
+ },
158
+ {
159
+ id: "verify-bundle",
160
+ title: "Check the bundle",
161
+ command: "cw report verify-bundle report.cwrun.json",
162
+ reason: "Checks the report bundle without the source repo or a .cw tree."
163
+ }
164
+ ]
165
+ },
166
+ {
167
+ id: "change-loop",
168
+ title: "Change Loop",
169
+ summary: "Use the small checks while changing code; save the slow full gate for the end.",
170
+ actions: [
171
+ {
172
+ id: "build",
173
+ title: "Type check the change",
174
+ command: npmCommand(cwd, "build"),
175
+ reason: "Fast first check for TypeScript errors."
176
+ },
177
+ {
178
+ id: "target-smoke",
179
+ title: "Run the closest smoke",
180
+ command: nodeSmokeCommand(cwd, "doctor-smoke.js"),
181
+ reason: "Replace this smoke name with the one that covers your changed path."
182
+ },
183
+ {
184
+ id: "fast-suite",
185
+ title: "Run the parallel suite",
186
+ command: npmCommand(cwd, "test:fast"),
187
+ reason: "Runs all smokes with isolated state and parallel workers."
188
+ }
189
+ ]
190
+ },
191
+ {
192
+ id: "surface-guard",
193
+ title: "Surface Guard",
194
+ summary: "Keep the wide runner, CLI, and MCP faces tied to one source.",
195
+ actions: [
196
+ {
197
+ id: "registry",
198
+ title: "Declare each new verb once",
199
+ command: npmCommand(cwd, "parity:check"),
200
+ reason: "Fails if CLI and MCP drift from the capability registry."
201
+ },
202
+ {
203
+ id: "manifest",
204
+ title: "Check generated faces",
205
+ command: npmCommand(cwd, "gen:manifests -- --check"),
206
+ reason: "Fails if plugin manifests drift from source."
207
+ }
208
+ ]
209
+ },
210
+ {
211
+ id: "release-gate",
212
+ title: "Release Gate",
213
+ summary: "Run the full gate only when the batch is ready.",
214
+ actions: [
215
+ {
216
+ id: "release-check",
217
+ title: "Dry-run the release gate",
218
+ command: npmCommand(cwd, "release:check"),
219
+ reason: "Builds, checks docs and generated files, runs the parallel suite, and makes no tag."
220
+ }
221
+ ]
222
+ }
223
+ ]
224
+ };
225
+ if (options.changedFrom) {
226
+ const changed = resolveChangedFiles({ cwd, changedFrom: options.changedFrom, env: options.env });
227
+ const contract = evaluateOnrampContract(changed.files, { cwd });
228
+ onramp.changedFiles = changed;
229
+ onramp.contract = contract;
230
+ onramp.recommendedChecks = {
231
+ summary: contract.recommendedSmokeTests.length
232
+ ? "run the closest smoke tests first, then the fast suite and release gate"
233
+ : "no changed files were found; use the normal short checks",
234
+ smokeTests: contract.recommendedSmokeTests,
235
+ commands: contract.recommendedCommands
236
+ };
237
+ }
238
+ return onramp;
239
+ }
240
+ function resolveChangedFiles(options = {}) {
241
+ const cwd = node_path_1.default.resolve(options.cwd || process.cwd());
242
+ const root = gitRoot(cwd);
243
+ const baseRef = resolveBaseRef(root, options.changedFrom, options.env || process.env);
244
+ const files = new Set();
245
+ for (const file of gitLines(root, ["diff", "--name-only", baseRef, "--"]))
246
+ files.add(normalizeChangedPath(file));
247
+ for (const file of gitLines(root, ["ls-files", "--others", "--exclude-standard"]))
248
+ files.add(normalizeChangedPath(file));
249
+ return { baseRef, files: [...files].filter(Boolean).sort() };
250
+ }
251
+ function evaluateOnrampContract(files, options = {}) {
252
+ const cwd = node_path_1.default.resolve(options.cwd || process.cwd());
253
+ const normalized = [...new Set(files.map((file) => normalizeChangedPath(file)).filter(Boolean))].sort();
254
+ const issues = [];
255
+ const runtimeFiles = normalized.filter(isRuntimeSource);
256
+ const appFiles = normalized.filter((file) => file.startsWith("plugins/cool-workflow/apps/"));
257
+ const typeFiles = normalized.filter((file) => file.startsWith("plugins/cool-workflow/src/types/") && file.endsWith(".ts"));
258
+ const scriptFiles = normalized.filter((file) => file.startsWith("plugins/cool-workflow/scripts/"));
259
+ const surfaceFiles = normalized.filter(isSurfaceFile);
260
+ const smokeFiles = normalized.filter((file) => /^plugins\/cool-workflow\/test\/.+-smoke\.js$/.test(file));
261
+ const docFiles = normalized.filter(isDocFile);
262
+ const iterationFiles = normalized.filter((file) => file === "ITERATION_LOG.md");
263
+ const sourceAppOrScript = runtimeFiles.length > 0 || typeFiles.length > 0 || appFiles.length > 0 || scriptFiles.length > 0;
264
+ if ((runtimeFiles.length > 0 || appFiles.length > 0) && smokeFiles.length === 0) {
265
+ issues.push({
266
+ code: "runtime-smoke-required",
267
+ detail: "Runtime or app changes must include at least one smoke test change.",
268
+ fix: "Add or update a focused test/*-smoke.js file for the changed behavior.",
269
+ files: [...runtimeFiles, ...appFiles]
270
+ });
271
+ }
272
+ if (typeFiles.length > 0 && runtimeFiles.length === 0 && appFiles.length === 0) {
273
+ issues.push({
274
+ code: "types-without-runtime",
275
+ detail: "Type-only source changes are not a valid cycle.",
276
+ fix: "Add the runtime behavior that reads the type, or remove the type-only change.",
277
+ files: typeFiles
278
+ });
279
+ }
280
+ if (surfaceFiles.length > 0 && docFiles.length === 0) {
281
+ issues.push({
282
+ code: "surface-docs-required",
283
+ detail: "CLI, MCP, or capability surface changes must update public docs.",
284
+ fix: "Update README.md or plugins/cool-workflow/docs/*.md with the changed surface.",
285
+ files: surfaceFiles
286
+ });
287
+ }
288
+ if (sourceAppOrScript && iterationFiles.length === 0) {
289
+ issues.push({
290
+ code: "iteration-log-required",
291
+ detail: "Source, app, or script changes must be recorded in ITERATION_LOG.md.",
292
+ fix: "Append one cycle row with goal, files, tests, gate, and tag decision.",
293
+ files: [...runtimeFiles, ...typeFiles, ...appFiles, ...scriptFiles]
294
+ });
295
+ }
296
+ const recommendedSmokeTests = recommendSmokeTests(normalized, cwd);
297
+ return {
298
+ ok: issues.length === 0,
299
+ changedFiles: normalized,
300
+ recommendedSmokeTests,
301
+ recommendedCommands: recommendedCommands(normalized, recommendedSmokeTests, cwd),
302
+ issues
303
+ };
304
+ }
305
+ function recommendSmokeTests(files, cwd = process.cwd()) {
306
+ const normalized = files.map((file) => normalizeChangedPath(file));
307
+ const smokes = new Set();
308
+ const curatedFiles = new Set();
309
+ for (const file of normalized) {
310
+ const pluginPath = stripPluginPrefix(file);
311
+ for (const entry of CURATED_SMOKE_MAP) {
312
+ const matched = entry.patterns.some((pattern) => pluginPath === pattern || pluginPath.startsWith(pattern));
313
+ if (matched) {
314
+ curatedFiles.add(file);
315
+ for (const smoke of entry.smokes)
316
+ smokes.add(smoke);
317
+ }
318
+ }
319
+ }
320
+ const available = availableSmokeTests(cwd);
321
+ for (const file of normalized) {
322
+ if (curatedFiles.has(file))
323
+ continue;
324
+ const pluginPath = stripPluginPrefix(file);
325
+ if (!pluginPath.startsWith("src/"))
326
+ continue;
327
+ const base = node_path_1.default.basename(pluginPath, ".ts");
328
+ const direct = `${base}-smoke.js`;
329
+ if (available.includes(direct))
330
+ smokes.add(direct);
331
+ const tokens = base.split(/[^a-zA-Z0-9]+/).filter((token) => token.length >= 3);
332
+ for (const smoke of available) {
333
+ if (tokens.some((token) => smoke.includes(token)))
334
+ smokes.add(smoke);
335
+ }
336
+ }
337
+ return [...smokes].sort();
338
+ }
339
+ function recommendedCommands(files, smokes, cwd) {
340
+ const commands = new Set();
341
+ commands.add(npmCommand(cwd, "build"));
342
+ for (const smoke of smokes)
343
+ commands.add(nodeSmokeCommand(cwd, smoke));
344
+ commands.add(npmCommand(cwd, "test:fast"));
345
+ if (files.some(isSurfaceFile))
346
+ commands.add(npmCommand(cwd, "parity:check"));
347
+ if (files.some(isSurfaceFile))
348
+ commands.add(npmCommand(cwd, "gen:manifests -- --check"));
349
+ commands.add(npmCommand(cwd, "release:check"));
350
+ return [...commands];
351
+ }
352
+ function resolveBaseRef(root, changedFrom, env) {
353
+ if (changedFrom)
354
+ return verifyRef(root, changedFrom);
355
+ if (env.CW_ONRAMP_BASE)
356
+ return verifyRef(root, env.CW_ONRAMP_BASE);
357
+ const baseBranch = env.GITHUB_BASE_REF ? `origin/${env.GITHUB_BASE_REF}` : "origin/main";
358
+ const mergeBase = gitOne(root, ["merge-base", "HEAD", baseBranch]);
359
+ if (mergeBase)
360
+ return mergeBase;
361
+ return verifyRef(root, "HEAD");
362
+ }
363
+ function verifyRef(root, ref) {
364
+ const resolved = gitOne(root, ["rev-parse", "--verify", `${ref}^{commit}`]);
365
+ if (!resolved)
366
+ throw new Error(`Unknown onramp base ref: ${ref}`);
367
+ return ref;
368
+ }
369
+ function gitRoot(cwd) {
370
+ return gitOne(node_path_1.default.resolve(cwd), ["rev-parse", "--show-toplevel"]) || node_path_1.default.resolve(cwd);
371
+ }
372
+ function gitLines(cwd, args) {
373
+ const result = (0, node_child_process_1.spawnSync)("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
374
+ if (result.status !== 0)
375
+ return [];
376
+ return String(result.stdout || "").split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
377
+ }
378
+ function gitOne(cwd, args) {
379
+ return gitLines(cwd, args)[0] || "";
380
+ }
381
+ function availableSmokeTests(cwd) {
382
+ const source = detectSourceCheckout(cwd);
383
+ const testDir = source ? node_path_1.default.join(source.packageDir, "test") : node_path_1.default.join(node_path_1.default.resolve(cwd), "test");
384
+ try {
385
+ return node_fs_1.default.readdirSync(testDir).filter((file) => file.endsWith("-smoke.js")).sort();
386
+ }
387
+ catch {
388
+ return [];
389
+ }
390
+ }
391
+ function normalizeChangedPath(file) {
392
+ const normalized = file.replace(/\\/g, "/").replace(/^\.\//, "");
393
+ if (/^(src|apps|scripts|test|docs|dist|manifest|ui|workflows)\//.test(normalized)) {
394
+ return `plugins/cool-workflow/${normalized}`;
395
+ }
396
+ return normalized;
397
+ }
398
+ function stripPluginPrefix(file) {
399
+ return file.startsWith("plugins/cool-workflow/") ? file.slice("plugins/cool-workflow/".length) : file;
400
+ }
401
+ function isRuntimeSource(file) {
402
+ return file.startsWith("plugins/cool-workflow/src/") && file.endsWith(".ts") && !file.startsWith("plugins/cool-workflow/src/types/");
403
+ }
404
+ function isSurfaceFile(file) {
405
+ const pluginPath = stripPluginPrefix(file);
406
+ return (pluginPath === "src/cli.ts" ||
407
+ pluginPath.startsWith("src/cli/") ||
408
+ pluginPath === "src/capability-registry.ts" ||
409
+ pluginPath === "src/mcp-server.ts" ||
410
+ pluginPath === "src/mcp-surface.ts" ||
411
+ pluginPath === "src/orchestrator.ts" ||
412
+ pluginPath === "scripts/parity-check.js");
413
+ }
414
+ function isDocFile(file) {
415
+ return (file === "README.md" ||
416
+ file === "CHANGELOG.md" ||
417
+ file === "RELEASE.md" ||
418
+ file.startsWith("docs/") ||
419
+ file === "plugins/cool-workflow/README.md" ||
420
+ file.startsWith("plugins/cool-workflow/docs/"));
421
+ }
@@ -178,7 +178,7 @@ class CoolWorkflowRunner {
178
178
  return {
179
179
  valid: false,
180
180
  appId: target,
181
- appPath: node_path_1.default.resolve(target),
181
+ appPath: this.resolveFromBase(target),
182
182
  issues
183
183
  };
184
184
  }
@@ -188,7 +188,7 @@ class CoolWorkflowRunner {
188
188
  if (!id)
189
189
  throw new Error("App id must include at least one letter or digit");
190
190
  const title = String(options.title || titleize(id));
191
- const destinationDir = node_path_1.default.resolve(String(options.directory || options.output || node_path_1.default.join(this.appsDir, id)));
191
+ const destinationDir = this.resolveFromBase(String(options.directory || options.output || node_path_1.default.join(this.appsDir, id)));
192
192
  const manifestPath = node_path_1.default.join(destinationDir, "app.json");
193
193
  const entrypointPath = node_path_1.default.join(destinationDir, "workflow.js");
194
194
  if (!options.force && (node_fs_1.default.existsSync(manifestPath) || node_fs_1.default.existsSync(entrypointPath))) {
@@ -205,8 +205,7 @@ class CoolWorkflowRunner {
205
205
  }
206
206
  packageApp(appId, options = {}) {
207
207
  const record = this.loadWorkflowAppById(appId);
208
- const destination = node_path_1.default.resolve(String(options.output ||
209
- node_path_1.default.join(process.cwd(), ".cw", "packages", `${record.app.id}-${record.app.version}.cwapp.json`)));
208
+ const destination = this.resolveFromBase(String(options.output || node_path_1.default.join(".cw", "packages", `${record.app.id}-${record.app.version}.cwapp.json`)));
210
209
  node_fs_1.default.mkdirSync(node_path_1.default.dirname(destination), { recursive: true });
211
210
  (0, state_1.writeJson)(destination, {
212
211
  schemaVersion: 1,
@@ -221,7 +220,7 @@ class CoolWorkflowRunner {
221
220
  if (!id)
222
221
  throw new Error("Workflow id must include at least one letter or digit");
223
222
  const title = String(options.title || titleize(id));
224
- const destination = node_path_1.default.resolve(String(options.output || node_path_1.default.join(this.workflowsDir, `${id}.workflow.js`)));
223
+ const destination = this.resolveFromBase(String(options.output || node_path_1.default.join(this.workflowsDir, `${id}.workflow.js`)));
225
224
  if (node_fs_1.default.existsSync(destination) && !options.force) {
226
225
  throw new Error(`Refusing to overwrite existing workflow: ${destination}`);
227
226
  }
@@ -248,7 +247,7 @@ class CoolWorkflowRunner {
248
247
  return lifecycleOps.dispatch(this.loadRun(runId), options);
249
248
  }
250
249
  recordResult(runId, taskId, resultPath, options = {}) {
251
- return lifecycleOps.recordResult(this.loadRun(runId), taskId, resultPath, options);
250
+ return lifecycleOps.recordResult(this.loadRun(runId), taskId, this.resolveFromBase(resultPath), options);
252
251
  }
253
252
  listWorkers(runId, options = {}) {
254
253
  return (0, worker_isolation_1.listWorkerScopes)(this.loadRun(runId), {
@@ -272,13 +271,13 @@ class CoolWorkflowRunner {
272
271
  return (0, worker_isolation_1.writeWorkerManifest)(run, worker);
273
272
  }
274
273
  recordWorkerOutput(runId, workerId, resultPath, options = {}) {
275
- return lifecycleOps.recordWorkerOutput(this.loadRun(runId), workerId, resultPath, options);
274
+ return lifecycleOps.recordWorkerOutput(this.loadRun(runId), workerId, this.resolveFromBase(resultPath), options);
276
275
  }
277
276
  recordWorkerFailure(runId, workerId, message, options = {}) {
278
277
  return lifecycleOps.recordWorkerFailure(this.loadRun(runId), workerId, message, options);
279
278
  }
280
279
  validateWorker(runId, workerId, targetPath) {
281
- return (0, worker_isolation_1.validateWorkerBoundary)(this.loadRun(runId), workerId, targetPath ? { path: targetPath } : {});
280
+ return (0, worker_isolation_1.validateWorkerBoundary)(this.loadRun(runId), workerId, targetPath ? { path: this.resolveFromBase(targetPath) } : {});
282
281
  }
283
282
  // Audit domain — delegated to ./orchestrator/audit-operations (v0.1.40 P3
284
283
  // router pattern). The runner stays the routing surface; the logic lives in the
@@ -314,13 +313,13 @@ class CoolWorkflowRunner {
314
313
  return auditOps.recordAuditDecision(this.loadRun(runId), workerId, options);
315
314
  }
316
315
  listSandboxProfiles(options = {}) {
317
- return (0, sandbox_profile_1.listBundledSandboxProfiles)((0, sandbox_profile_1.sandboxContextForValidation)(String(options.cwd || process.cwd())));
316
+ return (0, sandbox_profile_1.listBundledSandboxProfiles)((0, sandbox_profile_1.sandboxContextForValidation)(String(options.cwd || this.invocationCwd())));
318
317
  }
319
318
  showSandboxProfile(profileId, options = {}) {
320
- return (0, sandbox_profile_1.showBundledSandboxProfile)(profileId, (0, sandbox_profile_1.sandboxContextForValidation)(String(options.cwd || process.cwd())));
319
+ return (0, sandbox_profile_1.showBundledSandboxProfile)(profileId, (0, sandbox_profile_1.sandboxContextForValidation)(String(options.cwd || this.invocationCwd())));
321
320
  }
322
321
  validateSandboxProfile(profileFile, options = {}) {
323
- return (0, sandbox_profile_1.validateSandboxProfileFile)(profileFile, (0, sandbox_profile_1.sandboxContextForValidation)(String(options.cwd || process.cwd())));
322
+ return (0, sandbox_profile_1.validateSandboxProfileFile)(this.resolveFromBase(profileFile), (0, sandbox_profile_1.sandboxContextForValidation)(String(options.cwd || this.invocationCwd())));
324
323
  }
325
324
  listBackends(options = {}) {
326
325
  void options;
@@ -331,7 +330,7 @@ class CoolWorkflowRunner {
331
330
  return (0, execution_backend_1.backendShowPayload)(backendId);
332
331
  }
333
332
  probeBackend(backendId, options = {}) {
334
- return (0, execution_backend_1.backendProbePayload)(backendId, { cwd: String(options.cwd || process.cwd()) });
333
+ return (0, execution_backend_1.backendProbePayload)(backendId, { cwd: String(options.cwd || this.invocationCwd()) });
335
334
  }
336
335
  // Candidate domain — delegated to ./orchestrator/candidate-operations.
337
336
  listCandidates(runId, options = {}) {
@@ -716,6 +715,12 @@ class CoolWorkflowRunner {
716
715
  loadRun(runId) {
717
716
  return (0, state_1.loadRunFromCwd)(runId, this.baseDir);
718
717
  }
718
+ invocationCwd() {
719
+ return this.baseDir || process.cwd();
720
+ }
721
+ resolveFromBase(target) {
722
+ return node_path_1.default.resolve(this.invocationCwd(), target);
723
+ }
719
724
  loadWorkflowAppById(appId) {
720
725
  const record = this.loadWorkflowApps().find((candidate) => candidate.app.id === appId);
721
726
  if (!record)
@@ -725,7 +730,7 @@ class CoolWorkflowRunner {
725
730
  loadWorkflowAppTarget(target) {
726
731
  if (!target)
727
732
  throw new Error("Missing workflow app path or id");
728
- const resolved = node_path_1.default.resolve(target);
733
+ const resolved = this.resolveFromBase(target);
729
734
  if (node_fs_1.default.existsSync(resolved)) {
730
735
  const stat = node_fs_1.default.statSync(resolved);
731
736
  if (stat.isDirectory())
@@ -821,9 +826,9 @@ function formatHelp() {
821
826
  "",
822
827
  "Commands:",
823
828
  " list",
824
- " doctor [--json] (check your setup: Node, agent backend, git, writable state)",
829
+ " doctor [--json] [--onramp] [--changed-from REF] (check setup and show the shortest safe next steps)",
825
830
  " init <workflow-id> [--title TEXT] [--output PATH]",
826
- " quickstart [app-id] [--repo PATH] [--question TEXT] [--agent-command CMD] [--once] [--preview]",
831
+ " quickstart [app-id] [--repo PATH] [--question TEXT] [--agent-command CMD] [--check] [--once] [--preview]",
827
832
  " plan <workflow-id> [--repo PATH] [--question TEXT] [--invariant TEXT]",
828
833
  " status <run-id> [--json|--format json]",
829
834
  " next <run-id> [--limit N]",