cool-workflow 0.2.2 → 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 +13 -6
- package/dist/cli/entry.js +41 -1
- package/dist/cli/io.js +12 -22
- package/dist/cli/parseargv.js +1 -0
- package/dist/core/capability-data.js +3 -2
- package/dist/core/format/completion.js +68 -0
- package/dist/core/format/help.js +31 -6
- package/dist/core/format/safe-json.js +73 -0
- package/dist/core/format/state-explosion-text.js +1 -1
- package/dist/core/multi-agent/candidate-scoring.js +5 -1
- package/dist/core/multi-agent/collaboration.js +3 -3
- package/dist/core/multi-agent/coordinator.js +5 -5
- package/dist/core/multi-agent/runtime.js +4 -4
- package/dist/core/multi-agent/topology.js +3 -3
- package/dist/core/pipeline/dispatch.js +18 -4
- package/dist/core/pipeline/drive-decide.js +2 -1
- package/dist/core/state/migrations.js +16 -1
- package/dist/core/state/state-explosion/digest.js +13 -13
- package/dist/core/state/state-explosion/graph.js +17 -11
- package/dist/core/state/state-explosion/report.js +6 -6
- package/dist/core/util/cli-args.js +33 -0
- package/dist/core/util/numeric-flag.js +40 -0
- package/dist/core/version.js +1 -1
- package/dist/mcp/server.js +99 -11
- package/dist/shell/audit-cli.js +57 -25
- package/dist/shell/coordinator-io.js +73 -13
- package/dist/shell/dispatch.js +1 -1
- package/dist/shell/doctor.js +80 -1
- package/dist/shell/drive.js +251 -49
- package/dist/shell/eval-text.js +2 -2
- package/dist/shell/evidence-reasoning.js +4 -4
- package/dist/shell/execution-backend/agent.js +30 -2
- package/dist/shell/execution-backend/container.js +4 -1
- package/dist/shell/execution-backend/local.js +19 -11
- package/dist/shell/feedback-cli.js +6 -6
- package/dist/shell/fs-atomic.js +218 -29
- package/dist/shell/man-cli.js +6 -0
- package/dist/shell/metrics-cli.js +2 -1
- package/dist/shell/multi-agent-cli.js +367 -323
- package/dist/shell/multi-agent-host.js +9 -9
- package/dist/shell/multi-agent-operator-ux.js +80 -38
- package/dist/shell/node-store.js +10 -4
- package/dist/shell/observability.js +1 -1
- package/dist/shell/operator-ux-text.js +22 -22
- package/dist/shell/operator-ux.js +15 -14
- package/dist/shell/orchestrator.js +49 -38
- package/dist/shell/pipeline-cli.js +107 -42
- package/dist/shell/reclamation-io.js +88 -10
- package/dist/shell/registry-cli.js +23 -17
- package/dist/shell/remote-source.js +13 -8
- package/dist/shell/report-cli.js +45 -0
- package/dist/shell/report.js +2 -1
- package/dist/shell/run-registry-io.js +78 -19
- package/dist/shell/run-store.js +74 -2
- package/dist/shell/scheduling-io.js +12 -13
- package/dist/shell/state-cli.js +2 -7
- package/dist/shell/state-explosion-cli.js +17 -9
- package/dist/shell/topology-io.js +36 -5
- package/dist/shell/trust-audit.js +224 -22
- package/dist/shell/trust-policy-io.js +1 -1
- package/dist/shell/worker-cli.js +35 -31
- package/dist/shell/workflow-app-loader.js +67 -1
- package/dist/wiring/capability-table/basics.js +33 -8
- package/dist/wiring/capability-table/exec-backend.js +28 -19
- package/dist/wiring/capability-table/multi-agent.js +187 -180
- package/dist/wiring/capability-table/parity.js +1 -0
- package/dist/wiring/capability-table/pipeline.js +64 -52
- package/dist/wiring/capability-table/registry-core.js +30 -11
- package/dist/wiring/capability-table/reporting.js +147 -106
- package/dist/wiring/capability-table/scheduling-registry.js +201 -167
- package/dist/wiring/capability-table/state.js +59 -51
- package/dist/wiring/capability-table/trust-ledger.js +52 -28
- package/dist/wiring/capability-table/workflow-apps.js +85 -70
- package/docs/agent-delegation-drive.7.md +4 -0
- package/docs/candidate-scoring.7.md +1 -1
- package/docs/canonical-workflow-apps.7.md +7 -7
- package/docs/cli-mcp-parity.7.md +26 -16
- package/docs/contract-migration-tooling.7.md +4 -0
- package/docs/control-plane-scheduling.7.md +4 -0
- package/docs/coordinator-blackboard.7.md +17 -17
- package/docs/dogfood-one-real-repo.7.md +11 -11
- package/docs/durable-state-and-locking.7.md +4 -0
- package/docs/end-to-end-golden-path.7.md +14 -14
- package/docs/evidence-adoption-reasoning-chain.7.md +4 -0
- package/docs/execution-backends.7.md +4 -0
- package/docs/getting-started.md +37 -37
- package/docs/multi-agent-cli-mcp-surface.7.md +21 -17
- package/docs/multi-agent-eval-replay-harness.7.md +17 -13
- package/docs/multi-agent-operator-ux.7.md +23 -19
- package/docs/multi-agent-runtime-core.7.md +22 -22
- package/docs/multi-agent-topologies.7.md +6 -6
- package/docs/multi-agent-trust-policy-audit.7.md +11 -11
- package/docs/node-snapshot-diff-replay.7.md +4 -0
- package/docs/observability-cost-accounting.7.md +4 -0
- package/docs/operator-ux.7.md +34 -34
- package/docs/pipeline-runner.7.md +4 -4
- package/docs/project-index.md +31 -4
- package/docs/real-execution-backends.7.md +4 -0
- package/docs/release-and-migration.7.md +5 -1
- package/docs/release-tooling.7.md +22 -0
- package/docs/routines.md +4 -4
- package/docs/run-registry-control-plane.7.md +23 -19
- package/docs/run-retention-reclamation.7.md +24 -3
- package/docs/scheduled-tasks.md +14 -14
- package/docs/security-trust-hardening.7.md +15 -15
- package/docs/state-explosion-management.7.md +14 -10
- package/docs/team-collaboration.7.md +4 -0
- package/docs/trust-audit-anchor.7.md +4 -0
- package/docs/unix-principles.md +3 -1
- package/docs/verifier-gated-commit.7.md +1 -1
- package/docs/web-desktop-workbench.7.md +4 -0
- package/docs/workflow-app-framework.7.md +13 -13
- package/manifest/plugin.manifest.json +1 -1
- package/package.json +3 -2
- package/scripts/agents/claude-p-agent.js +2 -2
- package/scripts/block-unapproved-tag.sh +42 -6
- package/scripts/bump-version.js +27 -10
- package/scripts/canonical-apps.js +4 -4
- package/scripts/children/batch-delegate-child.js +52 -2
- package/scripts/dogfood-release.js +1 -1
- package/scripts/fake-date-for-reproduction.js +44 -0
- package/scripts/golden-path.js +4 -4
- package/scripts/purity-baseline.json +0 -27
- package/scripts/release-flow.js +194 -13
- package/scripts/release-oneclick.js +407 -0
- package/scripts/verdict-keygen.js +83 -0
- package/scripts/verify-bump-reproduction.sh +148 -0
- package/scripts/verify-verdict-signature.js +61 -0
- package/scripts/version-sync-check.js +39 -22
- package/skills/cool-workflow/SKILL.md +9 -9
- package/skills/cool-workflow/references/commands.md +89 -88
- package/ui/workbench/app.css +37 -1
- package/ui/workbench/app.js +124 -6
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
|
|
@@ -31,6 +38,17 @@
|
|
|
31
38
|
// Both release-flow.js --cut and CI's release-gate.yml verify this exact path and
|
|
32
39
|
// format. The file MUST be committed to the tag's history.
|
|
33
40
|
//
|
|
41
|
+
// VERDICT SIGNING (optional, opt-in — see scripts/verdict-keygen.js):
|
|
42
|
+
// Set CW_RELEASE_VERDICT_PRIVKEY (a path or inline PEM) and this script signs
|
|
43
|
+
// the verdict file's exact bytes, writing a sidecar
|
|
44
|
+
// .cw-release/review-<FULLSHA>.verdict.sig (base64 ed25519). Once
|
|
45
|
+
// .cw-release/verdict-signing.pub is committed to the repo, release-gate.yml,
|
|
46
|
+
// npm-publish.yml, and block-unapproved-tag.sh all start REQUIRING a valid
|
|
47
|
+
// signature in addition to the APPROVED text check — closing the gap where
|
|
48
|
+
// anyone with shell access could hand-write a passing verdict file. Until
|
|
49
|
+
// that public key is committed, every check stays exactly as before
|
|
50
|
+
// (grep-only) — this is inert by default, never a silent behavior change.
|
|
51
|
+
//
|
|
34
52
|
// Test seams (smoke/operator only, never the delegated agent):
|
|
35
53
|
// CW_RELEASE_FLOW_GATE_CMD overrides the deterministic gate command
|
|
36
54
|
// (default: `bash <thisdir>/release-gate.sh`) so the smoke can exercise the
|
|
@@ -48,6 +66,7 @@ const fs = require("node:fs");
|
|
|
48
66
|
const path = require("node:path");
|
|
49
67
|
const https = require("node:https");
|
|
50
68
|
const http = require("node:http");
|
|
69
|
+
const crypto = require("node:crypto");
|
|
51
70
|
const { spawnSync } = require("node:child_process");
|
|
52
71
|
|
|
53
72
|
const scriptsDir = __dirname;
|
|
@@ -364,6 +383,46 @@ function verifyVerdict(resultPath) {
|
|
|
364
383
|
return cap;
|
|
365
384
|
}
|
|
366
385
|
|
|
386
|
+
// ---- verdict signing (optional, opt-in — see the header comment) -----------
|
|
387
|
+
function resolveVerdictPrivateKey(value) {
|
|
388
|
+
const trimmed = String(value).trim();
|
|
389
|
+
if (trimmed.includes("BEGIN") && trimmed.includes("KEY")) return trimmed;
|
|
390
|
+
if (fs.existsSync(trimmed)) return fs.readFileSync(trimmed, "utf8");
|
|
391
|
+
die(`CW_RELEASE_VERDICT_PRIVKEY does not point to a readable key file or inline PEM: ${trimmed}`);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/** Called only after verifyVerdict() has confirmed resultPath's bytes are a
|
|
395
|
+
* genuine "APPROVED <HEAD>" verdict. A no-op unless CW_RELEASE_VERDICT_PRIVKEY
|
|
396
|
+
* is set. When it IS set, a signing failure fails closed (die()) rather than
|
|
397
|
+
* silently emitting an unsigned verdict — an operator who deliberately
|
|
398
|
+
* configured a signing key should never end up with the same on-disk state
|
|
399
|
+
* as one who didn't. */
|
|
400
|
+
function signVerdictIfConfigured(resultPath) {
|
|
401
|
+
const raw = process.env.CW_RELEASE_VERDICT_PRIVKEY;
|
|
402
|
+
if (!raw || !raw.trim()) return;
|
|
403
|
+
const pem = resolveVerdictPrivateKey(raw);
|
|
404
|
+
const sigPath = `${resultPath}.sig`;
|
|
405
|
+
try {
|
|
406
|
+
const key = crypto.createPrivateKey(pem);
|
|
407
|
+
// crypto.sign(null, ...) does NOT throw for an RSA or EC key — it silently
|
|
408
|
+
// signs with that algorithm's own default digest, producing a
|
|
409
|
+
// syntactically valid signature that can never verify against the
|
|
410
|
+
// committed ed25519 public key. Catching that here, at signing time,
|
|
411
|
+
// means a misconfigured key dies closed locally instead of only being
|
|
412
|
+
// caught later by CI (or not at all, if the repo hasn't committed
|
|
413
|
+
// verdict-signing.pub yet).
|
|
414
|
+
if (key.asymmetricKeyType !== "ed25519") {
|
|
415
|
+
die(`CW_RELEASE_VERDICT_PRIVKEY is a ${key.asymmetricKeyType || "unknown"} key, not ed25519 — generate one with scripts/verdict-keygen.js`);
|
|
416
|
+
}
|
|
417
|
+
const message = fs.readFileSync(resultPath);
|
|
418
|
+
const signature = crypto.sign(null, message, key).toString("base64");
|
|
419
|
+
fs.writeFileSync(sigPath, `${signature}\n`);
|
|
420
|
+
} catch (error) {
|
|
421
|
+
die(`failed to sign the verdict with CW_RELEASE_VERDICT_PRIVKEY: ${error.message}`);
|
|
422
|
+
}
|
|
423
|
+
say(`verdict signed → ${path.relative(repoRoot, sigPath)}`);
|
|
424
|
+
}
|
|
425
|
+
|
|
367
426
|
// ---- GitHub Release (optional, presentation/distribution) ------------------
|
|
368
427
|
// NOT a correctness gate: the load-bearing artifacts (the tag + the
|
|
369
428
|
// provenance-attested npm publish) already exist when this runs. So `gh` is NOT
|
|
@@ -398,13 +457,22 @@ function prevTagOf(version) {
|
|
|
398
457
|
return git(["describe", "--tags", "--abbrev=0", `v${version}^`]).out || "";
|
|
399
458
|
}
|
|
400
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
|
+
|
|
401
469
|
// Extract the `## <version>` section body from the CHANGELOG AS SHIPPED AT THE
|
|
402
470
|
// TAG (git show), so the notes reflect what that tag actually carried.
|
|
403
471
|
function changelogSection(version) {
|
|
404
472
|
const show = git(["show", `v${version}:CHANGELOG.md`]);
|
|
405
473
|
if (show.code !== 0 || !show.out) return "";
|
|
406
474
|
const lines = show.out.split(/\r?\n/);
|
|
407
|
-
const esc = version
|
|
475
|
+
const esc = escapeRegExp(version);
|
|
408
476
|
const startRe = new RegExp(`^## \\[?${esc}\\b`);
|
|
409
477
|
let start = -1;
|
|
410
478
|
for (let i = 0; i < lines.length; i++) if (startRe.test(lines[i])) { start = i; break; }
|
|
@@ -506,10 +574,102 @@ function releaseGitHub(version, { required = false } = {}) {
|
|
|
506
574
|
return true;
|
|
507
575
|
}
|
|
508
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
|
+
|
|
509
669
|
// ---- 3. optional cut (bump + commit verdict + tag + push) ------------------
|
|
510
670
|
function cut(resultPath, capability) {
|
|
511
671
|
if (!cutVersion || !/^\d+\.\d+\.\d+$/.test(cutVersion)) die("--cut requires --version x.y.z");
|
|
512
|
-
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; }
|
|
513
673
|
const bump = spawnSync("npm", ["run", "bump:version", "--", cutVersion], { cwd: pluginRoot, encoding: "utf8", stdio: "inherit" });
|
|
514
674
|
if (bump.status !== 0) die("bump:version failed");
|
|
515
675
|
// Regenerate the gated project index after the version bump (PR #87 gate).
|
|
@@ -522,19 +682,27 @@ function cut(resultPath, capability) {
|
|
|
522
682
|
// operator's local home path) must never ride into the immutable tag commit
|
|
523
683
|
// (that tripped pii-redaction-smoke and red-failed release-gate for v0.1.96).
|
|
524
684
|
// `git add -u` touches tracked files only, so no untracked file can be swept in;
|
|
525
|
-
// the verdict
|
|
685
|
+
// the verdict (and its .sig sidecar, when verdict signing is configured) are
|
|
686
|
+
// the only new paths the cut is allowed to add.
|
|
526
687
|
git(["add", "-u"]);
|
|
527
688
|
git(["add", "--", path.relative(repoRoot, resultPath)]);
|
|
689
|
+
const sigPath = `${resultPath}.sig`;
|
|
690
|
+
if (fs.existsSync(sigPath)) git(["add", "--", path.relative(repoRoot, sigPath)]);
|
|
528
691
|
const commit = git(["commit", "-m", `chore(release): record APPROVED reviewer verdict for v${cutVersion}`]);
|
|
529
692
|
if (commit.code !== 0) die("verdict commit failed", commit.err);
|
|
530
693
|
const tag = git(["tag", "-a", `v${cutVersion}`, "-m", `v${cutVersion}: ${capability || "release"}`]);
|
|
531
694
|
if (tag.code !== 0) die("git tag failed", tag.err);
|
|
532
695
|
if (PUSH) {
|
|
533
|
-
//
|
|
534
|
-
//
|
|
535
|
-
//
|
|
536
|
-
|
|
537
|
-
|
|
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);
|
|
538
706
|
}
|
|
539
707
|
say(`tagged v${cutVersion}${PUSH ? " and pushed" : " (local only; push when ready)"}`);
|
|
540
708
|
// Finishing step: create the GitHub Release for the just-pushed tag. Only when
|
|
@@ -559,6 +727,18 @@ function main() {
|
|
|
559
727
|
return;
|
|
560
728
|
}
|
|
561
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
|
+
|
|
562
742
|
const markerDir = path.join(repoRoot, ".cw-release");
|
|
563
743
|
fs.mkdirSync(markerDir, { recursive: true });
|
|
564
744
|
const resultPath = path.join(markerDir, `review-${HEAD}.verdict`);
|
|
@@ -569,6 +749,7 @@ function main() {
|
|
|
569
749
|
fs.writeFileSync(inputPath, buildReviewerInput(resultPath));
|
|
570
750
|
delegateReview(resultPath, inputPath);
|
|
571
751
|
const capability = verifyVerdict(resultPath);
|
|
752
|
+
signVerdictIfConfigured(resultPath);
|
|
572
753
|
|
|
573
754
|
if (MODE_CUT) cut(resultPath, capability);
|
|
574
755
|
|