cool-workflow 0.2.3 → 0.2.4

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 (65) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/apps/architecture-review/app.json +1 -1
  4. package/apps/architecture-review-fast/app.json +1 -1
  5. package/apps/end-to-end-golden-path/app.json +1 -1
  6. package/apps/pr-review-fix-ci/app.json +1 -1
  7. package/apps/release-cut/app.json +1 -1
  8. package/apps/research-synthesis/app.json +1 -1
  9. package/dist/cli/dispatch.js +2 -1
  10. package/dist/cli/io.js +6 -20
  11. package/dist/core/capability-data.js +2 -2
  12. package/dist/core/format/help.js +7 -1
  13. package/dist/core/util/cli-args.js +33 -0
  14. package/dist/core/version.js +1 -1
  15. package/dist/shell/drive.js +14 -6
  16. package/dist/shell/execution-backend/agent.js +10 -1
  17. package/dist/shell/execution-backend/local.js +15 -10
  18. package/dist/shell/pipeline-cli.js +20 -2
  19. package/dist/shell/reclamation-io.js +74 -6
  20. package/dist/shell/registry-cli.js +4 -0
  21. package/dist/shell/run-registry-io.js +1 -0
  22. package/dist/shell/workflow-app-loader.js +67 -1
  23. package/dist/wiring/capability-table/basics.js +2 -2
  24. package/dist/wiring/capability-table/exec-backend.js +9 -9
  25. package/dist/wiring/capability-table/multi-agent.js +69 -69
  26. package/dist/wiring/capability-table/pipeline.js +28 -28
  27. package/dist/wiring/capability-table/registry-core.js +3 -3
  28. package/dist/wiring/capability-table/reporting.js +38 -38
  29. package/dist/wiring/capability-table/scheduling-registry.js +58 -58
  30. package/dist/wiring/capability-table/state.js +34 -34
  31. package/dist/wiring/capability-table/trust-ledger.js +12 -12
  32. package/dist/wiring/capability-table/workflow-apps.js +15 -15
  33. package/docs/agent-delegation-drive.7.md +2 -0
  34. package/docs/cli-mcp-parity.7.md +18 -13
  35. package/docs/contract-migration-tooling.7.md +2 -0
  36. package/docs/control-plane-scheduling.7.md +2 -0
  37. package/docs/durable-state-and-locking.7.md +2 -0
  38. package/docs/evidence-adoption-reasoning-chain.7.md +2 -0
  39. package/docs/execution-backends.7.md +2 -0
  40. package/docs/multi-agent-cli-mcp-surface.7.md +2 -0
  41. package/docs/multi-agent-eval-replay-harness.7.md +2 -0
  42. package/docs/multi-agent-operator-ux.7.md +2 -0
  43. package/docs/node-snapshot-diff-replay.7.md +2 -0
  44. package/docs/observability-cost-accounting.7.md +2 -0
  45. package/docs/project-index.md +8 -4
  46. package/docs/real-execution-backends.7.md +2 -0
  47. package/docs/release-and-migration.7.md +2 -0
  48. package/docs/release-tooling.7.md +20 -0
  49. package/docs/run-registry-control-plane.7.md +2 -0
  50. package/docs/run-retention-reclamation.7.md +22 -3
  51. package/docs/state-explosion-management.7.md +2 -0
  52. package/docs/team-collaboration.7.md +2 -0
  53. package/docs/trust-audit-anchor.7.md +2 -0
  54. package/docs/web-desktop-workbench.7.md +2 -0
  55. package/manifest/plugin.manifest.json +1 -1
  56. package/package.json +2 -1
  57. package/scripts/block-unapproved-tag.sh +19 -4
  58. package/scripts/bump-version.js +27 -10
  59. package/scripts/canonical-apps.js +4 -4
  60. package/scripts/dogfood-release.js +1 -1
  61. package/scripts/golden-path.js +4 -4
  62. package/scripts/purity-baseline.json +0 -30
  63. package/scripts/release-flow.js +137 -12
  64. package/scripts/release-oneclick.js +407 -0
  65. package/scripts/version-sync-check.js +39 -22
@@ -6,7 +6,7 @@
6
6
  // retyped).
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
8
  const registry_core_1 = require("./registry-core");
9
- const io_1 = require("../../cli/io");
9
+ const cli_args_1 = require("../../core/util/cli-args");
10
10
  // MILESTONE 8 (ledger, telemetry, trust-audit, tamper/bundle demos) CLI
11
11
  // bindings: ledger propose|review|verify|apply|list, telemetry verify,
12
12
  // audit verify, demo tamper|bundle, report bundle|verify-bundle. Handler
@@ -80,41 +80,41 @@ registry_core_1.REGISTRY_BY_CAPABILITY.get("ledger.list").mcp.handler = (args) =
80
80
  path: ["telemetry", "verify"],
81
81
  jsonMode: "flag",
82
82
  handler: (args) => {
83
- const runId = (0, io_1.required)((0, io_1.optionalArg)(args.positionals[0]) || (0, io_1.optionalArg)(args.options.runId) || (0, io_1.optionalArg)(args.options.run), "run id");
83
+ const runId = (0, cli_args_1.required)((0, cli_args_1.optionalArg)(args.positionals[0]) || (0, cli_args_1.optionalArg)(args.options.runId) || (0, cli_args_1.optionalArg)(args.options.run), "run id");
84
84
  const result = loadTelemetryCli().telemetryVerifyCli(runId, args.options);
85
85
  return { json: result, text: loadTelemetryDemo().formatTelemetryVerify(result), exitCode: result.verified ? undefined : 1 };
86
86
  },
87
87
  });
