cool-workflow 0.1.78
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 +20 -0
- package/.codex-plugin/mcp.json +10 -0
- package/.codex-plugin/plugin.json +38 -0
- package/.mcp.json +10 -0
- package/LICENSE +24 -0
- package/README.md +638 -0
- package/apps/architecture-review/app.json +51 -0
- package/apps/architecture-review/workflow.js +116 -0
- package/apps/end-to-end-golden-path/app.json +30 -0
- package/apps/end-to-end-golden-path/workflow.js +33 -0
- package/apps/pr-review-fix-ci/app.json +59 -0
- package/apps/pr-review-fix-ci/workflow.js +90 -0
- package/apps/release-cut/app.json +54 -0
- package/apps/release-cut/workflow.js +82 -0
- package/apps/research-synthesis/app.json +50 -0
- package/apps/research-synthesis/workflow.js +76 -0
- package/apps/workflow-app-framework-demo/app.json +29 -0
- package/apps/workflow-app-framework-demo/workflow.js +44 -0
- package/dist/agent-config.js +223 -0
- package/dist/candidate-scoring.js +715 -0
- package/dist/capability-core.js +630 -0
- package/dist/capability-dispatcher.js +86 -0
- package/dist/capability-registry.js +523 -0
- package/dist/cli.js +1276 -0
- package/dist/collaboration.js +727 -0
- package/dist/commit.js +570 -0
- package/dist/contract-migration.js +234 -0
- package/dist/coordinator.js +1163 -0
- package/dist/daemon.js +44 -0
- package/dist/dispatch.js +201 -0
- package/dist/drive.js +503 -0
- package/dist/error-feedback.js +415 -0
- package/dist/evidence-grounding.js +179 -0
- package/dist/evidence-reasoning.js +733 -0
- package/dist/execution-backend.js +1279 -0
- package/dist/harness.js +61 -0
- package/dist/mcp-server.js +1615 -0
- package/dist/multi-agent-eval.js +857 -0
- package/dist/multi-agent-host.js +764 -0
- package/dist/multi-agent-operator-ux.js +537 -0
- package/dist/multi-agent-trust.js +366 -0
- package/dist/multi-agent.js +1173 -0
- package/dist/node-snapshot.js +270 -0
- package/dist/observability.js +922 -0
- package/dist/operator-ux.js +971 -0
- package/dist/orchestrator/audit-operations.js +182 -0
- package/dist/orchestrator/candidate-operations.js +117 -0
- package/dist/orchestrator/cli-options.js +288 -0
- package/dist/orchestrator/collaboration-operations.js +86 -0
- package/dist/orchestrator/feedback-operations.js +81 -0
- package/dist/orchestrator/host-operations.js +78 -0
- package/dist/orchestrator/lifecycle-operations.js +462 -0
- package/dist/orchestrator/migration-operations.js +44 -0
- package/dist/orchestrator/multi-agent-operations.js +362 -0
- package/dist/orchestrator/report.js +369 -0
- package/dist/orchestrator/topology-operations.js +84 -0
- package/dist/orchestrator.js +874 -0
- package/dist/pipeline-contract.js +92 -0
- package/dist/pipeline-runner.js +285 -0
- package/dist/reclamation.js +882 -0
- package/dist/result-normalize.js +194 -0
- package/dist/run-export.js +64 -0
- package/dist/run-registry.js +1347 -0
- package/dist/run-state-schema.js +67 -0
- package/dist/sandbox-profile.js +471 -0
- package/dist/scheduler.js +266 -0
- package/dist/scheduling.js +184 -0
- package/dist/schema-validate.js +98 -0
- package/dist/state-explosion.js +1213 -0
- package/dist/state-migrations.js +463 -0
- package/dist/state-node.js +301 -0
- package/dist/state.js +308 -0
- package/dist/telemetry-attestation.js +156 -0
- package/dist/telemetry-ledger.js +145 -0
- package/dist/topology.js +527 -0
- package/dist/triggers.js +159 -0
- package/dist/trust-audit.js +475 -0
- package/dist/types/blackboard.js +2 -0
- package/dist/types/boundary.js +29 -0
- package/dist/types/candidate.js +2 -0
- package/dist/types/collaboration.js +2 -0
- package/dist/types/core.js +2 -0
- package/dist/types/drive.js +10 -0
- package/dist/types/error-feedback.js +2 -0
- package/dist/types/evidence-reasoning.js +2 -0
- package/dist/types/execution-backend.js +2 -0
- package/dist/types/multi-agent.js +2 -0
- package/dist/types/observability.js +2 -0
- package/dist/types/pipeline.js +2 -0
- package/dist/types/reclamation.js +8 -0
- package/dist/types/result.js +2 -0
- package/dist/types/run-registry.js +2 -0
- package/dist/types/run.js +2 -0
- package/dist/types/sandbox.js +2 -0
- package/dist/types/schedule.js +2 -0
- package/dist/types/state-node.js +2 -0
- package/dist/types/topology.js +2 -0
- package/dist/types/trust.js +2 -0
- package/dist/types/workbench.js +2 -0
- package/dist/types/worker.js +2 -0
- package/dist/types/workflow-app.js +2 -0
- package/dist/types.js +43 -0
- package/dist/verifier-registry.js +46 -0
- package/dist/verifier.js +78 -0
- package/dist/version.js +8 -0
- package/dist/workbench-host.js +172 -0
- package/dist/workbench.js +190 -0
- package/dist/worker-isolation.js +1028 -0
- package/dist/workflow-api.js +98 -0
- package/dist/workflow-app-framework.js +626 -0
- package/docs/agent-delegation-drive.7.md +190 -0
- package/docs/agent-framework.md +176 -0
- package/docs/candidate-scoring.7.md +106 -0
- package/docs/canonical-workflow-apps.7.md +137 -0
- package/docs/capability-topology-registry.7.md +168 -0
- package/docs/cli-mcp-parity.7.md +373 -0
- package/docs/contract-migration-tooling.7.md +123 -0
- package/docs/control-plane-scheduling.7.md +110 -0
- package/docs/coordinator-blackboard.7.md +183 -0
- package/docs/dogfood/architecture-review-cool-workflow.md +16 -0
- package/docs/dogfood-one-real-repo.7.md +168 -0
- package/docs/durable-state-and-locking.7.md +107 -0
- package/docs/end-to-end-golden-path.7.md +117 -0
- package/docs/error-feedback.7.md +153 -0
- package/docs/evidence-adoption-reasoning-chain.7.md +270 -0
- package/docs/execution-backends.7.md +300 -0
- package/docs/getting-started.md +99 -0
- package/docs/index.md +41 -0
- package/docs/mcp-app-surface.7.md +235 -0
- package/docs/multi-agent-cli-mcp-surface.7.md +265 -0
- package/docs/multi-agent-eval-replay-harness.7.md +302 -0
- package/docs/multi-agent-operator-ux.7.md +314 -0
- package/docs/multi-agent-runtime-core.7.md +231 -0
- package/docs/multi-agent-topologies.7.md +103 -0
- package/docs/multi-agent-trust-policy-audit.7.md +154 -0
- package/docs/node-snapshot-diff-replay.7.md +135 -0
- package/docs/observability-cost-accounting.7.md +194 -0
- package/docs/operator-ux.7.md +180 -0
- package/docs/pipeline-runner.7.md +136 -0
- package/docs/project-index.md +261 -0
- package/docs/real-execution-backends.7.md +142 -0
- package/docs/release-and-migration.7.md +280 -0
- package/docs/release-tooling.7.md +159 -0
- package/docs/routines.md +48 -0
- package/docs/run-registry-control-plane.7.md +312 -0
- package/docs/run-retention-reclamation.7.md +191 -0
- package/docs/sandbox-profiles.7.md +137 -0
- package/docs/scheduled-tasks.md +80 -0
- package/docs/security-trust-hardening.7.md +117 -0
- package/docs/state-explosion-management.7.md +264 -0
- package/docs/state-node.7.md +96 -0
- package/docs/team-collaboration.7.md +207 -0
- package/docs/unix-principles.md +192 -0
- package/docs/verifier-gated-commit.7.md +140 -0
- package/docs/web-desktop-workbench.7.md +215 -0
- package/docs/worker-isolation.7.md +167 -0
- package/docs/workflow-app-framework.7.md +274 -0
- package/manifest/README.md +43 -0
- package/manifest/plugin.manifest.json +316 -0
- package/manifest/pricing.policy.json +14 -0
- package/package.json +79 -0
- package/scripts/agents/claude-p-agent.js +104 -0
- package/scripts/agents/claude-p-agent.sh +9 -0
- package/scripts/agents/cw-attest-keygen.js +55 -0
- package/scripts/agents/cw-attest-wrap.js +143 -0
- package/scripts/block-unapproved-tag.sh +39 -0
- package/scripts/bump-version.js +249 -0
- package/scripts/canonical-apps.js +171 -0
- package/scripts/cw.js +4 -0
- package/scripts/dist-drift-check.js +79 -0
- package/scripts/dogfood-architecture-review.js +237 -0
- package/scripts/dogfood-release.js +624 -0
- package/scripts/forward-ref-docs.js +73 -0
- package/scripts/gen-manifests.js +232 -0
- package/scripts/golden-path.js +300 -0
- package/scripts/mcp-server.js +4 -0
- package/scripts/new-feature.js +121 -0
- package/scripts/parity-check.js +213 -0
- package/scripts/release-check.js +118 -0
- package/scripts/release-flow.js +272 -0
- package/scripts/release-gate.sh +85 -0
- package/scripts/sync-project-index.js +387 -0
- package/scripts/validate-run-state-schema.js +126 -0
- package/scripts/verify-container-selfref.js +64 -0
- package/scripts/version-sync-check.js +237 -0
- package/skills/cool-workflow/SKILL.md +162 -0
- package/skills/cool-workflow/references/commands.md +282 -0
- package/tsconfig.json +16 -0
- package/ui/workbench/app.css +76 -0
- package/ui/workbench/app.js +159 -0
- package/ui/workbench/index.html +32 -0
- package/workflows/architecture-review.workflow.js +84 -0
- package/workflows/research-synthesis.workflow.js +47 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
// CLI <-> MCP parity gate. Two renderings of one data source must agree.
|
|
5
|
+
//
|
|
6
|
+
// node scripts/parity-check.js # print the parity report (JSON)
|
|
7
|
+
// node scripts/parity-check.js --check # fail (exit 1) on ANY drift
|
|
8
|
+
//
|
|
9
|
+
// FAIL CLOSED ON DRIFT (BSD discipline, same shape as gen-manifests --check):
|
|
10
|
+
// 1. STATIC parity — the declared capability registry must exactly match the
|
|
11
|
+
// live MCP tool list (tools/list) and the CLI dispatch tokens parsed from
|
|
12
|
+
// dist/cli.js. A tool or command on one surface but not the other, or not
|
|
13
|
+
// declared in src/capability-registry.ts, is release-blocking.
|
|
14
|
+
// 2. PAYLOAD parity — for every capability declared `payloadIdentical`, the
|
|
15
|
+
// `cw <cmd> --json` payload must equal the `cw_<tool>` MCP result on a real
|
|
16
|
+
// bootstrap run (whitespace + generation-moment ISO timestamps aside).
|
|
17
|
+
//
|
|
18
|
+
// The registry (src/capability-registry.ts -> dist/capability-registry.js) is
|
|
19
|
+
// the single source of truth; this script never re-declares capabilities.
|
|
20
|
+
|
|
21
|
+
const assert = require("node:assert/strict");
|
|
22
|
+
const { execFileSync, spawn } = require("node:child_process");
|
|
23
|
+
const fs = require("node:fs");
|
|
24
|
+
const os = require("node:os");
|
|
25
|
+
const path = require("node:path");
|
|
26
|
+
const readline = require("node:readline");
|
|
27
|
+
|
|
28
|
+
const pluginRoot = path.resolve(__dirname, "..");
|
|
29
|
+
const node = process.execPath;
|
|
30
|
+
const cli = path.join(pluginRoot, "dist", "cli.js");
|
|
31
|
+
const mcpServer = path.join(pluginRoot, "dist", "mcp-server.js");
|
|
32
|
+
const registry = require(path.join(pluginRoot, "dist", "capability-registry.js"));
|
|
33
|
+
|
|
34
|
+
// Read capabilities that are safe to probe on a freshly planned run with only a
|
|
35
|
+
// runId (or no args). Each: [capability, cliArgvAfterRunId, mcpTool].
|
|
36
|
+
// runId-less reads use [] for cli args and {} mcp args.
|
|
37
|
+
const RUN_PROBES = [
|
|
38
|
+
["status", ["--json"], "cw_status"],
|
|
39
|
+
["operator.status", ["--json"], "cw_operator_status"],
|
|
40
|
+
["operator.report", ["--json"], "cw_operator_report"],
|
|
41
|
+
["graph", ["--json"], "cw_operator_graph"],
|
|
42
|
+
["report", ["--json"], "cw_report"],
|
|
43
|
+
["next", [], "cw_next"],
|
|
44
|
+
["state.check", [], "cw_state_check"],
|
|
45
|
+
["contract.show", [], "cw_contract_show"],
|
|
46
|
+
["node.list", [], "cw_node_list"],
|
|
47
|
+
["node.graph", ["--json"], "cw_node_graph"],
|
|
48
|
+
["worker.summary", ["--json"], "cw_worker_summary"],
|
|
49
|
+
["candidate.summary", ["--json"], "cw_candidate_summary"],
|
|
50
|
+
["feedback.summary", ["--json"], "cw_feedback_summary"],
|
|
51
|
+
["commit.summary", ["--json"], "cw_commit_summary"],
|
|
52
|
+
["audit.summary", [], "cw_audit_summary"],
|
|
53
|
+
["multi-agent.summary", ["--json"], "cw_multi_agent_summary"],
|
|
54
|
+
["workbench.view", ["--json"], "cw_workbench_view"],
|
|
55
|
+
["metrics.show", ["--json"], "cw_metrics_show"],
|
|
56
|
+
["review.status", ["--json"], "cw_review_status"],
|
|
57
|
+
["comment.list", ["--json"], "cw_comment_list"],
|
|
58
|
+
["run.drive", ["--json"], "cw_run_drive"],
|
|
59
|
+
["gc.plan", ["--json"], "cw_gc_plan"],
|
|
60
|
+
["gc.verify", ["--json"], "cw_gc_verify"]
|
|
61
|
+
];
|
|
62
|
+
const GLOBAL_PROBES = [
|
|
63
|
+
["list", "cw_list"],
|
|
64
|
+
["app.list", "cw_app_list"],
|
|
65
|
+
["topology.list", "cw_topology_list"],
|
|
66
|
+
["sandbox.list", "cw_sandbox_list"],
|
|
67
|
+
["backend.list", "cw_backend_list"],
|
|
68
|
+
["backend.agent.config.show", "cw_backend_agent_config_show"],
|
|
69
|
+
["metrics.summary", "cw_metrics_summary"]
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
function capById(id) {
|
|
73
|
+
const cap = registry.CAPABILITY_REGISTRY.find((entry) => entry.capability === id);
|
|
74
|
+
assert.ok(cap, `probe references unknown capability ${id}`);
|
|
75
|
+
return cap;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ---- 1. static surface parity ---------------------------------------------
|
|
79
|
+
function liveMcpTools() {
|
|
80
|
+
const result = execFileSync(node, [mcpServer], {
|
|
81
|
+
cwd: pluginRoot,
|
|
82
|
+
input: `${JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list", params: {} })}\n`,
|
|
83
|
+
encoding: "utf8"
|
|
84
|
+
});
|
|
85
|
+
const line = result
|
|
86
|
+
.trim()
|
|
87
|
+
.split("\n")
|
|
88
|
+
.find((entry) => entry.includes('"tools"'));
|
|
89
|
+
assert.ok(line, "MCP server returned no tools/list result");
|
|
90
|
+
return JSON.parse(line).result.tools.map((tool) => tool.name);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function cliDispatchTokens() {
|
|
94
|
+
const source = fs.readFileSync(cli, "utf8");
|
|
95
|
+
return [...new Set([...source.matchAll(/case\s+"([^"]+)":/g)].map((match) => match[1]))];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ---- 2. payload identity ---------------------------------------------------
|
|
99
|
+
// Generation-moment ISO timestamps are presentation metadata, not capability
|
|
100
|
+
// data: the same field carries the wall-clock instant of the render. Neutralize
|
|
101
|
+
// them (and only them) before comparison so we assert canonical-payload identity.
|
|
102
|
+
function canonical(value) {
|
|
103
|
+
return JSON.stringify(value).replace(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z/g, "<ts>");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function openMcp() {
|
|
107
|
+
const server = spawn(node, [mcpServer], { cwd: pluginRoot, stdio: ["pipe", "pipe", "pipe"] });
|
|
108
|
+
const lines = readline.createInterface({ input: server.stdout });
|
|
109
|
+
const pending = new Map();
|
|
110
|
+
let nextId = 1;
|
|
111
|
+
lines.on("line", (line) => {
|
|
112
|
+
let message;
|
|
113
|
+
try {
|
|
114
|
+
message = JSON.parse(line);
|
|
115
|
+
} catch {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const waiter = pending.get(message.id);
|
|
119
|
+
if (!waiter) return;
|
|
120
|
+
pending.delete(message.id);
|
|
121
|
+
if (message.error) waiter.reject(new Error(message.error.message));
|
|
122
|
+
else waiter.resolve(message.result);
|
|
123
|
+
});
|
|
124
|
+
const rpc = (method, params) => {
|
|
125
|
+
const id = nextId++;
|
|
126
|
+
server.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, method, params })}\n`);
|
|
127
|
+
return new Promise((resolve, reject) => pending.set(id, { resolve, reject }));
|
|
128
|
+
};
|
|
129
|
+
const tool = (name, args) => rpc("tools/call", { name, arguments: args }).then((result) => JSON.parse(result.content[0].text));
|
|
130
|
+
return { server, rpc, tool };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function payloadParity() {
|
|
134
|
+
// realpath so the CLI (which realpath-resolves its cwd) and the MCP server we
|
|
135
|
+
// hand `cwd` to observe identical absolute paths — a workspace symlink would
|
|
136
|
+
// otherwise masquerade as a payload divergence.
|
|
137
|
+
const workspace = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), "cw-parity-")));
|
|
138
|
+
const plan = JSON.parse(
|
|
139
|
+
execFileSync(node, [cli, "plan", "architecture-review", "--repo", workspace, "--question", "v0.1.27 CLI<->MCP parity probe"], {
|
|
140
|
+
cwd: workspace,
|
|
141
|
+
encoding: "utf8"
|
|
142
|
+
})
|
|
143
|
+
);
|
|
144
|
+
const runId = plan.runId;
|
|
145
|
+
const mismatches = [];
|
|
146
|
+
const checked = [];
|
|
147
|
+
const mcp = openMcp();
|
|
148
|
+
try {
|
|
149
|
+
await mcp.rpc("initialize", {});
|
|
150
|
+
for (const [capability, mcpTool] of GLOBAL_PROBES) {
|
|
151
|
+
const cap = capById(capability);
|
|
152
|
+
assert.equal(cap.mcp.tool, mcpTool, `probe/registry MCP tool mismatch for ${capability}`);
|
|
153
|
+
const cliArgv = [...cap.cli.path, ...(cap.cli.jsonMode === "flag" ? ["--json"] : [])];
|
|
154
|
+
const cliOut = JSON.parse(execFileSync(node, [cli, ...cliArgv], { cwd: workspace, encoding: "utf8" }));
|
|
155
|
+
const mcpOut = await mcp.tool(mcpTool, { cwd: workspace });
|
|
156
|
+
checked.push(capability);
|
|
157
|
+
if (canonical(cliOut) !== canonical(mcpOut)) mismatches.push(capability);
|
|
158
|
+
}
|
|
159
|
+
for (const [capability, extraArgv, mcpTool] of RUN_PROBES) {
|
|
160
|
+
const cap = capById(capability);
|
|
161
|
+
assert.equal(cap.mcp.tool, mcpTool, `probe/registry MCP tool mismatch for ${capability}`);
|
|
162
|
+
const cliArgv = [...cap.cli.path, runId, ...extraArgv];
|
|
163
|
+
const cliOut = JSON.parse(execFileSync(node, [cli, ...cliArgv], { cwd: workspace, encoding: "utf8" }));
|
|
164
|
+
const mcpOut = await mcp.tool(mcpTool, { cwd: workspace, runId });
|
|
165
|
+
checked.push(capability);
|
|
166
|
+
if (canonical(cliOut) !== canonical(mcpOut)) mismatches.push(capability);
|
|
167
|
+
}
|
|
168
|
+
} finally {
|
|
169
|
+
mcp.server.kill();
|
|
170
|
+
}
|
|
171
|
+
return { runId, checked, mismatches };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function main() {
|
|
175
|
+
const check = process.argv.includes("--check");
|
|
176
|
+
const tools = liveMcpTools();
|
|
177
|
+
const tokens = cliDispatchTokens();
|
|
178
|
+
const report = registry.buildParityReport({ mcpTools: tools, cliTokens: tokens });
|
|
179
|
+
const payload = await payloadParity();
|
|
180
|
+
|
|
181
|
+
const ok = report.ok && payload.mismatches.length === 0;
|
|
182
|
+
const out = {
|
|
183
|
+
ok,
|
|
184
|
+
static: report,
|
|
185
|
+
payload: {
|
|
186
|
+
ok: payload.mismatches.length === 0,
|
|
187
|
+
runId: payload.runId,
|
|
188
|
+
checked: payload.checked.length,
|
|
189
|
+
capabilities: payload.checked,
|
|
190
|
+
mismatches: payload.mismatches
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
process.stdout.write(`${JSON.stringify(out, null, 2)}\n`);
|
|
194
|
+
|
|
195
|
+
if (check && !ok) {
|
|
196
|
+
const lines = ["", "CLI <-> MCP parity drift detected (release-blocking):"];
|
|
197
|
+
if (report.missingMcpTools.length) lines.push(` - registry declares MCP tools absent from the server: ${report.missingMcpTools.join(", ")}`);
|
|
198
|
+
if (report.undeclaredMcpTools.length) lines.push(` - server exposes MCP tools not declared in the registry: ${report.undeclaredMcpTools.join(", ")}`);
|
|
199
|
+
if (report.missingCliTokens.length) lines.push(` - registry declares CLI tokens absent from dist/cli.js: ${report.missingCliTokens.join(", ")}`);
|
|
200
|
+
if (report.undeclaredCliTokens.length) lines.push(` - dist/cli.js dispatches tokens not declared in the registry: ${report.undeclaredCliTokens.join(", ")}`);
|
|
201
|
+
if (report.reasonlessExceptions.length) lines.push(` - surface-specific / payload-divergent capabilities missing a recorded reason: ${report.reasonlessExceptions.join(", ")}`);
|
|
202
|
+
if (report.registryLint.length) lines.push(` - registry lint: ${report.registryLint.join("; ")}`);
|
|
203
|
+
if (payload.mismatches.length) lines.push(` - cw --json != cw_<tool> payload for: ${payload.mismatches.join(", ")}`);
|
|
204
|
+
lines.push("Reconcile src/capability-registry.ts, cli.ts, and mcp-server.ts so both surfaces render one data source.\n");
|
|
205
|
+
process.stderr.write(lines.join("\n"));
|
|
206
|
+
process.exitCode = 1;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
main().catch((error) => {
|
|
211
|
+
process.stderr.write(`parity-check: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
212
|
+
process.exitCode = 1;
|
|
213
|
+
});
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const { spawnSync } = require("node:child_process");
|
|
5
|
+
const fs = require("node:fs");
|
|
6
|
+
const path = require("node:path");
|
|
7
|
+
|
|
8
|
+
const pluginRoot = path.resolve(__dirname, "..");
|
|
9
|
+
const repoRoot = path.resolve(pluginRoot, "..", "..");
|
|
10
|
+
const checks = [
|
|
11
|
+
{
|
|
12
|
+
name: "docs presence",
|
|
13
|
+
run: () => {
|
|
14
|
+
for (const file of [
|
|
15
|
+
"README.md",
|
|
16
|
+
"docs/index.md",
|
|
17
|
+
"docs/getting-started.md",
|
|
18
|
+
"docs/release-and-migration.7.md",
|
|
19
|
+
"docs/multi-agent-cli-mcp-surface.7.md",
|
|
20
|
+
"docs/multi-agent-operator-ux.7.md",
|
|
21
|
+
"docs/multi-agent-trust-policy-audit.7.md",
|
|
22
|
+
"docs/multi-agent-eval-replay-harness.7.md",
|
|
23
|
+
"docs/state-explosion-management.7.md",
|
|
24
|
+
"docs/evidence-adoption-reasoning-chain.7.md",
|
|
25
|
+
"docs/cli-mcp-parity.7.md",
|
|
26
|
+
"docs/run-registry-control-plane.7.md",
|
|
27
|
+
"docs/execution-backends.7.md",
|
|
28
|
+
"docs/web-desktop-workbench.7.md",
|
|
29
|
+
"docs/observability-cost-accounting.7.md",
|
|
30
|
+
"docs/team-collaboration.7.md",
|
|
31
|
+
"docs/release-tooling.7.md",
|
|
32
|
+
"docs/real-execution-backends.7.md",
|
|
33
|
+
"docs/node-snapshot-diff-replay.7.md",
|
|
34
|
+
"docs/contract-migration-tooling.7.md",
|
|
35
|
+
"docs/control-plane-scheduling.7.md",
|
|
36
|
+
"docs/agent-delegation-drive.7.md",
|
|
37
|
+
"docs/run-retention-reclamation.7.md",
|
|
38
|
+
"docs/durable-state-and-locking.7.md",
|
|
39
|
+
"docs/security-trust-hardening.7.md",
|
|
40
|
+
"../../CHANGELOG.md",
|
|
41
|
+
"../../RELEASE.md"
|
|
42
|
+
]) {
|
|
43
|
+
const absolute = path.resolve(pluginRoot, file);
|
|
44
|
+
if (!fs.existsSync(absolute)) throw new Error(`missing ${path.relative(repoRoot, absolute)}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
// NOTE: the individual `node test/<x>-smoke.js` steps were removed — every one
|
|
49
|
+
// is already run by `npm test` below (proven by set intersection). Re-running
|
|
50
|
+
// them here doubled wall time (~86s -> ~25s) without adding coverage. The steps
|
|
51
|
+
// kept below are the ones NOT covered by `npm test`: the build/typecheck, the
|
|
52
|
+
// app/script runners (canonical-apps, golden-path), and the dedicated gates
|
|
53
|
+
// (parity, manifest drift, version sync). `npm test` already runs every smoke,
|
|
54
|
+
// including eval-replay-harness, fixture-compat, dogfood, security, and the
|
|
55
|
+
// per-feature smokes.
|
|
56
|
+
// `dist:check` builds from src/ AND fails closed if the committed dist/ drifted
|
|
57
|
+
// from that fresh build — strictly stronger than a bare `npm run build`.
|
|
58
|
+
{ name: "dist freshness", command: ["npm", "run", "dist:check"] },
|
|
59
|
+
{ name: "type check", command: ["npm", "run", "check"] },
|
|
60
|
+
{ name: "run-state schema consistency", command: ["node", "scripts/validate-run-state-schema.js"] },
|
|
61
|
+
{ name: "tests", command: ["npm", "test"] },
|
|
62
|
+
{ name: "canonical apps", command: ["npm", "run", "canonical-apps"] },
|
|
63
|
+
{ name: "golden path", command: ["npm", "run", "golden-path"] },
|
|
64
|
+
{ name: "CLI MCP parity", command: ["npm", "run", "parity:check"] },
|
|
65
|
+
{ name: "vendor manifest synchronization", command: ["npm", "run", "gen:manifests", "--", "--check"] },
|
|
66
|
+
{ name: "version synchronization", command: ["npm", "run", "version:sync"] },
|
|
67
|
+
// Lightweight (~50ms) standalone gate: fail closed if docs/project-index.md has
|
|
68
|
+
// drifted from a fresh source scan. The teeth-on-the-gate live in
|
|
69
|
+
// test/project-index-sync-smoke.js (run by `npm test`); this entry gives the
|
|
70
|
+
// real-doc check its own visible PASS/FAIL line in the release summary.
|
|
71
|
+
{ name: "project index sync", command: ["npm", "run", "index:check"] }
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
function main() {
|
|
75
|
+
const results = [];
|
|
76
|
+
for (const check of checks) {
|
|
77
|
+
process.stdout.write(`release:check ${check.name} ... `);
|
|
78
|
+
const started = Date.now();
|
|
79
|
+
try {
|
|
80
|
+
if (check.run) check.run();
|
|
81
|
+
else runCommand(check.command);
|
|
82
|
+
const elapsedMs = Date.now() - started;
|
|
83
|
+
results.push({ name: check.name, ok: true, elapsedMs });
|
|
84
|
+
process.stdout.write(`ok (${elapsedMs}ms)\n`);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
const elapsedMs = Date.now() - started;
|
|
87
|
+
results.push({ name: check.name, ok: false, elapsedMs, error: error.message });
|
|
88
|
+
process.stdout.write(`failed (${elapsedMs}ms)\n`);
|
|
89
|
+
process.stderr.write(`${error.message}\n`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const failed = results.filter((entry) => !entry.ok);
|
|
94
|
+
process.stdout.write("\nRelease Check Summary\n");
|
|
95
|
+
for (const result of results) {
|
|
96
|
+
process.stdout.write(`- ${result.ok ? "PASS" : "FAIL"} ${result.name}\n`);
|
|
97
|
+
}
|
|
98
|
+
process.stdout.write(`\nDry-run only: no tag, push, publish, or fixture mutation was requested.\n`);
|
|
99
|
+
if (failed.length > 0) {
|
|
100
|
+
process.stderr.write(`\n${failed.length} release-blocking check(s) failed.\n`);
|
|
101
|
+
process.exitCode = 1;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function runCommand(command) {
|
|
106
|
+
const result = spawnSync(command[0], command.slice(1), {
|
|
107
|
+
cwd: pluginRoot,
|
|
108
|
+
encoding: "utf8",
|
|
109
|
+
stdio: "pipe",
|
|
110
|
+
env: { ...process.env, CW_RELEASE_CHECK: "1" }
|
|
111
|
+
});
|
|
112
|
+
if (result.status !== 0) {
|
|
113
|
+
const output = [result.stdout, result.stderr].filter(Boolean).join("\n").trim();
|
|
114
|
+
throw new Error(`${command.join(" ")} exited ${result.status}\n${output}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
main();
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
// release-flow.js — portable, vendor-neutral release orchestrator.
|
|
5
|
+
//
|
|
6
|
+
// One script that runs the SAME gated release flow under any harness
|
|
7
|
+
// (Claude / Codex / Gemini / OpenCode) or a plain shell. It does NOT depend on
|
|
8
|
+
// any one host's agent-orchestration primitive — it is just node + git + the
|
|
9
|
+
// existing CW scripts. The only LLM step (the independent reviewer) is
|
|
10
|
+
// DELEGATED through CW's agent backend config (CW_AGENT_COMMAND / CW_AGENT_ENDPOINT),
|
|
11
|
+
// so whichever model you configure does the review. CW spawns the agent
|
|
12
|
+
// argv-style (shell:false), inherits the agent's own env/key, and imports no
|
|
13
|
+
// model SDK — the same red line as src/execution-backend.ts.
|
|
14
|
+
//
|
|
15
|
+
// Modes:
|
|
16
|
+
// node release-flow.js [--check] gate + review, no mutation (default)
|
|
17
|
+
// node release-flow.js --cut --version x.y.z [--push]
|
|
18
|
+
// also bump:version, commit verdict, tag, (push)
|
|
19
|
+
// Flags also accepted: --prev-tag <t>, --agent-command "...", --agent-model m, --dry-run
|
|
20
|
+
//
|
|
21
|
+
// Test seam: CW_RELEASE_FLOW_GATE_CMD overrides the deterministic gate command
|
|
22
|
+
// (default: `bash <thisdir>/release-gate.sh`) so the smoke can exercise the
|
|
23
|
+
// orchestration layer without re-running the full build/test suite.
|
|
24
|
+
//
|
|
25
|
+
// Zero dependency: node + git only.
|
|
26
|
+
|
|
27
|
+
const fs = require("node:fs");
|
|
28
|
+
const path = require("node:path");
|
|
29
|
+
const https = require("node:https");
|
|
30
|
+
const http = require("node:http");
|
|
31
|
+
const { spawnSync } = require("node:child_process");
|
|
32
|
+
|
|
33
|
+
const scriptsDir = __dirname;
|
|
34
|
+
const pluginRoot = path.resolve(scriptsDir, "..");
|
|
35
|
+
|
|
36
|
+
// ---- args ------------------------------------------------------------------
|
|
37
|
+
const argv = process.argv.slice(2);
|
|
38
|
+
const has = (f) => argv.includes(f);
|
|
39
|
+
const val = (f) => {
|
|
40
|
+
const i = argv.indexOf(f);
|
|
41
|
+
return i >= 0 ? argv[i + 1] : undefined;
|
|
42
|
+
};
|
|
43
|
+
const MODE_CUT = has("--cut");
|
|
44
|
+
const DRY_RUN = has("--dry-run");
|
|
45
|
+
const PUSH = has("--push");
|
|
46
|
+
const cutVersion = val("--version");
|
|
47
|
+
const prevTagArg = val("--prev-tag");
|
|
48
|
+
|
|
49
|
+
function die(msg, extra) {
|
|
50
|
+
process.stderr.write(`release-flow: ${msg}\n`);
|
|
51
|
+
if (extra) process.stderr.write(`${extra}\n`);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
function say(msg) {
|
|
55
|
+
process.stdout.write(`${msg}\n`);
|
|
56
|
+
}
|
|
57
|
+
function git(args, opts = {}) {
|
|
58
|
+
const r = spawnSync("git", args, { cwd: repoRoot, encoding: "utf8", ...opts });
|
|
59
|
+
return { code: r.status, out: (r.stdout || "").trim(), err: (r.stderr || "").trim() };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ---- repo + revision context ----------------------------------------------
|
|
63
|
+
const top = spawnSync("git", ["rev-parse", "--show-toplevel"], { encoding: "utf8" });
|
|
64
|
+
if (top.status !== 0) die("not inside a git work tree");
|
|
65
|
+
const repoRoot = top.stdout.trim();
|
|
66
|
+
|
|
67
|
+
const HEAD = git(["rev-parse", "HEAD"]).out;
|
|
68
|
+
// Previous tag, excluding any tag that already points at HEAD (so this works
|
|
69
|
+
// whether run before tagging or re-run on a freshly tagged commit — same fix
|
|
70
|
+
// as release-gate.sh).
|
|
71
|
+
function resolvePrevTag() {
|
|
72
|
+
if (prevTagArg) return prevTagArg;
|
|
73
|
+
const headTags = git(["tag", "--points-at", "HEAD"]).out.split("\n").filter(Boolean);
|
|
74
|
+
let prev = git(["describe", "--tags", "--abbrev=0"]).out;
|
|
75
|
+
if (prev && headTags.includes(prev)) prev = git(["describe", "--tags", "--abbrev=0", "HEAD^"]).out;
|
|
76
|
+
return prev || "";
|
|
77
|
+
}
|
|
78
|
+
const PREV_TAG = resolvePrevTag();
|
|
79
|
+
|
|
80
|
+
// ---- 1. deterministic gate -------------------------------------------------
|
|
81
|
+
function runGate() {
|
|
82
|
+
const override = (process.env.CW_RELEASE_FLOW_GATE_CMD || "").trim();
|
|
83
|
+
say("[1/3] deterministic gate");
|
|
84
|
+
let r;
|
|
85
|
+
if (override) {
|
|
86
|
+
// Test/override seam — run via the shell intentionally; this path is for
|
|
87
|
+
// the smoke and operator overrides only, never the delegated agent.
|
|
88
|
+
r = spawnSync(override, { cwd: repoRoot, encoding: "utf8", shell: true, stdio: "inherit" });
|
|
89
|
+
} else {
|
|
90
|
+
r = spawnSync("bash", [path.join(scriptsDir, "release-gate.sh")], {
|
|
91
|
+
cwd: repoRoot,
|
|
92
|
+
encoding: "utf8",
|
|
93
|
+
stdio: "inherit"
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
if (r.status !== 0) die("deterministic gate FAILED — fix findings in normal cycles, do not retry the release here.");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ---- 2. independent reviewer, delegated to the configured agent -------------
|
|
100
|
+
function reviewerPromptBody() {
|
|
101
|
+
// Reuse the committed reviewer spec as the prompt; strip YAML frontmatter.
|
|
102
|
+
const specPath = path.join(pluginRoot, "agents", "release-reviewer.md");
|
|
103
|
+
let body = fs.existsSync(specPath) ? fs.readFileSync(specPath, "utf8") : "";
|
|
104
|
+
body = body.replace(/^---[\s\S]*?---\n/, "");
|
|
105
|
+
return body.trim();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function buildReviewerInput(resultPath) {
|
|
109
|
+
const action = MODE_CUT ? `tag v${cutVersion || "<next>"}` : "check (no tag)";
|
|
110
|
+
return [
|
|
111
|
+
reviewerPromptBody(),
|
|
112
|
+
"",
|
|
113
|
+
"---",
|
|
114
|
+
"## Release candidate context (derive/verify everything yourself)",
|
|
115
|
+
`- HEAD: ${HEAD}`,
|
|
116
|
+
`- Previous tag: ${PREV_TAG || "(none)"}`,
|
|
117
|
+
`- Diff range: ${PREV_TAG ? `${PREV_TAG}..HEAD` : "(no previous tag)"}`,
|
|
118
|
+
`- Proposed action: ${action}`,
|
|
119
|
+
`- Repo root (run git here): ${repoRoot}`,
|
|
120
|
+
"",
|
|
121
|
+
"## Required output — write ONLY this file, nothing else",
|
|
122
|
+
`Write your verdict to this exact path:`,
|
|
123
|
+
` ${resultPath}`,
|
|
124
|
+
"First line MUST be exactly one of:",
|
|
125
|
+
` APPROVED ${HEAD}`,
|
|
126
|
+
" <one concrete sentence: the user-visible capability this ships>",
|
|
127
|
+
"or:",
|
|
128
|
+
" REJECTED",
|
|
129
|
+
" <numbered gate failures with file:line references>",
|
|
130
|
+
""
|
|
131
|
+
].join("\n");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function substitute(arg, map) {
|
|
135
|
+
return arg.replace(/\{\{(\w+)\}\}/g, (m, k) => (k in map ? String(map[k]) : m));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function delegateReview(resultPath, inputPath) {
|
|
139
|
+
// Reuse the canonical agent-config resolver (flags > env > file).
|
|
140
|
+
let resolveAgentConfig;
|
|
141
|
+
try {
|
|
142
|
+
({ resolveAgentConfig } = require(path.join(pluginRoot, "dist", "agent-config.js")));
|
|
143
|
+
} catch (e) {
|
|
144
|
+
die("cannot load dist/agent-config.js — run `npm run build` first", String(e));
|
|
145
|
+
}
|
|
146
|
+
const cfg = resolveAgentConfig(
|
|
147
|
+
{ "agent-command": val("--agent-command"), "agent-model": val("--agent-model") },
|
|
148
|
+
process.env
|
|
149
|
+
);
|
|
150
|
+
if (cfg.source === "none" || (!cfg.command && !cfg.endpoint)) {
|
|
151
|
+
die(
|
|
152
|
+
"no reviewer agent configured. Set one of:\n" +
|
|
153
|
+
' CW_AGENT_COMMAND="claude -p {{input}}" (or codex exec / gemini -p / opencode run)\n' +
|
|
154
|
+
" CW_AGENT_ENDPOINT=https://... (HTTP agent, e.g. DeepSeek)\n" +
|
|
155
|
+
" or pass --agent-command. CW delegates the review; it never runs a model itself."
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const subMap = {
|
|
160
|
+
input: inputPath,
|
|
161
|
+
manifest: inputPath,
|
|
162
|
+
result: resultPath,
|
|
163
|
+
workerDir: repoRoot,
|
|
164
|
+
model: cfg.model || "",
|
|
165
|
+
prompt: fs.readFileSync(inputPath, "utf8")
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
if (cfg.command) {
|
|
169
|
+
const args = (cfg.args || []).map((a) => substitute(a, subMap));
|
|
170
|
+
say(`[2/3] reviewer — delegating to: ${cfg.command} ${(cfg.args || []).join(" ")} (model: ${cfg.model || "unreported"})`);
|
|
171
|
+
// RED LINE: argv-style, shell:false. The agent runs the model in its own
|
|
172
|
+
// process and inherits its own credentials; CW holds none.
|
|
173
|
+
const r = spawnSync(cfg.command, args, {
|
|
174
|
+
cwd: repoRoot,
|
|
175
|
+
env: { ...process.env },
|
|
176
|
+
encoding: "utf8",
|
|
177
|
+
timeout: cfg.timeoutMs || 600000,
|
|
178
|
+
shell: false,
|
|
179
|
+
stdio: "inherit"
|
|
180
|
+
});
|
|
181
|
+
if (r.status !== 0) die(`reviewer agent exited ${r.status === null ? "(timeout/no-exit)" : r.status} — no verdict trusted.`);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Endpoint mode (e.g. DeepSeek HTTP): POST the prompt, write the response as
|
|
186
|
+
// the verdict. The remote agent cannot write our local file, so we persist
|
|
187
|
+
// its returned text ourselves.
|
|
188
|
+
say(`[2/3] reviewer — POSTing to endpoint ${cfg.endpoint} (model: ${cfg.model || "unreported"})`);
|
|
189
|
+
const body = JSON.stringify({ prompt: subMap.prompt, model: cfg.model, sha: HEAD });
|
|
190
|
+
const lib = cfg.endpoint.startsWith("https:") ? https : http;
|
|
191
|
+
const text = postSync(lib, cfg.endpoint, body, cfg.timeoutMs || 600000);
|
|
192
|
+
if (text === null) die("reviewer endpoint call failed — no verdict trusted.");
|
|
193
|
+
fs.writeFileSync(resultPath, text.endsWith("\n") ? text : `${text}\n`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Minimal synchronous-ish POST using a child node process would overcomplicate;
|
|
197
|
+
// use a blocking wait via Atomics on a SharedArrayBuffer-free approach: do a
|
|
198
|
+
// simple promise + deasync-free busy loop is not available, so use execFileSync
|
|
199
|
+
// with curl ONLY if present; otherwise instruct the operator to use command mode.
|
|
200
|
+
function postSync(lib, endpoint, body, timeoutMs) {
|
|
201
|
+
const curl = spawnSync("curl", [
|
|
202
|
+
"-sS", "--max-time", String(Math.ceil(timeoutMs / 1000)),
|
|
203
|
+
"-X", "POST", "-H", "Content-Type: application/json",
|
|
204
|
+
"--data-binary", body, endpoint
|
|
205
|
+
], { encoding: "utf8" });
|
|
206
|
+
if (curl.error || curl.status !== 0) {
|
|
207
|
+
process.stderr.write(curl.stderr || String(curl.error || "curl failed") + "\n");
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
return curl.stdout || "";
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function verifyVerdict(resultPath) {
|
|
214
|
+
say("[3/3] verify verdict");
|
|
215
|
+
if (!fs.existsSync(resultPath)) die(`no verdict written to ${path.relative(repoRoot, resultPath)} — fail closed.`);
|
|
216
|
+
const text = fs.readFileSync(resultPath, "utf8");
|
|
217
|
+
const lines = text.split(/\r?\n/);
|
|
218
|
+
const firstLine = lines[0] || "";
|
|
219
|
+
if (firstLine !== `APPROVED ${HEAD}`) {
|
|
220
|
+
die(`verdict first line must be exactly "APPROVED ${HEAD}" — release blocked.`, text.trim());
|
|
221
|
+
}
|
|
222
|
+
const cap = (lines[1] || "").trim();
|
|
223
|
+
return cap;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ---- 3. optional cut (bump + commit verdict + tag + push) ------------------
|
|
227
|
+
function cut(resultPath, capability) {
|
|
228
|
+
if (!cutVersion || !/^\d+\.\d+\.\d+$/.test(cutVersion)) die("--cut requires --version x.y.z");
|
|
229
|
+
if (DRY_RUN) { say(`[dry-run] would: bump:version ${cutVersion}, commit verdict, tag v${cutVersion}${PUSH ? ", push" : ""}`); return; }
|
|
230
|
+
const bump = spawnSync("npm", ["run", "bump:version", "--", cutVersion], { cwd: pluginRoot, encoding: "utf8", stdio: "inherit" });
|
|
231
|
+
if (bump.status !== 0) die("bump:version failed");
|
|
232
|
+
// Regenerate the gated project index after the version bump (PR #87 gate).
|
|
233
|
+
spawnSync("npm", ["run", "sync:project-index", "--", "--repo-only"], { cwd: pluginRoot, stdio: "inherit" });
|
|
234
|
+
git(["add", "-A"]);
|
|
235
|
+
const commit = git(["commit", "-m", `chore(release): record APPROVED reviewer verdict for v${cutVersion}`]);
|
|
236
|
+
if (commit.code !== 0) die("verdict commit failed", commit.err);
|
|
237
|
+
const tag = git(["tag", "-a", `v${cutVersion}`, "-m", `v${cutVersion}: ${capability || "release"}`]);
|
|
238
|
+
if (tag.code !== 0) die("git tag failed", tag.err);
|
|
239
|
+
if (PUSH) {
|
|
240
|
+
git(["push", "origin", "HEAD"]);
|
|
241
|
+
git(["push", "origin", `v${cutVersion}`]);
|
|
242
|
+
}
|
|
243
|
+
say(`tagged v${cutVersion}${PUSH ? " and pushed" : " (local only; push when ready)"}`);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// ---- main ------------------------------------------------------------------
|
|
247
|
+
function main() {
|
|
248
|
+
if (!HEAD) die("could not resolve HEAD");
|
|
249
|
+
const markerDir = path.join(repoRoot, ".cw-release");
|
|
250
|
+
fs.mkdirSync(markerDir, { recursive: true });
|
|
251
|
+
const resultPath = path.join(markerDir, `review-${HEAD}.verdict`);
|
|
252
|
+
const inputPath = path.join(markerDir, `review-input-${HEAD}.md`);
|
|
253
|
+
|
|
254
|
+
runGate();
|
|
255
|
+
fs.writeFileSync(inputPath, buildReviewerInput(resultPath));
|
|
256
|
+
delegateReview(resultPath, inputPath);
|
|
257
|
+
const capability = verifyVerdict(resultPath);
|
|
258
|
+
|
|
259
|
+
if (MODE_CUT) cut(resultPath, capability);
|
|
260
|
+
|
|
261
|
+
process.stdout.write(`${JSON.stringify({
|
|
262
|
+
ok: true,
|
|
263
|
+
mode: MODE_CUT ? "cut" : "check",
|
|
264
|
+
head: HEAD,
|
|
265
|
+
prevTag: PREV_TAG || null,
|
|
266
|
+
verdict: "APPROVED",
|
|
267
|
+
capability,
|
|
268
|
+
tagged: MODE_CUT && !DRY_RUN ? `v${cutVersion}` : null
|
|
269
|
+
}, null, 2)}\n`);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
main();
|