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
|
@@ -83,7 +83,7 @@ const canonicalApps = [
|
|
|
83
83
|
"--source",
|
|
84
84
|
"plugins/cool-workflow/docs/workflow-app-framework.7.md",
|
|
85
85
|
"--scope",
|
|
86
|
-
"Cool Workflow v0.2.
|
|
86
|
+
"Cool Workflow v0.2.4",
|
|
87
87
|
"--freshness",
|
|
88
88
|
"as of release preparation"
|
|
89
89
|
]
|
|
@@ -117,14 +117,14 @@ function main() {
|
|
|
117
117
|
assert.ok(summary, `${app.id} must appear in app list`);
|
|
118
118
|
assert.equal(summary.sourceKind, "app-directory");
|
|
119
119
|
assert.equal(summary.legacy, false);
|
|
120
|
-
assert.equal(summary.version, "0.2.
|
|
120
|
+
assert.equal(summary.version, "0.2.4");
|
|
121
121
|
|
|
122
122
|
const validation = runJson(["app", "validate", manifestPath]);
|
|
123
123
|
assert.equal(validation.valid, true, `${app.id} manifest must validate`);
|
|
124
124
|
|
|
125
125
|
const shown = runJson(["app", "show", app.id]);
|
|
126
126
|
assert.equal(shown.app.id, app.id);
|
|
127
|
-
assert.equal(shown.app.version, "0.2.
|
|
127
|
+
assert.equal(shown.app.version, "0.2.4");
|
|
128
128
|
assert.ok(shown.app.metadata.canonical, `${app.id} must be marked canonical`);
|
|
129
129
|
assert.ok(shown.app.sandboxProfiles.length > 0, `${app.id} must declare sandbox profiles`);
|
|
130
130
|
assertTaskIdsUnique(shown);
|
|
@@ -135,7 +135,7 @@ function main() {
|
|
|
135
135
|
const plan = runJson(["plan", app.id, ...app.args(workspace)]);
|
|
136
136
|
const state = JSON.parse(fs.readFileSync(plan.statePath, "utf8"));
|
|
137
137
|
assert.equal(state.workflow.app.id, app.id);
|
|
138
|
-
assert.equal(state.workflow.app.version, "0.2.
|
|
138
|
+
assert.equal(state.workflow.app.version, "0.2.4");
|
|
139
139
|
assert.equal(state.workflow.app.metadata.canonical, true);
|
|
140
140
|
assert.ok(state.tasks.some((task) => task.requiresEvidence), `${app.id} plan must include evidence gates`);
|
|
141
141
|
assert.ok(state.tasks.every((task) => task.sandboxProfileId), `${app.id} plan must include sandbox hints`);
|
|
@@ -6,7 +6,7 @@ const fs = require("node:fs");
|
|
|
6
6
|
const path = require("node:path");
|
|
7
7
|
const { CoolWorkflowRunner } = require("../dist/shell/orchestrator.js");
|
|
8
8
|
|
|
9
|
-
const TARGET_VERSION = "0.2.
|
|
9
|
+
const TARGET_VERSION = "0.2.4";
|
|
10
10
|
const PREVIOUS_VERSION = "0.1.31";
|
|
11
11
|
const pluginRoot = path.resolve(__dirname, "..");
|
|
12
12
|
const repoRoot = path.resolve(pluginRoot, "..", "..");
|
package/scripts/golden-path.js
CHANGED
|
@@ -33,7 +33,7 @@ function main() {
|
|
|
33
33
|
const appValidation = runJson(["app", "validate", "end-to-end-golden-path"], pluginRoot);
|
|
34
34
|
assert.equal(appValidation.valid, true);
|
|
35
35
|
assert.equal(appValidation.summary.id, "end-to-end-golden-path");
|
|
36
|
-
assert.equal(appValidation.summary.version, "0.2.
|
|
36
|
+
assert.equal(appValidation.summary.version, "0.2.4");
|
|
37
37
|
|
|
38
38
|
const plan = runJson(
|
|
39
39
|
[
|
|
@@ -42,7 +42,7 @@ function main() {
|
|
|
42
42
|
"--repo",
|
|
43
43
|
tmp,
|
|
44
44
|
"--question",
|
|
45
|
-
"Prove the deterministic v0.2.
|
|
45
|
+
"Prove the deterministic v0.2.4 end-to-end golden path."
|
|
46
46
|
],
|
|
47
47
|
pluginRoot
|
|
48
48
|
);
|
|
@@ -52,7 +52,7 @@ function main() {
|
|
|
52
52
|
|
|
53
53
|
let state = readJson(plan.statePath);
|
|
54
54
|
assert.equal(state.workflow.app.id, "end-to-end-golden-path");
|
|
55
|
-
assert.equal(state.workflow.app.version, "0.2.
|
|
55
|
+
assert.equal(state.workflow.app.version, "0.2.4");
|
|
56
56
|
assert.equal(state.loopStage, "interpret");
|
|
57
57
|
|
|
58
58
|
const dispatch = runJson(["dispatch", plan.runId, "--limit", "1", "--sandbox", "readonly"], tmp);
|
|
@@ -195,7 +195,7 @@ function main() {
|
|
|
195
195
|
assert.equal(reportPath, plan.reportPath);
|
|
196
196
|
assert.ok(fs.existsSync(reportPath));
|
|
197
197
|
const report = fs.readFileSync(reportPath, "utf8");
|
|
198
|
-
assert.match(report, /Workflow App: end-to-end-golden-path@0\.2\.
|
|
198
|
+
assert.match(report, /Workflow App: end-to-end-golden-path@0\.2\.4/);
|
|
199
199
|
assert.match(report, /## Candidates/);
|
|
200
200
|
assert.match(report, /## Trust Audit/);
|
|
201
201
|
assert.match(report, /## Acceptance Rationale/);
|
|
@@ -36,36 +36,6 @@
|
|
|
36
36
|
"layerViolations": {
|
|
37
37
|
"core/capability-table.ts": [
|
|
38
38
|
"../wiring/capability-table"
|
|
39
|
-
],
|
|
40
|
-
"wiring/capability-table/basics.ts": [
|
|
41
|
-
"../../cli/io"
|
|
42
|
-
],
|
|
43
|
-
"wiring/capability-table/exec-backend.ts": [
|
|
44
|
-
"../../cli/io"
|
|
45
|
-
],
|
|
46
|
-
"wiring/capability-table/multi-agent.ts": [
|
|
47
|
-
"../../cli/io"
|
|
48
|
-
],
|
|
49
|
-
"wiring/capability-table/pipeline.ts": [
|
|
50
|
-
"../../cli/io"
|
|
51
|
-
],
|
|
52
|
-
"wiring/capability-table/registry-core.ts": [
|
|
53
|
-
"../../cli/io"
|
|
54
|
-
],
|
|
55
|
-
"wiring/capability-table/reporting.ts": [
|
|
56
|
-
"../../cli/io"
|
|
57
|
-
],
|
|
58
|
-
"wiring/capability-table/scheduling-registry.ts": [
|
|
59
|
-
"../../cli/io"
|
|
60
|
-
],
|
|
61
|
-
"wiring/capability-table/state.ts": [
|
|
62
|
-
"../../cli/io"
|
|
63
|
-
],
|
|
64
|
-
"wiring/capability-table/trust-ledger.ts": [
|
|
65
|
-
"../../cli/io"
|
|
66
|
-
],
|
|
67
|
-
"wiring/capability-table/workflow-apps.ts": [
|
|
68
|
-
"../../cli/io"
|
|
69
39
|
]
|
|
70
40
|
}
|
|
71
41
|
}
|
package/scripts/release-flow.js
CHANGED
|
@@ -15,15 +15,22 @@
|
|
|
15
15
|
// Modes:
|
|
16
16
|
// node release-flow.js [--check] gate + review, no mutation (default)
|
|
17
17
|
// node release-flow.js --cut --version x.y.z [--push]
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
18
|
+
// preflight (fail-fast checks BEFORE the
|
|
19
|
+
// gate/vendor/reviewer spend anything),
|
|
20
|
+
// then gate + review, then bump:version,
|
|
21
|
+
// commit verdict, tag, and — when --push —
|
|
22
|
+
// push ONLY the tag (refs/tags/vx.y.z; the
|
|
23
|
+
// verdict commit is a one-hop leaf off the
|
|
24
|
+
// reviewed main tip, it never lands on a
|
|
25
|
+
// branch) and create the GitHub Release
|
|
26
|
+
// (idempotent; opt out with --no-release)
|
|
22
27
|
// node release-flow.js --release --version x.y.z create-or-skip the GitHub Release for an
|
|
23
28
|
// already-pushed tag (backfill); no gate/cut.
|
|
24
29
|
// Fails closed if gh can't create it; add
|
|
25
30
|
// --soft for best-effort (skip-not-fail).
|
|
26
|
-
// Flags also accepted: --prev-tag <t>, --agent-command "...", --agent-model m, --dry-run
|
|
31
|
+
// Flags also accepted: --prev-tag <t>, --agent-command "...", --agent-model m, --dry-run,
|
|
32
|
+
// --preflight-only (run the cut preflight and stop — used by release-oneclick.js),
|
|
33
|
+
// --allow-stale-head (skip the HEAD == origin/main tip check, deliberate re-cuts only)
|
|
27
34
|
//
|
|
28
35
|
// VERDICT CONTRACT (auto-generated by --cut, never write by hand):
|
|
29
36
|
// Path: .cw-release/review-<FULLSHA>.verdict
|
|
@@ -450,13 +457,22 @@ function prevTagOf(version) {
|
|
|
450
457
|
return git(["describe", "--tags", "--abbrev=0", `v${version}^`]).out || "";
|
|
451
458
|
}
|
|
452
459
|
|
|
460
|
+
// Full regex escape (same shape as parity-check.js's helper — scripts stay
|
|
461
|
+
// zero-dependency single files, so it is copied, not required). The version
|
|
462
|
+
// is SEMVER-validated on every path that reaches here, but escaping every
|
|
463
|
+
// metacharacter costs nothing and keeps the RegExp construction safe on its
|
|
464
|
+
// own terms.
|
|
465
|
+
function escapeRegExp(value) {
|
|
466
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
467
|
+
}
|
|
468
|
+
|
|
453
469
|
// Extract the `## <version>` section body from the CHANGELOG AS SHIPPED AT THE
|
|
454
470
|
// TAG (git show), so the notes reflect what that tag actually carried.
|
|
455
471
|
function changelogSection(version) {
|
|
456
472
|
const show = git(["show", `v${version}:CHANGELOG.md`]);
|
|
457
473
|
if (show.code !== 0 || !show.out) return "";
|
|
458
474
|
const lines = show.out.split(/\r?\n/);
|
|
459
|
-
const esc = version
|
|
475
|
+
const esc = escapeRegExp(version);
|
|
460
476
|
const startRe = new RegExp(`^## \\[?${esc}\\b`);
|
|
461
477
|
let start = -1;
|
|
462
478
|
for (let i = 0; i < lines.length; i++) if (startRe.test(lines[i])) { start = i; break; }
|
|
@@ -558,10 +574,102 @@ function releaseGitHub(version, { required = false } = {}) {
|
|
|
558
574
|
return true;
|
|
559
575
|
}
|
|
560
576
|
|
|
577
|
+
// ---- 2b. cut preflight (fail fast, BEFORE any paid step) --------------------
|
|
578
|
+
// Every check here answers one question: "will this cut die AFTER the gate,
|
|
579
|
+
// the live vendor preflight, and the reviewer have already run?" Each check
|
|
580
|
+
// was a real post-reviewer failure in the v0.2.3 cut (2026-07-10) — a missing
|
|
581
|
+
// --version, an unsigned verdict the CI gate then rejected, a left-over local
|
|
582
|
+
// tag, and a two-hop recovery that broke the HEAD~1 topology rule. All of
|
|
583
|
+
// them are answerable in under a second, so answer them FIRST.
|
|
584
|
+
//
|
|
585
|
+
// Fixture safety: each check is gated on the thing it checks being present
|
|
586
|
+
// (a committed signing pubkey, a CHANGELOG.md, an `origin` remote), so the
|
|
587
|
+
// smoke fixtures — bare repos with none of those — pass through untouched.
|
|
588
|
+
function preflightCut() {
|
|
589
|
+
say("[0/3] cut preflight");
|
|
590
|
+
// (a) version — cheap and load-bearing for every later step. Checking it
|
|
591
|
+
// here (not only inside cut()) is what stops a full gate+reviewer run from
|
|
592
|
+
// being thrown away over a missing flag.
|
|
593
|
+
if (!cutVersion || !/^\d+\.\d+\.\d+$/.test(cutVersion)) die("--cut requires --version x.y.z");
|
|
594
|
+
|
|
595
|
+
// (b) signing — once .cw-release/verdict-signing.pub is committed, CI's
|
|
596
|
+
// release-gate REQUIRES a valid signature; a cut without the private key
|
|
597
|
+
// configured would push an unsigned verdict that fails only in CI. Fail
|
|
598
|
+
// closed here instead, and also prove the configured key IS the committed
|
|
599
|
+
// key (a wrong key signs "successfully" and still fails in CI).
|
|
600
|
+
const pubPath = path.join(repoRoot, ".cw-release", "verdict-signing.pub");
|
|
601
|
+
if (fs.existsSync(pubPath)) {
|
|
602
|
+
const raw = process.env.CW_RELEASE_VERDICT_PRIVKEY;
|
|
603
|
+
if (!raw || !raw.trim()) {
|
|
604
|
+
die(
|
|
605
|
+
"verdict-signing.pub is committed, so CI requires a SIGNED verdict — set the private key first",
|
|
606
|
+
'fix: export CW_RELEASE_VERDICT_PRIVKEY=~/.cw-keys/verdict-signing.key'
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
const pem = resolveVerdictPrivateKey(raw);
|
|
610
|
+
let key;
|
|
611
|
+
try {
|
|
612
|
+
key = crypto.createPrivateKey(pem);
|
|
613
|
+
} catch (error) {
|
|
614
|
+
die(`CW_RELEASE_VERDICT_PRIVKEY is not a readable private key: ${error.message}`);
|
|
615
|
+
}
|
|
616
|
+
if (key.asymmetricKeyType !== "ed25519") {
|
|
617
|
+
die(`CW_RELEASE_VERDICT_PRIVKEY is a ${key.asymmetricKeyType || "unknown"} key, not ed25519 — generate one with scripts/verdict-keygen.js`);
|
|
618
|
+
}
|
|
619
|
+
const derivedPub = crypto.createPublicKey(key).export({ type: "spki", format: "pem" }).toString().trim();
|
|
620
|
+
const committedPub = fs.readFileSync(pubPath, "utf8").trim();
|
|
621
|
+
if (derivedPub !== committedPub) {
|
|
622
|
+
die("CW_RELEASE_VERDICT_PRIVKEY does not match the committed .cw-release/verdict-signing.pub — wrong key; CI would reject its signature");
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// (c) tag must not exist yet — a stale local tag makes cut() die at the very
|
|
627
|
+
// last step ("tag 'vX.Y.Z' already exists") after everything else has run.
|
|
628
|
+
if (git(["tag", "-l", `v${cutVersion}`]).out) {
|
|
629
|
+
die(`tag v${cutVersion} already exists locally — delete it first (git tag -d v${cutVersion}) or pick the next version`);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// (d) CHANGELOG must already carry the release section — release notes get
|
|
633
|
+
// pasted verbatim into the GitHub Release, and bump:version's content gate
|
|
634
|
+
// would only catch this inside cut(), after the reviewer.
|
|
635
|
+
const changelogPath = path.join(repoRoot, "CHANGELOG.md");
|
|
636
|
+
if (fs.existsSync(changelogPath) && !fs.readFileSync(changelogPath, "utf8").includes(`## ${cutVersion}`)) {
|
|
637
|
+
die(`CHANGELOG.md has no "## ${cutVersion}" section — write the release notes first`);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// (e) clean tree (tracked files only — cut()'s `git add -u` can never sweep
|
|
641
|
+
// an untracked stray in, so untracked files are not a cut hazard).
|
|
642
|
+
const dirty = git(["status", "--porcelain", "-uno"]).out;
|
|
643
|
+
if (dirty) die("working tree has tracked modifications — commit or stash them before a cut", dirty);
|
|
644
|
+
|
|
645
|
+
// (f) network-dependent checks, only when this cut will actually push and
|
|
646
|
+
// only when an `origin` remote exists (smoke fixtures have none):
|
|
647
|
+
if (PUSH && git(["remote"]).out.split("\n").includes("origin")) {
|
|
648
|
+
// remote tag must not exist (someone/some run already published this version)
|
|
649
|
+
const remoteTag = git(["ls-remote", "origin", `refs/tags/v${cutVersion}`]);
|
|
650
|
+
if (remoteTag.code === 0 && remoteTag.out) {
|
|
651
|
+
die(`tag v${cutVersion} already exists on origin — this version is already published; pick the next version`);
|
|
652
|
+
}
|
|
653
|
+
// HEAD must be the current origin/main tip — cutting a stale HEAD publishes
|
|
654
|
+
// old code (nearly happened on the v0.2.2 cut).
|
|
655
|
+
if (git(["fetch", "origin", "main", "--quiet"]).code !== 0) {
|
|
656
|
+
die("git fetch origin main failed — a --push cut needs the network anyway; fix connectivity first");
|
|
657
|
+
}
|
|
658
|
+
const mainTip = git(["rev-parse", "origin/main"]).out;
|
|
659
|
+
if (mainTip !== HEAD && !has("--allow-stale-head")) {
|
|
660
|
+
die(
|
|
661
|
+
`HEAD (${HEAD.slice(0, 12)}) is not the origin/main tip (${mainTip.slice(0, 12)}) — a cut must release exactly what main has`,
|
|
662
|
+
"fix: git fetch origin main && git checkout -B <branch> origin/main (or pass --allow-stale-head if this is deliberate)"
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
say("cut preflight OK");
|
|
667
|
+
}
|
|
668
|
+
|
|
561
669
|
// ---- 3. optional cut (bump + commit verdict + tag + push) ------------------
|
|
562
670
|
function cut(resultPath, capability) {
|
|
563
671
|
if (!cutVersion || !/^\d+\.\d+\.\d+$/.test(cutVersion)) die("--cut requires --version x.y.z");
|
|
564
|
-
if (DRY_RUN) { say(`[dry-run] would: bump:version ${cutVersion}, commit verdict, tag v${cutVersion}${PUSH ?
|
|
672
|
+
if (DRY_RUN) { say(`[dry-run] would: bump:version ${cutVersion}, commit verdict, tag v${cutVersion}${PUSH ? `, push refs/tags/v${cutVersion} (tag only, no branch)` : ""}`); return; }
|
|
565
673
|
const bump = spawnSync("npm", ["run", "bump:version", "--", cutVersion], { cwd: pluginRoot, encoding: "utf8", stdio: "inherit" });
|
|
566
674
|
if (bump.status !== 0) die("bump:version failed");
|
|
567
675
|
// Regenerate the gated project index after the version bump (PR #87 gate).
|
|
@@ -585,11 +693,16 @@ function cut(resultPath, capability) {
|
|
|
585
693
|
const tag = git(["tag", "-a", `v${cutVersion}`, "-m", `v${cutVersion}: ${capability || "release"}`]);
|
|
586
694
|
if (tag.code !== 0) die("git tag failed", tag.err);
|
|
587
695
|
if (PUSH) {
|
|
588
|
-
//
|
|
589
|
-
//
|
|
590
|
-
//
|
|
591
|
-
|
|
592
|
-
|
|
696
|
+
// Push ONLY the tag. The verdict commit is a one-hop leaf on the reviewed
|
|
697
|
+
// main-tip commit, reachable through the tag alone — it does not need to
|
|
698
|
+
// be on any branch. Pushing HEAD here used to (a) hit main's branch
|
|
699
|
+
// protection (enforce_admins blocks even the owner's direct push) or
|
|
700
|
+
// (b) mint a stray remote branch named after whatever branch the cut ran
|
|
701
|
+
// from. Tags are not protected in this repo, and CI's release-gate checks
|
|
702
|
+
// the TAG's own history (verdict at HEAD or HEAD~1), so the tag alone is
|
|
703
|
+
// the complete release artifact — proven live by the v0.2.3 cut.
|
|
704
|
+
const push = git(["push", "origin", `refs/tags/v${cutVersion}`]);
|
|
705
|
+
if (push.code !== 0) die("tag push failed (nothing was pushed)", push.err);
|
|
593
706
|
}
|
|
594
707
|
say(`tagged v${cutVersion}${PUSH ? " and pushed" : " (local only; push when ready)"}`);
|
|
595
708
|
// Finishing step: create the GitHub Release for the just-pushed tag. Only when
|
|
@@ -614,6 +727,18 @@ function main() {
|
|
|
614
727
|
return;
|
|
615
728
|
}
|
|
616
729
|
|
|
730
|
+
// Fail-fast checks FIRST — nothing below (gate, live vendor calls, the
|
|
731
|
+
// reviewer) runs until every cheap precondition holds. --preflight-only
|
|
732
|
+
// lets an orchestrator (release-oneclick.js) run just this step. Without
|
|
733
|
+
// --cut there is no preflight to run, so answering ok:true would be a
|
|
734
|
+
// green light with zero checks done — refuse instead.
|
|
735
|
+
if (has("--preflight-only") && !MODE_CUT) die("--preflight-only requires --cut (there is no preflight to run in check/release mode)");
|
|
736
|
+
if (MODE_CUT) preflightCut();
|
|
737
|
+
if (has("--preflight-only")) {
|
|
738
|
+
process.stdout.write(`${JSON.stringify({ ok: true, mode: "preflight", version: cutVersion || null }, null, 2)}\n`);
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
|
|
617
742
|
const markerDir = path.join(repoRoot, ".cw-release");
|
|
618
743
|
fs.mkdirSync(markerDir, { recursive: true });
|
|
619
744
|
const resultPath = path.join(markerDir, `review-${HEAD}.verdict`);
|