88
- registry_core_1.REGISTRY_BY_CAPABILITY.get("telemetry.verify").mcp.handler = (args) => loadTelemetryCli().telemetryVerifyCli((0, io_1.required)((0, io_1.optionalArg)(args.runId), "run id"), args);
88
+ registry_core_1.REGISTRY_BY_CAPABILITY.get("telemetry.verify").mcp.handler = (args) => loadTelemetryCli().telemetryVerifyCli((0, cli_args_1.required)((0, cli_args_1.optionalArg)(args.runId), "run id"), args);
89
89
  (0, registry_core_1.attachCliBinding)("audit.verify", {
90
90
  path: ["audit", "verify"],
91
91
  jsonMode: "default",
92
92
  handler: (args) => {
93
- const runId = (0, io_1.required)((0, io_1.optionalArg)(args.positionals[0]), "run id");
93
+ const runId = (0, cli_args_1.required)((0, cli_args_1.optionalArg)(args.positionals[0]), "run id");
94
94
  const result = loadAuditCli().auditVerifyCli(runId, args.options);
95
95
  return { json: result, exitCode: result.verified ? undefined : 1 };
96
96
  },
97
97
  });
98
- registry_core_1.REGISTRY_BY_CAPABILITY.get("audit.verify").mcp.handler = (args) => loadAuditCli().auditVerifyCli((0, io_1.required)((0, io_1.optionalArg)(args.runId), "run id"), args);
98
+ registry_core_1.REGISTRY_BY_CAPABILITY.get("audit.verify").mcp.handler = (args) => loadAuditCli().auditVerifyCli((0, cli_args_1.required)((0, cli_args_1.optionalArg)(args.runId), "run id"), args);
99
99
  (0, registry_core_1.attachCliBinding)("audit.head", {
100
100
  path: ["audit", "head"],
101
101
  jsonMode: "default",
102
102
  handler: (args) => {
103
- const runId = (0, io_1.required)((0, io_1.optionalArg)(args.positionals[0]), "run id");
103
+ const runId = (0, cli_args_1.required)((0, cli_args_1.optionalArg)(args.positionals[0]), "run id");
104
104
  return { json: loadAuditCli().auditHeadCli(runId, args.options) };
105
105
  },
106
106
  });
107
- registry_core_1.REGISTRY_BY_CAPABILITY.get("audit.head").mcp.handler = (args) => loadAuditCli().auditHeadCli((0, io_1.required)((0, io_1.optionalArg)(args.runId), "run id"), args);
107
+ registry_core_1.REGISTRY_BY_CAPABILITY.get("audit.head").mcp.handler = (args) => loadAuditCli().auditHeadCli((0, cli_args_1.required)((0, cli_args_1.optionalArg)(args.runId), "run id"), args);
108
108
  (0, registry_core_1.attachCliBinding)("audit.repair", {
109
109
  path: ["audit", "repair"],
110
110
  jsonMode: "default",
111
111
  handler: (args) => {
112
- const runId = (0, io_1.required)((0, io_1.optionalArg)(args.positionals[0]), "run id");
112
+ const runId = (0, cli_args_1.required)((0, cli_args_1.optionalArg)(args.positionals[0]), "run id");
113
113
  const result = loadAuditCli().auditRepairCli(runId, args.options);
114
114
  return { json: result, exitCode: result.outcome === "refused" ? 1 : undefined };
115
115
  },
116
116
  });
117
- registry_core_1.REGISTRY_BY_CAPABILITY.get("audit.repair").mcp.handler = (args) => loadAuditCli().auditRepairCli((0, io_1.required)((0, io_1.optionalArg)(args.runId), "run id"), args);
117
+ registry_core_1.REGISTRY_BY_CAPABILITY.get("audit.repair").mcp.handler = (args) => loadAuditCli().auditRepairCli((0, cli_args_1.required)((0, cli_args_1.optionalArg)(args.runId), "run id"), args);
118
118
  (0, registry_core_1.addCliOnlyCapability)("demo.tamper", "Prove tamper-evidence: build a signed telemetry ledger, forge it, watch verification fail offline.", {
119
119
  path: ["demo", "tamper"],
120
120
  jsonMode: "flag",
@@ -135,17 +135,17 @@ registry_core_1.REGISTRY_BY_CAPABILITY.get("audit.repair").mcp.handler = (args)
135
135
  path: ["report", "bundle"],
136
136
  jsonMode: "default",
137
137
  handler: (args) => {
138
- const runId = (0, io_1.required)((0, io_1.optionalArg)(args.positionals[0]), "run id");
138
+ const runId = (0, cli_args_1.required)((0, cli_args_1.optionalArg)(args.positionals[0]), "run id");
139
139
  const result = loadReportCli().reportBundleCli(runId, args.options);
140
140
  return { json: result, exitCode: result.ok ? undefined : 1 };
141
141
  },
142
142
  });
