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.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/apps/architecture-review/app.json +1 -1
- package/apps/architecture-review-fast/app.json +1 -1
- package/apps/end-to-end-golden-path/app.json +1 -1
- package/apps/pr-review-fix-ci/app.json +1 -1
- package/apps/release-cut/app.json +1 -1
- package/apps/research-synthesis/app.json +1 -1
- package/dist/cli/dispatch.js +2 -1
- package/dist/cli/io.js +6 -20
- package/dist/core/capability-data.js +2 -2
- package/dist/core/format/help.js +7 -1
- package/dist/core/util/cli-args.js +33 -0
- package/dist/core/version.js +1 -1
- package/dist/shell/drive.js +14 -6
- package/dist/shell/execution-backend/agent.js +10 -1
- package/dist/shell/execution-backend/local.js +15 -10
- package/dist/shell/pipeline-cli.js +20 -2
- package/dist/shell/reclamation-io.js +74 -6
- package/dist/shell/registry-cli.js +4 -0
- package/dist/shell/run-registry-io.js +1 -0
- package/dist/shell/workflow-app-loader.js +67 -1
- package/dist/wiring/capability-table/basics.js +2 -2
- package/dist/wiring/capability-table/exec-backend.js +9 -9
- package/dist/wiring/capability-table/multi-agent.js +69 -69
- package/dist/wiring/capability-table/pipeline.js +28 -28
- package/dist/wiring/capability-table/registry-core.js +3 -3
- package/dist/wiring/capability-table/reporting.js +38 -38
- package/dist/wiring/capability-table/scheduling-registry.js +58 -58
- package/dist/wiring/capability-table/state.js +34 -34
- package/dist/wiring/capability-table/trust-ledger.js +12 -12
- package/dist/wiring/capability-table/workflow-apps.js +15 -15
- package/docs/agent-delegation-drive.7.md +2 -0
- package/docs/cli-mcp-parity.7.md +18 -13
- package/docs/contract-migration-tooling.7.md +2 -0
- package/docs/control-plane-scheduling.7.md +2 -0
- package/docs/durable-state-and-locking.7.md +2 -0
- package/docs/evidence-adoption-reasoning-chain.7.md +2 -0
- package/docs/execution-backends.7.md +2 -0
- package/docs/multi-agent-cli-mcp-surface.7.md +2 -0
- package/docs/multi-agent-eval-replay-harness.7.md +2 -0
- package/docs/multi-agent-operator-ux.7.md +2 -0
- package/docs/node-snapshot-diff-replay.7.md +2 -0
- package/docs/observability-cost-accounting.7.md +2 -0
- package/docs/project-index.md +8 -4
- package/docs/real-execution-backends.7.md +2 -0
- package/docs/release-and-migration.7.md +2 -0
- package/docs/release-tooling.7.md +20 -0
- package/docs/run-registry-control-plane.7.md +2 -0
- package/docs/run-retention-reclamation.7.md +22 -3
- package/docs/state-explosion-management.7.md +2 -0
- package/docs/team-collaboration.7.md +2 -0
- package/docs/trust-audit-anchor.7.md +2 -0
- package/docs/web-desktop-workbench.7.md +2 -0
- package/manifest/plugin.manifest.json +1 -1
- package/package.json +2 -1
- package/scripts/block-unapproved-tag.sh +19 -4
- package/scripts/bump-version.js +27 -10
- package/scripts/canonical-apps.js +4 -4
- package/scripts/dogfood-release.js +1 -1
- package/scripts/golden-path.js +4 -4
- package/scripts/purity-baseline.json +0 -30
- package/scripts/release-flow.js +137 -12
- package/scripts/release-oneclick.js +407 -0
- 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
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
package/docs/cli-mcp-parity.7.md
CHANGED
|
@@ -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
|
|
433
|
-
|
|
434
|
-
(
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
`
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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
|
package/docs/project-index.md
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# Cool Workflow Project Index
|
|
2
2
|
|
|
3
|
-
Generated from the current repository code on 2026-07-
|
|
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.
|
|
9
|
-
- Source modules: `
|
|
8
|
+
- Version: `0.2.4`
|
|
9
|
+
- Source modules: `149`
|
|
10
10
|
- Workflow apps: `8`
|
|
11
11
|
- Docs: `62`
|
|
12
|
-
- Smoke tests: `
|
|
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)
|
|
@@ -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
|
|
@@ -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`,
|
|
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
|
|
@@ -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.
|
|
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
|
+
"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
|
-
|
|
34
|
-
|
|
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
|
|
package/scripts/bump-version.js
CHANGED
|
@@ -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
|
|
81
|
-
// rather than
|
|
82
|
-
// the bump was already landed in a
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
|
|
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
|
-
|
|
166
|
-
|
|
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
|
-
|
|
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) {
|