143
- registry_core_1.REGISTRY_BY_CAPABILITY.get("report.bundle").mcp.handler = (args) => loadReportCli().reportBundleCli((0, io_1.required)((0, io_1.optionalArg)(args.runId), "run id"), args);
143
+ registry_core_1.REGISTRY_BY_CAPABILITY.get("report.bundle").mcp.handler = (args) => loadReportCli().reportBundleCli((0, cli_args_1.required)((0, cli_args_1.optionalArg)(args.runId), "run id"), args);
144
144
  (0, registry_core_1.attachCliBinding)("report.verify-bundle", {
145
145
  path: ["report", "verify-bundle"],
146
146
  jsonMode: "flag",
147
147
  handler: (args) => {
148
- const archivePath = (0, io_1.required)((0, io_1.optionalArg)(args.positionals[0]), "bundle path");
148
+ const archivePath = (0, cli_args_1.required)((0, cli_args_1.optionalArg)(args.positionals[0]), "bundle path");
149
149
  const reportCli = loadReportCli();
150
150
  const result = reportCli.reportVerifyBundleCli({ ...args.options, archive: archivePath });
151
151
  return { json: result, text: reportCli.formatReportVerifyBundle(result), exitCode: result.ok ? undefined : 1 };
@@ -6,7 +6,7 @@
6
6
  // sed, not retyped).
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
8
  const registry_core_1 = require("./registry-core");
9
- const io_1 = require("../../cli/io");
9
+ const cli_args_1 = require("../../core/util/cli-args");
10
10
  // This whole module is required unconditionally at startup for EVERY
11
11
  // command (see wiring/capability-table/index.ts) — loading `next`'s and
12
12
  // `app.run`'s shell modules lazily, inside the handler that actually
@@ -44,24 +44,24 @@ registry_core_1.REGISTRY_BY_CAPABILITY.get("app.list").mcp.handler = () => loadW
44
44
  (0, registry_core_1.attachCliBinding)("app.show", {
45
45
  path: ["app", "show"],
46
46
  jsonMode: "default",
47
- handler: (args) => ({ json: loadWorkflowAppLoader().showWorkflowApp((0, io_1.required)(args.positionals[0], "workflow app id")) }),
47
+ handler: (args) => ({ json: loadWorkflowAppLoader().showWorkflowApp((0, cli_args_1.required)(args.positionals[0], "workflow app id")) }),
48
48
  });
49
- registry_core_1.REGISTRY_BY_CAPABILITY.get("app.show").mcp.handler = (args) => loadWorkflowAppLoader().showWorkflowApp((0, io_1.required)((0, io_1.optionalArg)(args.appId), "workflow app id"));
49
+ registry_core_1.REGISTRY_BY_CAPABILITY.get("app.show").mcp.handler = (args) => loadWorkflowAppLoader().showWorkflowApp((0, cli_args_1.required)((0, cli_args_1.optionalArg)(args.appId), "workflow app id"));
50
50
  (0, registry_core_1.attachCliBinding)("app.validate", {
51
51
  path: ["app", "validate"],
52
52
  jsonMode: "default",
53
53
  handler: (args) => {
54
- const result = loadWorkflowAppLoader().validateWorkflowAppTarget((0, io_1.required)(args.positionals[0], "workflow app path or id"));
54
+ const result = loadWorkflowAppLoader().validateWorkflowAppTarget((0, cli_args_1.required)(args.positionals[0], "workflow app path or id"));
55
55
  return { json: result, exitCode: result.valid ? undefined : 1 };
56
56
  },
57
57
  });
58
- registry_core_1.REGISTRY_BY_CAPABILITY.get("app.validate").mcp.handler = (args) => loadWorkflowAppLoader().validateWorkflowAppTarget((0, io_1.required)((0, io_1.optionalArg)(args.target ?? args.appId), "workflow app path or id"));
58
+ registry_core_1.REGISTRY_BY_CAPABILITY.get("app.validate").mcp.handler = (args) => loadWorkflowAppLoader().validateWorkflowAppTarget((0, cli_args_1.required)((0, cli_args_1.optionalArg)(args.target ?? args.appId), "workflow app path or id"));
59
59
  (0, registry_core_1.attachCliBinding)("app.init", {
60
60
  path: ["app", "init"],
61
61
  jsonMode: "default",
62
- handler: (args) => ({ json: loadWorkflowAppLoader().initWorkflowApp((0, io_1.required)(args.positionals[0], "app id"), args.options) }),
62
+ handler: (args) => ({ json: loadWorkflowAppLoader().initWorkflowApp((0, cli_args_1.required)(args.positionals[0], "app id"), args.options) }),
63
63
  });
64
- registry_core_1.REGISTRY_BY_CAPABILITY.get("app.init").mcp.handler = (args) => loadWorkflowAppLoader().initWorkflowApp((0, io_1.required)((0, io_1.optionalArg)(args.appId), "app id"), args);
64
+ registry_core_1.REGISTRY_BY_CAPABILITY.get("app.init").mcp.handler = (args) => loadWorkflowAppLoader().initWorkflowApp((0, cli_args_1.required)((0, cli_args_1.optionalArg)(args.appId), "app id"), args);
65
65
  // `cw init <id>` — the standalone scaffold verb. v2 folds `init` into
66
66
  // `app.init` (the old build's legacy `.workflow.js` scaffold is gone), so
67
67
  // both surfaces route through initWorkflowApp, same as `cw app init`. The
@@ -71,15 +71,15 @@ registry_core_1.REGISTRY_BY_CAPABILITY.get("app.init").mcp.handler = (args) => l
71
71
  (0, registry_core_1.attachCliBinding)("init", {
72
72
  path: ["init"],
73
73
  jsonMode: "default",
74
- handler: (args) => ({ json: loadWorkflowAppLoader().initWorkflowApp((0, io_1.required)((0, io_1.optionalArg)(args.positionals[0]), "workflow id"), args.options) }),
74
+ handler: (args) => ({ json: loadWorkflowAppLoader().initWorkflowApp((0, cli_args_1.required)((0, cli_args_1.optionalArg)(args.positionals[0]), "workflow id"), args.options) }),
75
75
  });
76
- registry_core_1.REGISTRY_BY_CAPABILITY.get("init").mcp.handler = (args) => loadWorkflowAppLoader().initWorkflowApp((0, io_1.required)((0, io_1.optionalArg)(args.workflowId ?? args.appId), "workflow id"), args);
76
+ registry_core_1.REGISTRY_BY_CAPABILITY.get("init").mcp.handler = (args) => loadWorkflowAppLoader().initWorkflowApp((0, cli_args_1.required)((0, cli_args_1.optionalArg)(args.workflowId ?? args.appId), "workflow id"), args);
77
77
  (0, registry_core_1.attachCliBinding)("app.package", {
78
78
  path: ["app", "package"],
79
79
  jsonMode: "default",
80
- handler: (args) => ({ json: loadWorkflowAppLoader().packageWorkflowApp((0, io_1.required)(args.positionals[0], "app id"), args.options) }),
80
+ handler: (args) => ({ json: loadWorkflowAppLoader().packageWorkflowApp((0, cli_args_1.required)(args.positionals[0], "app id"), args.options) }),
81
81
  });
82
- registry_core_1.REGISTRY_BY_CAPABILITY.get("app.package").mcp.handler = (args) => loadWorkflowAppLoader().packageWorkflowApp((0, io_1.required)((0, io_1.optionalArg)(args.appId), "app id"), args);
82
+ registry_core_1.REGISTRY_BY_CAPABILITY.get("app.package").mcp.handler = (args) => loadWorkflowAppLoader().packageWorkflowApp((0, cli_args_1.required)((0, cli_args_1.optionalArg)(args.appId), "app id"), args);
83
83
  // `cw app run <app-id>` — plan+drive+report an app in one call. 2-token
84
84
  // cli.path found before the ["app"] usage catch-all; `appRunCli` reads the
85
85
  // app id from `appId`, so the first positional after "run" is forwarded as
@@ -87,7 +87,7 @@ registry_core_1.REGISTRY_BY_CAPABILITY.get("app.package").mcp.handler = (args) =
87
87
  (0, registry_core_1.attachCliBinding)("app.run", {
88
88
  path: ["app", "run"],
89
89
  jsonMode: "default",
90
- handler: (args) => ({ json: loadAppRunCli().appRunCli({ ...args.options, appId: (0, io_1.required)(args.positionals[0], "app id") }) }),
90
+ handler: (args) => ({ json: loadAppRunCli().appRunCli({ ...args.options, appId: (0, cli_args_1.required)(args.positionals[0], "app id") }) }),
91
91
  });
92
92
  // A 1-token `["app"]` row that exists ONLY to own the fixed usage string
93
93
  // for an unrecognized `app` subcommand (`app run` is not yet CLI-wired at
@@ -329,7 +329,7 @@ registry_core_1.REGISTRY_BY_CAPABILITY.get("app.package").mcp.handler = (args) =
329
329
  path: ["info"],
330
330
  jsonMode: "flag",
331
331
  handler: (args) => {
332
- const appId = (0, io_1.required)(args.positionals[0], "workflow app id");
332
+ const appId = (0, cli_args_1.required)(args.positionals[0], "workflow app id");
333
333
  const data = loadWorkflowAppLoader().showWorkflowApp(appId);
334
334
  return { json: data, text: `${(0, help_1.formatInfo)(appId, data)}\n` };
335
335
  },
@@ -351,9 +351,9 @@ registry_core_1.REGISTRY_BY_CAPABILITY.get("app.package").mcp.handler = (args) =
351
351
  (0, registry_core_1.attachCliBinding)("next", {
352
352
  path: ["next"],
353
353
  jsonMode: "default",
354
- handler: (args) => ({ json: loadStateCli().nextCli((0, io_1.required)((0, io_1.optionalArg)(args.positionals[0]), "run id"), args.options) }),
354
+ handler: (args) => ({ json: loadStateCli().nextCli((0, cli_args_1.required)((0, cli_args_1.optionalArg)(args.positionals[0]), "run id"), args.options) }),
355
355
  });
356
- registry_core_1.REGISTRY_BY_CAPABILITY.get("next").mcp.handler = (args) => loadStateCli().nextCli((0, io_1.required)((0, io_1.optionalArg)(args.runId), "run id"), args);
356
+ registry_core_1.REGISTRY_BY_CAPABILITY.get("next").mcp.handler = (args) => loadStateCli().nextCli((0, cli_args_1.required)((0, cli_args_1.optionalArg)(args.runId), "run id"), args);
357
357
  // `ledger.propose`/`.review`/`.verify`/`.apply`/`.list` are documented
358
358
  // payload-probe opt-outs in the old build (each mints a fresh timestamped/
359
359
  // digested entry, or reads args that arrive by --file/stdin on the CLI vs
@@ -421,3 +421,5 @@ The one-command `cw -q` headline now routes the question and defaults the repo t
421
421
  0.2.2
422
422
 
423
423
  0.2.3
424
+
425
+ 0.2.4
@@ -429,19 +429,22 @@ Parity is checked by `scripts/parity-check.js --check`, run by
429
429
  the registry, lists the live CLI commands and MCP tools, and fails closed on any
430
430
  of the rules above.
431
431
 
432
- The CLI dispatcher (`src/cli/command-surface.ts`) is being decomposed out of one
433
- large `switch` into per-command handler modules under `src/cli/handlers/`
434
- (`handle<Group>(args, runner)`), with `command-surface.ts` left as the thin
435
- router; shared helpers live in `src/cli/io.ts` (arg/JSON) and `src/cli/format.ts`
436
- (render). Carved so far: `workbench`, `clones`, `audit`, `worker`, `schedule`, `routine`,
437
- `sched`, `registry`, `queue`, `history`, `report`, `operator`, `graph`,
438
- `topology`, `summary`, `multi-agent`, `run`, `approve`, `reject`, `comment`,
439
- `handoff`, `review`, `blackboard`, `coordinator`, `eval`, `node`, `gc`,
440
- `telemetry`, `demo`, `feedback`, `metrics`, `migration`, `sandbox`, `backend`,
441
- `contract`, `candidate`. This is a pure code-move
442
- the command surface is unchanged and the parity scanner reads
443
- `dist/cli/handlers/*` so a subcommand `case` in a handler module still counts as a
444
- live CLI token.
432
+ The CLI dispatch surface is table-driven, not a hand-kept `switch`. The source
433
+ tree is small: `src/cli/entry.ts` (process entry), `src/cli/parseargv.ts`
434
+ (argv reading), `src/cli/dispatch.ts` (the one dispatcher), and `src/cli/io.ts`
435
+ (output helpers: `printJson`, `styledHelp`). `dispatch(args)` looks the parsed
436
+ command up in the capability table (`src/core/capability-table.ts`, put
437
+ together from the slices under `src/wiring/capability-table/`) and runs that
438
+ row's `cli.handler` a new capability is a new table row, never a new `case`.
439
+ The shared arg helpers (`required`, `optionalArg`, `wantsJson`) live in
440
+ `src/core/util/cli-args.ts`, a pure module used by the CLI and MCP sides of
441
+ every wiring slice; they were moved out of `cli/io.ts` because the purity
442
+ gate's layer rule says `wiring/` may not take imports from `cli/`. The parity
443
+ scanner works the same way: `cliDispatchTokens()` gets the live token list
444
+ straight from the registry's CLI capabilities (`cli.caseTokens` when a row
445
+ names more than one first token, else `cli.path`), then makes sure each
446
+ declared path resolves through the live dispatcher — there is no grep over
447
+ built dispatch source.
445
448
 
446
449
  `test/cli-mcp-parity-smoke.js` proves the contract from end to end. It checks
447
450
  registry ⇄ CLI ⇄ MCP coverage (every declared capability is found on its declared
@@ -638,3 +641,5 @@ CLI golden-path fixes: `cw -q "…"` routes the question (was read as an app id
638
641
  0.2.2
639
642
 
640
643
  0.2.3
644
+
645
+ 0.2.4
@@ -181,3 +181,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
181
181
  0.2.2
182
182
 
183
183
  0.2.3
184
+
185
+ 0.2.4
@@ -165,3 +165,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
165
165
  0.2.2
166
166
 
167
167
  0.2.3
168
+
169
+ 0.2.4
@@ -184,3 +184,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
184
184
  0.2.2
185
185
 
186
186
  0.2.3
187
+
188
+ 0.2.4
@@ -325,3 +325,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
325
325
  0.2.2
326
326
 
327
327
  0.2.3
328
+
329
+ 0.2.4
@@ -355,3 +355,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
355
355
  0.2.2
356
356
 
357
357
  0.2.3
358
+
359
+ 0.2.4
@@ -331,3 +331,5 @@ The host-facing surface tracks the CLI golden-path fixes (`cw -q` routing + repo
331
331
  0.2.2
332
332
 
333
333
  0.2.3
334
+
335
+ 0.2.4
@@ -357,3 +357,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
357
357
  0.2.2
358
358
 
359
359
  0.2.3
360
+
361
+ 0.2.4
@@ -369,3 +369,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
369
369
  0.2.2
370
370
 
371
371
  0.2.3
372
+
373
+ 0.2.4
@@ -190,3 +190,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
190
190
  0.2.2
191
191
 
192
192
  0.2.3
193
+
194
+ 0.2.4
@@ -250,3 +250,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
250
250
  0.2.2
251
251
 
252
252
  0.2.3
253
+
254
+ 0.2.4
@@ -1,15 +1,15 @@
1
1
  # Cool Workflow Project Index
2
2
 
3
- Generated from the current repository code on 2026-07-10 by `npm run sync:project-index`.
3
+ Generated from the current repository code on 2026-07-11 by `npm run sync:project-index`.
4
4
 
5
5
  ## Snapshot
6
6
 
7
7
  - Package: `cool-workflow`
8
- - Version: `0.2.3`
9
- - Source modules: `148`
8
+ - Version: `0.2.4`
9
+ - Source modules: `149`
10
10
  - Workflow apps: `8`
11
11
  - Docs: `62`
12
- - Smoke tests: `201`
12
+ - Smoke tests: `204`
13
13
  - Repository: https://github.com/coo1white/cool-workflow
14
14
 
15
15
  ## Architecture
@@ -118,6 +118,7 @@ multi-agent host -> topology -> blackboard/coordinator
118
118
  - [core/types/boundary.ts](../src/core/types/boundary.ts)
119
119
  - [core/types/execution-backend.ts](../src/core/types/execution-backend.ts)
120
120
  - [core/types/observability.ts](../src/core/types/observability.ts)
121
+ - [core/util/cli-args.ts](../src/core/util/cli-args.ts)
121
122
  - [core/util/collate.ts](../src/core/util/collate.ts)
122
123
  - [core/util/numeric-flag.ts](../src/core/util/numeric-flag.ts)
123
124
  - [mcp/dispatch.ts](../src/mcp/dispatch.ts)
@@ -284,6 +285,7 @@ multi-agent host -> topology -> blackboard/coordinator
284
285
  Smoke tests mirror the public contracts. The high-signal suites are:
285
286
 
286
287
  - [agent-backend-concurrent-user-env-smoke.js](../test/agent-backend-concurrent-user-env-smoke.js)
288
+ - [agent-backend-sandbox-deny-smoke.js](../test/agent-backend-sandbox-deny-smoke.js)
287
289
  - [agent-backend-user-env-smoke.js](../test/agent-backend-user-env-smoke.js)
288
290
  - [agent-config-atomic-write-smoke.js](../test/agent-config-atomic-write-smoke.js)
289
291
  - [agent-delegation-drive-smoke.js](../test/agent-delegation-drive-smoke.js)
@@ -342,6 +344,7 @@ Smoke tests mirror the public contracts. The high-signal suites are:
342
344
  - [dogfood-release-smoke.js](../test/dogfood-release-smoke.js)
343
345
  - [drive-async-real-signal-smoke.js](../test/drive-async-real-signal-smoke.js)
344
346
  - [drive-concurrency-flag-smoke.js](../test/drive-concurrency-flag-smoke.js)
347
+ - [drive-concurrent-round-sigkill-smoke.js](../test/drive-concurrent-round-sigkill-smoke.js)
345
348
  - [drive-exhaustion-blocked-smoke.js](../test/drive-exhaustion-blocked-smoke.js)
346
349
  - [drive-round-cache-serial-smoke.js](../test/drive-round-cache-serial-smoke.js)
347
350
  - [durable-atomic-write-smoke.js](../test/durable-atomic-write-smoke.js)
@@ -418,6 +421,7 @@ Smoke tests mirror the public contracts. The high-signal suites are:
418
421
  - [release-check-skip-smoke.js](../test/release-check-skip-smoke.js)
419
422
  - [release-flow-smoke.js](../test/release-flow-smoke.js)
420
423
  - [release-gate-smoke.js](../test/release-gate-smoke.js)
424
+ - [release-oneclick-smoke.js](../test/release-oneclick-smoke.js)
421
425
  - [release-pipeline-hygiene-smoke.js](../test/release-pipeline-hygiene-smoke.js)
422
426
  - [release-tooling-smoke.js](../test/release-tooling-smoke.js)
423
427
  - [remote-link-archive-smoke.js](../test/remote-link-archive-smoke.js)
@@ -197,3 +197,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
197
197
  0.2.2
198
198
 
199
199
  0.2.3
200
+
201
+ 0.2.4
@@ -337,3 +337,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
337
337
  0.2.2
338
338
 
339
339
  0.2.3
340
+
341
+ 0.2.4
@@ -170,14 +170,32 @@ model you set up does the review. CW starts the agent argv-style (`shell:false`)
170
170
  takes on the agent's own credentials, and pulls in no model SDK — the red line.
171
171
 
172
172
  ```bash
173
+ # the operator's one-command release (scripts/release-oneclick.js — preflight,
174
+ # gated cut, tag-only push, CI wait, npm confirmation, verdict-record PR):
175
+ npm run release -- 0.2.4
173
176
  # check only (gate + independent review, no mutation):
174
177
  node plugins/cool-workflow/scripts/release-flow.js --check
175
178
  # cut a tag once review is green (when --push, also creates the GitHub Release):
176
179
  node plugins/cool-workflow/scripts/release-flow.js --cut --version 0.1.77 [--push] [--no-release]
180
+ # run ONLY the cut's fail-fast preflight (seconds; used by release-oneclick.js):
181
+ node plugins/cool-workflow/scripts/release-flow.js --cut --version 0.1.77 --push --preflight-only
177
182
  # backfill / re-create the GitHub Release for an already-pushed tag (no gate/cut):
178
183
  node plugins/cool-workflow/scripts/release-flow.js --release --version 0.1.77 [--soft]
179
184
  ```
180
185
 
186
+ A `--cut` starts with a fail-fast **preflight** (added after the v0.2.3 cut,
187
+ where each of these fired only AFTER the gate/vendor/reviewer had already
188
+ spent real time and tokens): the version flag, the signing key (present AND
189
+ matching the committed `verdict-signing.pub` — once that pubkey is in the
190
+ repo, an unsigned cut is refused up front instead of failing later in CI),
191
+ no pre-existing `vX.Y.Z` tag (local or on origin), a `## X.Y.Z` CHANGELOG
192
+ section, a clean tracked tree, and — for `--push` — HEAD equal to the
193
+ `origin/main` tip (`--allow-stale-head` skips that one check for a
194
+ deliberate re-cut). The push at the end moves ONLY `refs/tags/vX.Y.Z`: the
195
+ verdict commit is a one-hop leaf on the reviewed commit, reachable through
196
+ the tag alone, so main's branch protection is never in the path and no
197
+ stray remote branch is created.
198
+
181
199
  The per-platform difference is config, not code — set the reviewer agent here:
182
200
 
183
201
  | Platform | Reviewer config |
@@ -300,3 +318,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
300
318
  0.2.2
301
319
 
302
320
  0.2.3
321
+
322
+ 0.2.4
@@ -480,3 +480,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
480
480
  0.2.2
481
481
 
482
482
  0.2.3
483
+
484
+ 0.2.4
@@ -107,6 +107,22 @@ scratch dir) is **re-pointed** to the retained `results/<task-id>.md` copy, and
107
107
  result-node snapshot is shown to stay `valid` (not `absent`) — so no surviving
108
108
  node points to a freed path. Opt out with `--keep-scratch`.
109
109
 
110
+ ## Commit snapshots — never reconstructable, always downgrades to verify-only
111
+
112
+ `commitState` embeds the FULL run into `commits/<id>.json` on every commit, so
113
+ these grow unbounded in both count and per-file size with no cap. Reclaiming them
114
+ frees the run's superseded, non-`verifierGated` "checkpoint" commit snapshots —
115
+ the run's LATEST commit and every `verifierGated` commit (the actual
116
+ audit-significant milestones) are always kept. `run.commits`' own lightweight
117
+ metadata (id, `verifierGated`, evidence digests, acceptance rationale — everything
118
+ `extractSkeleton` seals) lives in `state.json`, not the snapshot file, so it stays
119
+ fully intact and queryable regardless. Unlike a node snapshot, a commit snapshot is
120
+ a genuine point-in-time capture with no `ReconstructionRecipe` — reclaiming even
121
+ one downgrades capability to `verify-only`, never `re-runnable-by-reconstruction`.
122
+ Each reclaimed commit's own `StateNode` had its `snapshot` artifact stripped (not
123
+ re-pointed — there is no retained alternative) before the free, so `prepareFree`'s
124
+ dangling-reference proof covers this kind too. Opt out with `--keep-commits`.
125
+
110
126
  ## Orphan runs — a second pure-scratch case, OUTSIDE this transaction
111
127
 
112
128
  A run directory can also be pure scratch a different way: a process killed or
@@ -131,8 +147,8 @@ class of run; it is left `retained` indefinitely rather than guessed at.
131
147
  ## CLI
132
148
 
133
149
  ```
134
- cw gc plan [run-id] [--reclaimAfterArchiveDays N] [--keep-scratch] [--keep-snapshots] [--scope repo|home] [--json]
135
- cw gc run [run-id] [--reclaimAfterArchiveDays N] [--keep-scratch] [--keep-snapshots] [--limit N] [--actor NAME] [--json]
150
+ cw gc plan [run-id] [--reclaimAfterArchiveDays N] [--keep-scratch] [--keep-snapshots] [--keep-commits] [--scope repo|home] [--json]
151
+ cw gc run [run-id] [--reclaimAfterArchiveDays N] [--keep-scratch] [--keep-snapshots] [--keep-commits] [--limit N] [--actor NAME] [--json]
136
152
  cw gc verify <run-id> [--scope repo|home] [--json]
137
153
  ```
138
154
 
@@ -163,7 +179,8 @@ rule: only ISO timestamps may be now-derived.
163
179
 
164
180
  Retention/reclamation thresholds extend `RunRegistryPolicy` (alongside
165
181
  `archiveOlderThanDays`), never a new policy file: `reclaimAfterArchiveDays`,
166
- `keepSnapshots`, `keepScratch`, `reclaimStates`, `maxReclaimRuns`, `maxReclaimBytes`.
182
+ `keepSnapshots`, `keepScratch`, `keepCommits`, `reclaimStates`, `maxReclaimRuns`,
183
+ `maxReclaimBytes`.
167
184
  Back-compatible defaults reclaim nothing; pre-v0.1.39 runs load unchanged.
168
185
 
169
186
  ## Compatibility
@@ -269,3 +286,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
269
286
  0.2.2
270
287
 
271
288
  0.2.3
289
+
290
+ 0.2.4
@@ -326,3 +326,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
326
326
  0.2.2
327
327
 
328
328
  0.2.3
329
+
330
+ 0.2.4
@@ -262,3 +262,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
262
262
  0.2.2
263
263
 
264
264
  0.2.3
265
+
266
+ 0.2.4
@@ -69,3 +69,5 @@ security-trust-hardening(7), cli-mcp-parity(7), report-verifiable-bundle(7)
69
69
  0.2.2
70
70
 
71
71
  0.2.3
72
+
73
+ 0.2.4
@@ -280,3 +280,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
280
280
  0.2.2
281
281
 
282
282
  0.2.3
283
+
284
+ 0.2.4
@@ -2,7 +2,7 @@
2
2
  "_comment": "SINGLE SOURCE OF TRUTH for every vendor manifest. Edit THIS file, then run `npm run gen:manifests`. Do NOT hand-edit the generated vendor manifests (.claude-plugin/, .codex-plugin/, .agents/, .mcp.json) — `npm run gen:manifests -- --check` (run by release:check) will fail if they drift from this source.",
3
3
  "identity": {
4
4
  "name": "cool-workflow",
5
- "version": "0.2.3",
5
+ "version": "0.2.4",
6
6
  "license": "BSD-2-Clause",
7
7
  "homepage": "https://github.com/coo1white/cool-workflow",
8
8
  "author": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cool-workflow",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "bin": {
5
5
  "cool-workflow": "scripts/cw.js",
6
6
  "cw": "scripts/cw.js"
@@ -63,6 +63,7 @@
63
63
  "onramp:check": "node scripts/onramp-check.js --check",
64
64
  "version:sync": "node scripts/version-sync-check.js",
65
65
  "release:check": "node scripts/release-check.js",
66
+ "release": "node scripts/release-oneclick.js",
66
67
  "test": "node dist/cli.js version > /dev/null && node test/run-all.js --fast --sample 35",
67
68
  "test:full": "node dist/cli.js version > /dev/null && node test/run-all.js --sample 55",
68
69
  "test:gate": "node dist/cli.js version > /dev/null && node test/run-all.js",
@@ -30,16 +30,31 @@ fi
30
30
 
31
31
  REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || exit 0
32
32
  SHA="$(git -C "$REPO_ROOT" rev-parse HEAD)"
33
- GATE="$REPO_ROOT/.cw-release/gate-$SHA.ok"
34
- VERDICT="$REPO_ROOT/.cw-release/review-$SHA.verdict"
33
+ # cut() commits the verdict ON TOP of the reviewed commit, so at tag time the
34
+ # verdict filename is keyed on HEAD~1's sha, not HEAD's — the same
35
+ # HEAD-or-HEAD~1 tolerance release-gate.yml uses. Without this, a manual
36
+ # retag of a cut-produced commit was always blocked (v0.2.3 recovery).
37
+ PARENT="$(git -C "$REPO_ROOT" rev-parse HEAD~1 2>/dev/null || echo none)"
38
+ GATE=""
39
+ VERDICT=""
40
+ for C in "$SHA" "$PARENT"; do
41
+ [[ "$C" == "none" ]] && continue
42
+ if [[ -f "$REPO_ROOT/.cw-release/review-$C.verdict" ]]; then
43
+ GATE="$REPO_ROOT/.cw-release/gate-$C.ok"
44
+ VERDICT="$REPO_ROOT/.cw-release/review-$C.verdict"
45
+ break
46
+ fi
47
+ done
48
+ [[ -z "$VERDICT" ]] && VERDICT="$REPO_ROOT/.cw-release/review-$SHA.verdict"
49
+ [[ -z "$GATE" ]] && GATE="$REPO_ROOT/.cw-release/gate-$SHA.ok"
35
50
 
36
51
  if [[ ! -f "$GATE" ]]; then
37
- echo "BLOCKED: no release-gate pass for HEAD $SHA. Run plugins/cool-workflow/scripts/release-gate.sh first. Tagging without a green gate is forbidden." >&2
52
+ echo "BLOCKED: no release-gate pass for HEAD $SHA (or its parent). Run plugins/cool-workflow/scripts/release-gate.sh first. Tagging without a green gate is forbidden." >&2
38
53
  exit 2
39
54
  fi
40
55
 
41
56
  if [[ ! -f "$VERDICT" ]] || ! grep -q '^APPROVED' "$VERDICT"; then
42
- echo "BLOCKED: no APPROVED verdict from the release-reviewer agent for HEAD $SHA. Invoke the 'release-reviewer' subagent and obtain approval. Do not write the verdict file yourself — that is a gaming attempt and will be flagged in CI." >&2
57
+ echo "BLOCKED: no APPROVED verdict from the release-reviewer agent for HEAD $SHA (or its parent). Invoke the 'release-reviewer' subagent and obtain approval. Do not write the verdict file yourself — that is a gaming attempt and will be flagged in CI." >&2
43
58
  exit 2
44
59
  fi
45
60
 
@@ -77,12 +77,18 @@ function main() {
77
77
 
78
78
  const current = JSON.parse(fs.readFileSync(path.join(pluginRoot, "package.json"), "utf8")).version;
79
79
  if (next === current) {
80
- // Idempotent: re-running the bump for the same version is a no-op (exit 0)
81
- // rather than a hard fail. Required by `release-flow --cut --version X` when
82
- // the bump was already landed in a prior PR (e.g. a release-prep PR that
83
- // committed the version surfaces, then a follow-up fix on top cut should
84
- // still be able to commit the verdict and tag without re-bumping).
85
- process.stdout.write(`bump:version: already at ${next}, no surfaces to update\n`);
80
+ // Idempotent for the STRUCTURED surfaces: re-running the bump for the same
81
+ // version skips the writes (exit 0) rather than hard-failing. Required by
82
+ // `release-flow --cut --version X` when the bump was already landed in a
83
+ // prior PR cut should still be able to commit the verdict and tag
84
+ // without re-bumping. But the CONTENT-surface gate must still run: the
85
+ // v0.2.3 cut hit exactly this hole package.json was already bumped, so
86
+ // this early-exit skipped the content check entirely and `--content`
87
+ // never filled the missing docs/RELEASE references. Same-version reruns
88
+ // now gate (or fill) content exactly like a fresh bump does.
89
+ process.stdout.write(`bump:version: structured surfaces already at ${next}\n`);
90
+ const sameVersionContent = handleContentSurfaces(current, next);
91
+ if (!sameVersionContent.ok) fail(sameVersionContent.error);
86
92
  process.exit(0);
87
93
  }
88
94
 
@@ -161,9 +167,11 @@ function main() {
161
167
  // and a RegExp string (`0\\.1\\.32`, from `new RegExp(\`...@0\\.1\\.32\`)`).
162
168
  // A plain replace silently misses the escaped forms — the exact "escaped-dot"
163
169
  // surface that fails version:sync mid-release. Replace all three, most-escaped
164
- // first so the forms can never overlap.
165
- const esc1 = (v) => v.replace(/\./g, "\\.");
166
- const esc2 = (v) => v.replace(/\./g, "\\\\.");
170
+ // first so the forms can never overlap. No regex is ever built from these —
171
+ // the forms feed the plain split/join TEXT replacement below (which is also
172
+ // why split/join, not a regex-escape-shaped replace, builds them).
173
+ const esc1 = (v) => v.split(".").join("\\.");
174
+ const esc2 = (v) => v.split(".").join("\\\\.");
167
175
  const forms = [[esc2(current), esc2(next)], [esc1(current), esc1(next)], [current, next]];
168
176
  for (const rel of targeted) {
169
177
  const abs = path.join(pluginRoot, rel);
@@ -261,7 +269,16 @@ function handleContentSurfaces(current, next) {
261
269
  const abs = path.join(repoRoot, rel);
262
270
  if (!fs.existsSync(abs)) { missing.push({ rel, reason: "file missing" }); continue; }
263
271
  const text = fs.readFileSync(abs, "utf8");
264
- if (!text.includes(needle)) missing.push({ rel, needle });
272
+ // Own-line match, not a plain substring: every entry here is a line this
273
+ // function itself appends (`\n${needle}\n`) when missing, and a bare
274
+ // version number can otherwise coincidentally already appear in the
275
+ // doc's unrelated prose (an example command, a cross-reference) without
276
+ // the real version-history line actually being present. See
277
+ // version-sync-check.js's checkIncludesOwnLine for the matching CI-side
278
+ // gate (found live: v0.2.4's bump masked a real missing footer line in
279
+ // release-tooling.7.md this way).
280
+ const hasOwnLine = text.split("\n").some((line) => line.trim() === needle);
281
+ if (!hasOwnLine) missing.push({ rel, needle });
265
282
  }
266
283
 
267
284
  if (missing.length === 0) {