elanous 0.2.17 → 0.2.19
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/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/J5H7BvqgSI8DUjtRRcA_S/_buildManifest.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1157-cba0e8553d35a980.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{726.83532d69452ee24b.js → 1470.98a6ab61ccb4131c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/171-23a877ed11720de9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2336-e830c777eb6bce04.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9247-269e4464cc4b491c.js → 2375-e061bb24cd2b5cde.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2415.ed6428ff334c7ce2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{211-9c9090c59e2c5907.js → 2977-e38442e86289f1d3.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{f6b7c9ba.64a895b8b2fedbdc.js → 2ea96958.e8c7e112de50b067.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3094.67339318c7f72a3a.js +2 -0
- package/apps/pwa/out/_next/static/chunks/338-ceb956612dcfaa86.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3395-835e4ceaa6335ba4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3403.4bc97b4066945a94.js +5 -0
- package/apps/pwa/out/_next/static/chunks/3441-be82e3595aef3a20.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3689-89905039bf3e7257.js +1 -0
- package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3822-6f268bc26c6a84ac.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1268.0840ff91e083fcbd.js → 4323.c5dfb6f503304775.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4457-d7df81c90f9d0268.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4551-77dadae76f6cbace.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6110.1297d4093fdfbb0d.js → 4847.56f5bc454fbcc827.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/4978.6fd93b974441e87f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5012-f746f6549b869f91.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5791-bb82743fa6299562.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7327-2d69fb321ba73389.js → 6062-73e68343a9814f6a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/62-5bfed1e23790b745.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6320-9bc8561fde416e07.js +1 -0
- package/apps/pwa/out/_next/static/chunks/649-ca96d1c5d09e02b8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6726-85a7db59b288c53f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6735-33267c4630d0d963.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7633-141682015873beb6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{935.4ddaa99fc1d1126c.js → 7647.2fcd30d894d5ee30.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{f6231284-45726550aba08fca.js → 8046cadf-211ca3c0e00f2b72.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8067-143791231c99bd02.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8430-a10cfba8ef282d4e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8870-9614a213b990b10b.js +25 -0
- package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9454-12c2f30dea62291c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/976-4a9b4ebdca7c739a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9989-15cdf568bafa9fc8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/ae787e4c-e5b1ef00b2dbceca.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-deef5ee191830f14.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-b193da485f9a14b2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-b6e1a79df0e214f4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-e84df4d907f703c7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-a5fb9a5db89ae4ea.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-1499f481d7fc52d6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-c7cf753aeb648790.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-4118469e514e10fd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-e968f7630654332a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-505915052f947022.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-c2f962fb4a3b06c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-7b5ed0bd8a589ec4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-cf8193250dd42dd4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-cf3f5dfa35dea8e9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-5b29d9b06ceadade.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-a15e7fcc283e9ca7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-d2915e7b70ff3d0e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-b70cd005d9169e25.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-2595f0eba29f57e1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-c776322454156e36.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-77bbd17f4435aa10.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-049689e611ecc936.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-85954a4fb09c44eb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-e773e552e1068ee6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-5633c4bfa7684b4a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-d829b92bd01b4994.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-061c317fc771becf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-3a45ece8865ed4b6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-0be990618c0d56bd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-bfea4865dc244b73.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-0d6ba8e337741484.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-76a88b55d816482e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-bba1542df59f61ce.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-4185c95689bb372e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-b699e1a8d5d34b8b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-c225e338c31bf274.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-449353f70507ca1f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-90b889714102476d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-f1ee7fbeb7f96ddc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-d59a95e1434dfaa0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-71491a23414d1a47.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-de7112bdb65fa4fe.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-feb4d0d88079660d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-f17d887b40dd6b1d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-8da77be2192bcd5d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-72c3b9a5e9582b29.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3afe4c8d0c48c39.js +1 -0
- package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-b9308f2d8a931531.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-6f35f9d892fa630b.js +1 -0
- package/apps/pwa/out/_next/static/css/{e99e3cff94ced78a.css → 599369d853c61df7.css} +1 -1
- package/apps/pwa/out/_next/static/css/85f53c6f35d11382.css +1 -0
- package/apps/pwa/out/_next/static/css/d3427996da11e480.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +23 -23
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +23 -23
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +23 -23
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +22 -22
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +22 -22
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +23 -23
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +22 -22
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +23 -23
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +23 -23
- package/apps/pwa/out/decisions/index.html +1 -0
- package/apps/pwa/out/decisions/index.txt +30 -0
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +23 -23
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +25 -24
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +23 -23
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +23 -23
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +22 -22
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +24 -23
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +23 -23
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +23 -23
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +28 -27
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +22 -22
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +22 -22
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +23 -23
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +23 -23
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +22 -22
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +22 -22
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +22 -22
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +22 -22
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +23 -23
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +23 -23
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +22 -22
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +23 -23
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +23 -23
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +23 -23
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +23 -23
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +23 -23
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +22 -22
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +23 -23
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +23 -23
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +23 -23
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +23 -23
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +23 -23
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +23 -23
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +23 -23
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +23 -23
- package/graphs/ops/guardian.yaml +26 -0
- package/graphs/ops/recipes.yaml +3 -0
- package/package.json +2 -1
- package/samples/workflows/templates/approval-gated-deploy.yaml +30 -0
- package/samples/workflows/templates/chat-driven-research.yaml +35 -0
- package/samples/workflows/templates/discord-listen-and-respond.yaml +29 -0
- package/samples/workflows/templates/http-poll-then-alert.yaml +30 -0
- package/samples/workflows/templates/manual-quickstart.yaml +21 -0
- package/samples/workflows/templates/scheduled-daily-digest.yaml +30 -0
- package/samples/workflows/templates/webhook-deploy-hook.yaml +27 -0
- package/scripts/ci-public-export-import-gate.ts +7 -2
- package/scripts/ci-typecheck-changed.ts +21 -6
- package/scripts/coord-channel-watch.sh +2 -2
- package/scripts/discord-seat-probe.ts +6 -3
- package/scripts/docs/generate-cli-anatomy.ts +162 -0
- package/scripts/goals/authoring-metrics.ts +73 -0
- package/scripts/landing-heal/must-fix-match.ts +7 -7
- package/scripts/landing-heal/run-mission.ts +3 -2
- package/scripts/lib/pch-regress-map.ts +23 -0
- package/scripts/morning-report.ts +18 -4
- package/scripts/pch-regress.ts +88 -0
- package/scripts/public-export-test-run.ts +37 -2
- package/scripts/release-loop/checklist-gate-node.ts +13 -11
- package/scripts/release-loop/cut-branch.ts +2 -1
- package/scripts/release-loop/cutoff.ts +4 -2
- package/scripts/release-loop/docs-land-node.ts +8 -1
- package/scripts/release-loop/docs-node.ts +1 -1
- package/scripts/release-loop/gate-diff.ts +18 -0
- package/scripts/release-loop/gate-node.ts +436 -59
- package/scripts/release-loop/gate-partial.ts +106 -0
- package/scripts/release-loop/gate-timing.ts +120 -0
- package/scripts/release-loop/node-verdict.ts +2 -1
- package/scripts/release-loop/npm-publish-node.ts +11 -7
- package/scripts/release-loop/ops-upgrade-node.ts +8 -1
- package/scripts/release-loop/preflight.ts +246 -0
- package/scripts/release-loop/publish-node.ts +8 -1
- package/scripts/release-loop/release-readiness.ts +11 -6
- package/scripts/release-loop/release-version.ts +48 -0
- package/scripts/release-loop/resume-release.ts +174 -0
- package/scripts/release-loop/shard-plan.ts +65 -0
- package/scripts/release-loop/unattended-release.ts +45 -7
- package/scripts/release-loop/version-node.ts +145 -40
- package/scripts/rhythm/daily.ts +25 -4
- package/scripts/skills/public-check.ts +62 -12
- package/scripts/test-deterministic.ts +51 -33
- package/scripts/test-diet/effectiveness.ts +62 -1
- package/scripts/test-diet/lib.ts +1 -1
- package/scripts/test-diet/node.ts +8 -1
- package/scripts/video/scene-table.ts +159 -0
- package/src/acp/core-turn-bridge.ts +39 -2
- package/src/agent-mission/review-loop.ts +4 -3
- package/src/agent-substrate/pr-comment-meta.ts +20 -0
- package/src/autopilot/build/llm-conflict-merge.ts +28 -8
- package/src/away/presence.ts +40 -0
- package/src/away/release-watch.ts +255 -0
- package/src/briefing/brief-items.ts +86 -17
- package/src/chat/index.ts +2 -1
- package/src/chat/short-question.ts +3 -40
- package/src/cli/autopilot-cli.ts +30 -11
- package/src/cli/away-cli.ts +68 -0
- package/src/cli/brief-cli.ts +36 -2
- package/src/cli/freeze-cli.ts +3 -2
- package/src/cli/guardian-cli.ts +49 -0
- package/src/cli/intake-cli.ts +85 -7
- package/src/cli/log-event-names.ts +1 -0
- package/src/cli/notify-cli.ts +75 -0
- package/src/cli/pod-cli.ts +5 -1
- package/src/cli/provider-cli.ts +12 -2
- package/src/cli/pty-drive-cli.ts +14 -3
- package/src/cli/release-cli.ts +128 -19
- package/src/cli/setup-cli.ts +31 -1
- package/src/cli/tasks-cli.ts +403 -4
- package/src/context-bus/condense.ts +19 -3
- package/src/context-bus/context-day.ts +77 -24
- package/src/context-bus/context-first.ts +33 -0
- package/src/context-bus/context-now-public.ts +1 -1
- package/src/context-bus/context-now-surfaces.ts +16 -6
- package/src/context-bus/context-now.ts +97 -3
- package/src/core-turn/short-question.ts +100 -0
- package/src/dashboard/dashboard-status-lines.ts +24 -4
- package/src/dashboard/index.ts +50 -5
- package/src/dashboard/input/slash-executor.ts +6 -2
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +63 -36
- package/src/dashboard/slash-runtime/decide-slash.ts +60 -0
- package/src/dashboard/slash-runtime/help-from-registry.ts +21 -8
- package/src/dashboard/slash-runtime/help-groups.ts +44 -0
- package/src/dashboard/slash-runtime/model-choices.ts +125 -0
- package/src/discord-self-message.ts +20 -2
- package/src/discord-slash-wire.ts +1 -0
- package/src/domains/guardian-tick.ts +138 -0
- package/src/domains/outbound-alert.ts +244 -40
- package/src/domains/outbound-log-probe.ts +16 -1
- package/src/domains/release-tool.ts +2 -2
- package/src/domains/schedule-registry.ts +23 -0
- package/src/domains/telegram-kind-route.ts +39 -0
- package/src/domains/watch-default-scheduler.ts +95 -0
- package/src/esc-abort-gate.ts +44 -0
- package/src/execution-loop/gates.ts +11 -2
- package/src/flow-loop/split.ts +8 -4
- package/src/git-fs/worktree.ts +23 -3
- package/src/graph-runner/runner.ts +25 -5
- package/src/harness/harness-cli-command.ts +12 -2
- package/src/harness/harness-queue.ts +388 -33
- package/src/harness/harness-salvage-cli.ts +78 -0
- package/src/harness/harness-worktree-add.ts +68 -0
- package/src/harness/helper-scan.ts +10 -3
- package/src/harness/ledger-write-guard.ts +6 -16
- package/src/hq/hq.ts +6 -1
- package/src/hq/seats.ts +369 -0
- package/src/index.ts +185 -18
- package/src/instance/test-write-guard-spy-first.fixture.ts +13 -0
- package/src/instance/test-write-guard.ts +200 -0
- package/src/intake-plane/adopt-gate.ts +49 -0
- package/src/intake-plane/check.ts +3 -0
- package/src/intake-plane/collect-github.ts +57 -25
- package/src/intake-plane/digest.ts +39 -15
- package/src/intake-plane/discord-seat-work.ts +8 -2
- package/src/intake-plane/harness-target.ts +4 -3
- package/src/intake-plane/intake-sources.ts +1 -0
- package/src/intake-plane/items.ts +1 -0
- package/src/intake-plane/requirement-funnel.ts +316 -0
- package/src/knowledge/know-find.ts +10 -1
- package/src/loops/budget.ts +36 -0
- package/src/loops/orchestrator/degradation.ts +57 -0
- package/src/loops/orchestrator/finish-rate.ts +106 -4
- package/src/loops/orchestrator/grid-place.ts +11 -0
- package/src/loops/orchestrator/tick.ts +450 -54
- package/src/maturity/feature-maturity.ts +8 -5
- package/src/maturity/tui-slash-maturity.ts +1 -0
- package/src/mss/logging/log-store.ts +3 -0
- package/src/nexus/api/decisions-route.ts +11 -1
- package/src/nexus/api/http-server.ts +16 -5
- package/src/nexus/api/loop-edges.ts +352 -23
- package/src/nexus/api/ops-api.ts +96 -28
- package/src/nexus/api/ops-seats-sources.ts +2 -2
- package/src/nexus/api/workflow-templates.ts +3 -0
- package/src/nexus/index.ts +7 -0
- package/src/oauth/codex.ts +38 -0
- package/src/onboarding/first-chat-gate.ts +29 -0
- package/src/release-loop/checklist.ts +106 -6
- package/src/release-loop/feature-store.ts +64 -16
- package/src/release-loop/landing-freeze.ts +6 -4
- package/src/release-loop/light-rc.ts +173 -0
- package/src/release-loop/placement.ts +64 -20
- package/src/release-loop/rubric.ts +136 -0
- package/src/seat-dispatch/seat-ask.ts +79 -16
- package/src/seat-loop/seat-loop.ts +12 -6
- package/src/self-dev/ask-launch-flow.ts +24 -3
- package/src/self-dev/ask-path-candidates.ts +68 -0
- package/src/self-dev/dev-cli.ts +36 -0
- package/src/self-dev/dev-pipeline.ts +4 -0
- package/src/self-dev/draft-triage-rules.ts +1 -1
- package/src/self-dev/orchestrate-cli.ts +22 -0
- package/src/self-dev/orchestrate.ts +1 -0
- package/src/self-dev/pr-preview.ts +179 -0
- package/src/self-dev/round-pr-supersede.ts +244 -0
- package/src/self-dev/run-supervisor.ts +14 -0
- package/src/self-dev/shard-run.ts +23 -7
- package/src/self-implement/follow-up-goals.ts +2 -2
- package/src/self-implement/gate-cli.ts +29 -8
- package/src/self-implement/gate-remote.ts +427 -0
- package/src/self-implement/goal-author-clarification.ts +88 -13
- package/src/self-implement/goal-author.ts +8 -3
- package/src/self-implement/goal-clarification-escalation.ts +2 -0
- package/src/self-implement/l8-merge-queue.ts +29 -5
- package/src/self-implement/orchestrator.ts +61 -13
- package/src/self-implement/prior-draft-findings.ts +4 -3
- package/src/self-implement/salvage-branches.ts +254 -0
- package/src/skills/skill-feedback.ts +76 -0
- package/src/task-agent/actions.ts +101 -7
- package/src/task-agent/board.ts +132 -0
- package/src/task-agent/card-evidence.ts +203 -0
- package/src/task-agent/completion-evidence.ts +126 -0
- package/src/task-agent/judge.ts +49 -5
- package/src/task-agent/live-moves.ts +176 -0
- package/src/task-agent/mission.ts +449 -0
- package/src/task-agent/shadow.ts +258 -0
- package/src/task-agent/task-hand.ts +337 -0
- package/src/task-orchestrator/surfaces/pod-command-job.ts +64 -4
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +10 -0
- package/src/task-orchestrator/surfaces/pod-lease.ts +142 -13
- package/src/task-orchestrator/surfaces/pod-pool.ts +13 -5
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +88 -20
- package/src/telegram-away-command.ts +38 -0
- package/src/telegram-commands.ts +11 -0
- package/src/telegram-loops-command.ts +1 -1
- package/src/telegram-report-log-probe.ts +21 -0
- package/src/telegram-report.ts +27 -3
- package/src/telegram.ts +5 -20
- package/src/tui-client/dashboard-session.ts +1 -0
- package/src/user-config.ts +196 -4
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/1307-aac7332576c4d6fc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1844-9ec4b1d12f6e80eb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1948.15f356b15a625597.js +0 -5
- package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +0 -1
- package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4554-3565eea1f1495ba7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4614-21403aac730d4586.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4795-f372490b178583fc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5135-44008fe8b1f2aa41.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5233-bf28bd6bd7a32099.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6158.9c0449a9a8d0064b.js +0 -2
- package/apps/pwa/out/_next/static/chunks/6877-fe94e76a5841ee9b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7395-164e238f471b5d7b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7787-02a1c0b04e25e811.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8401-4ce1c42c8d89903f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +0 -25
- package/apps/pwa/out/_next/static/chunks/8949-ecb4128a0858b911.js +0 -1
- package/apps/pwa/out/_next/static/chunks/899-f41be85e8a680651.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9103-60fbef33e66f0a46.js +0 -1
- package/apps/pwa/out/_next/static/chunks/a9f2b54d-7865aba0f4d2ee49.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-3143b07a817d1da9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-17ba086ef82d339b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-7eb661ff23af7da2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-6b8444914da74ca6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-6e5c3a867557298b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-7110f732f45bf944.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-9a41d65355fc25b9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-5e77cd1802567c89.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-41afec0b255ed2bb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-4187666132fc98c4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-09328d499162bae4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-29ff3a8f84f03b6a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-80ec7401bc83f511.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-ab93b240f687d69a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-fc7d86467291ae38.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-de43a186e5ba5683.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-8b08cafad27759c9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-0e2d6985fb42cd2f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-13e4108999f58d13.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-e8296ee284aa1293.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-043a9111e74f6b24.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-23a4601ceb7f452e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-1ed1324b1dc36754.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-10d958605a77b3bd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-166745e51ac0c7b0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-a04260b8ccc968d4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-7a1d85e121065cc8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-1f50e3d4e13435f4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-12916918988ac97e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-d8a3347a9d0c3f4a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-a9117d2cd3093d36.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-5a64cb5208c6a8fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-1407ec83ea64c1f9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-1d824510df025d18.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-6e878ebbe1737d00.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-7f49fe645d5132f7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-81a4e7f8dbfe74cb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-27d71f131acd9320.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-3ab59d19913b200e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-bf85b2f5bf077925.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-424659bacfb8cdd7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-ab32a8f522a60371.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-fc2df261f8a2c7df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-09461366af82ef13.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-5d4fef9ccd7d406b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ea90a8ac73ce2749.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-539cde99554be20f.js +0 -1
- package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +0 -1
- package/apps/pwa/out/_next/static/yRA5ur6DcPl0YHSRheH2O/_buildManifest.js +0 -1
- /package/apps/pwa/out/_next/static/{yRA5ur6DcPl0YHSRheH2O → J5H7BvqgSI8DUjtRRcA_S}/_ssgManifest.js +0 -0
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { releaseLedgerRoot } from '../../src/instance/resolve.js';
|
|
4
|
-
import {
|
|
4
|
+
import { cutChecklistGate, type ChecklistGate } from '../../src/release-loop/checklist.js';
|
|
5
|
+
|
|
6
|
+
/** Only the verdict fields are read — so a `release run` deps checklist (`Pick<…>`) fits as well. */
|
|
7
|
+
export type ReadinessChecklist = Pick<ChecklistGate, 'ok' | 'red' | 'undecided' | 'blocked'>;
|
|
5
8
|
import { getSchedule, formatKst } from '../../src/release-loop/release-schedule.js';
|
|
6
9
|
import { debug } from '../../src/debug/log.js';
|
|
7
10
|
|
|
@@ -9,12 +12,12 @@ export type ReleaseReadiness =
|
|
|
9
12
|
| { ready: false; reason: 'already-published'; details: string }
|
|
10
13
|
| { ready: false; reason: 'already-running'; details: string }
|
|
11
14
|
| { ready: false; reason: 'before-cut'; details: string }
|
|
12
|
-
| { ready: false; reason: 'checklist-blocked'; details:
|
|
13
|
-
| { ready: true; reason: 'ready'; details:
|
|
15
|
+
| { ready: false; reason: 'checklist-blocked'; details: ReadinessChecklist }
|
|
16
|
+
| { ready: true; reason: 'ready'; details: ReadinessChecklist };
|
|
14
17
|
|
|
15
18
|
export interface ReleaseReadinessDeps {
|
|
16
19
|
ledgerRoot?: string;
|
|
17
|
-
checklist?:
|
|
20
|
+
checklist?: (version: string) => ReadinessChecklist;
|
|
18
21
|
isPidAlive?: (pid: number) => boolean;
|
|
19
22
|
now?: () => Date;
|
|
20
23
|
}
|
|
@@ -57,11 +60,13 @@ export function releaseReadiness(version: string, deps: ReleaseReadinessDeps = {
|
|
|
57
60
|
}
|
|
58
61
|
}
|
|
59
62
|
const schedule = getSchedule(version, deps.ledgerRoot);
|
|
60
|
-
|
|
63
|
+
const now = (deps.now ?? (() => new Date()))();
|
|
64
|
+
if (schedule && now.getTime() < Date.parse(schedule.cutAt)) {
|
|
61
65
|
debug.log('release.schedule', 'before-cut', { version, cutAt: schedule.cutAt, landBy: schedule.landBy });
|
|
62
66
|
return { ready: false, reason: 'before-cut', details: `before-cut (${formatKst(schedule.cutAt)})` };
|
|
63
67
|
}
|
|
64
|
-
|
|
68
|
+
// GATE-ENTRY-ALIGN: the same cut judgement as `release run`'s entry check and the checklist-gate node.
|
|
69
|
+
const gate = (deps.checklist ?? ((v: string) => cutChecklistGate(v, schedule?.landBy, now)))(version);
|
|
65
70
|
if (!gate.ok) return { ready: false, reason: 'checklist-blocked', details: gate };
|
|
66
71
|
return { ready: true, reason: 'ready', details: gate };
|
|
67
72
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Release version shapes for the release loop: a stable x.y.z or a prerelease x.y.z-(rc|alpha|beta).N.
|
|
2
|
+
// RELEASE-BRANCH ⊕ RELEASE-REHEARSAL-RC (10-06): prerelease runs publish to npm `next` and skip the
|
|
3
|
+
// post-publish nodes that touch main or the operating hosts.
|
|
4
|
+
|
|
5
|
+
export const PRERELEASE_KINDS = ['rc', 'alpha', 'beta'] as const;
|
|
6
|
+
export type PrereleaseKind = (typeof PRERELEASE_KINDS)[number];
|
|
7
|
+
|
|
8
|
+
const RELEASE_VERSION = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-(rc|alpha|beta)\.(0|[1-9]\d*))?$/;
|
|
9
|
+
const STABLE_VERSION = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
|
|
10
|
+
|
|
11
|
+
export function isStableVersion(version: string): boolean { return STABLE_VERSION.test(version); }
|
|
12
|
+
|
|
13
|
+
export function isReleaseVersion(version: string): boolean { return RELEASE_VERSION.test(version); }
|
|
14
|
+
|
|
15
|
+
/** `0.2.18-rc.0` → `0.2.18`; a stable version is returned as is. Throws on anything else. */
|
|
16
|
+
export function baseVersion(version: string): string {
|
|
17
|
+
const match = RELEASE_VERSION.exec(version);
|
|
18
|
+
if (!match) throw new Error(`invalid release version: ${version}`);
|
|
19
|
+
return `${match[1]}.${match[2]}.${match[3]}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The prerelease kind of `0.2.18-rc.0` (`rc`), or null for a stable version. */
|
|
23
|
+
export function prereleaseKind(version: string): PrereleaseKind | null {
|
|
24
|
+
const match = RELEASE_VERSION.exec(version);
|
|
25
|
+
if (!match) throw new Error(`invalid release version: ${version}`);
|
|
26
|
+
return (match[4] as PrereleaseKind | undefined) ?? null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function isPrereleaseKind(value: unknown): value is PrereleaseKind {
|
|
30
|
+
return typeof value === 'string' && (PRERELEASE_KINDS as readonly string[]).includes(value);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The next free prerelease number for `base` and `kind`, given the names already taken
|
|
35
|
+
* (release branches `release/<v>` and tags `v<v>`, with or without those prefixes).
|
|
36
|
+
*/
|
|
37
|
+
export function nextPrereleaseVersion(base: string, kind: PrereleaseKind, taken: readonly string[]): string {
|
|
38
|
+
if (!STABLE_VERSION.test(base)) throw new Error(`prerelease base must be x.y.z: ${base}`);
|
|
39
|
+
const prefix = `${base}-${kind}.`;
|
|
40
|
+
let next = 0;
|
|
41
|
+
for (const raw of taken) {
|
|
42
|
+
const name = raw.replace(/^refs\/heads\//, '').replace(/^refs\/tags\//, '').replace(/^release\//, '').replace(/^v(?=\d)/, '').replace(/\^\{\}$/, '');
|
|
43
|
+
if (!name.startsWith(prefix)) continue;
|
|
44
|
+
const n = name.slice(prefix.length);
|
|
45
|
+
if (/^(0|[1-9]\d*)$/.test(n)) next = Math.max(next, Number(n) + 1);
|
|
46
|
+
}
|
|
47
|
+
return `${prefix}${next}`;
|
|
48
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// RELEASE-BRANCH (10-06): resume a failed release run after `release cut-branch --append --pick` moved release/<v>.
|
|
2
|
+
// The saved version-release output is re-pointed at the new branch tip (fast-forward only, state backed up first), so
|
|
3
|
+
// every later node reads the repaired commit without anyone hand-editing the run state.
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
import { constants, copyFileSync, existsSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { debug } from '../../src/debug/log.js';
|
|
8
|
+
import { effectiveInstanceRoot, releaseLedgerRoot } from '../../src/instance/resolve.js';
|
|
9
|
+
import { lastJsonObject, runGraph, type GraphRunState } from '../../src/graph-runner/runner.js';
|
|
10
|
+
import { isReleaseVersion } from './release-version.js';
|
|
11
|
+
import { changedFilesBetween, clearPartialPlan, planPartialRegate, writePartialPlan } from './gate-partial.js';
|
|
12
|
+
|
|
13
|
+
const GRAPH = join(import.meta.dir, '../../graphs/release/release-loop.yaml');
|
|
14
|
+
|
|
15
|
+
export interface ResumeReleaseDeps {
|
|
16
|
+
root?: string;
|
|
17
|
+
repo?: string;
|
|
18
|
+
/** Runs git and returns stdout (throws on failure). */
|
|
19
|
+
git?: (args: string[]) => string;
|
|
20
|
+
/** `git merge-base --is-ancestor a b`. */
|
|
21
|
+
isAncestor?: (ancestor: string, descendant: string) => boolean;
|
|
22
|
+
graph?: typeof runGraph;
|
|
23
|
+
/** The release graph (tests point it at a probe graph with the same graph id). */
|
|
24
|
+
graphPath?: string;
|
|
25
|
+
/** Node command runner for the resumed graph (tests record each node's context instead of running it). */
|
|
26
|
+
runBash?: NonNullable<NonNullable<Parameters<typeof runGraph>[1]>['deps']>['runBash'];
|
|
27
|
+
now?: () => Date;
|
|
28
|
+
/** Machine release ledger (gate-failures.json of the prior gate) — default releaseLedgerRoot(). */
|
|
29
|
+
ledgerRoot?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface TipRefresh { version: string; branch: string; from: string; to: string; changed: boolean; statePath: string; backup?: string }
|
|
33
|
+
|
|
34
|
+
function gitRunner(repo: string) {
|
|
35
|
+
return (args: string[]): string => {
|
|
36
|
+
// git-spawn-allow: read-only fetch / ls-remote / rev-parse against release/<v>; nothing is pushed or checked out.
|
|
37
|
+
const result = spawnSync('git', args, { cwd: repo, encoding: 'utf8' });
|
|
38
|
+
if (result.status !== 0 || result.error) throw new Error(`git ${args[0]} failed: ${(result.stderr || result.error || 'no output').toString().trim()}`);
|
|
39
|
+
return result.stdout.trim();
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function ancestorRunner(repo: string) {
|
|
44
|
+
return (ancestor: string, descendant: string): boolean => {
|
|
45
|
+
const result = spawnSync('git', ['merge-base', '--is-ancestor', ancestor, descendant], { cwd: repo });
|
|
46
|
+
if (result.status !== 0 && result.status !== 1) throw new Error(`git merge-base failed: ${result.error || result.stderr || 'unknown'}`);
|
|
47
|
+
return result.status === 0;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function safeRunId(runId: string): string {
|
|
52
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(runId) || runId === '.' || runId === '..') throw new Error(`invalid run id: ${runId}`);
|
|
53
|
+
return runId;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Point the saved version-release output at the current release/<v> tip (fast-forward of the recorded commit only). */
|
|
57
|
+
export function refreshReleaseBranchTip(runId: string, deps: ResumeReleaseDeps = {}, from?: string): TipRefresh {
|
|
58
|
+
const root = deps.root ?? effectiveInstanceRoot();
|
|
59
|
+
const statePath = join(root, 'graph-runs', 'release-loop', `${safeRunId(runId)}.json`);
|
|
60
|
+
const state = JSON.parse(readFileSync(statePath, 'utf8')) as GraphRunState;
|
|
61
|
+
if (state.graphId !== 'release-loop' || state.runId !== runId) throw new Error(`run identity mismatch: ${statePath}`);
|
|
62
|
+
if (state.status !== 'failed') throw new Error(`release run ${runId} is ${state.status} — only a failed run is resumed`);
|
|
63
|
+
const restartAt = from === undefined ? -1 : state.path.indexOf(from);
|
|
64
|
+
if (from !== undefined && (restartAt < 0 || state.path.lastIndexOf(from) !== restartAt)) throw new Error(`--from ${from} is not a node on the saved path of ${runId}`);
|
|
65
|
+
const index = state.nodes.findIndex((node) => node.nodeId === 'version-release');
|
|
66
|
+
const record = index >= 0 ? state.nodes[index]! : undefined;
|
|
67
|
+
const output = record ? lastJsonObject(record.output) : undefined;
|
|
68
|
+
if (!record?.ok || !output) throw new Error(`release run ${runId} has no successful version-release record`);
|
|
69
|
+
const version = typeof output.version === 'string' ? output.version : '';
|
|
70
|
+
const recorded = typeof output.commit === 'string' ? output.commit : '';
|
|
71
|
+
if (!isReleaseVersion(version) || !/^[0-9a-f]{40}$/i.test(recorded)) throw new Error(`release run ${runId} version-release output is not a release commit`);
|
|
72
|
+
const branch = `release/${version}`;
|
|
73
|
+
// Only a run that cut release/<v> itself is re-pointed — a main-cut or --cut-commit run has no branch of its own.
|
|
74
|
+
const input = (state.input ?? {}) as { branchCut?: unknown; version?: unknown };
|
|
75
|
+
if (input.branchCut !== true || input.version !== version || output.branch !== branch) {
|
|
76
|
+
throw new Error(`release run ${runId} was not cut as ${branch} (input.branchCut/version or version-release branch differ) — refusing to resume`);
|
|
77
|
+
}
|
|
78
|
+
const git = deps.git ?? gitRunner(deps.repo ?? process.cwd());
|
|
79
|
+
const advertised = git(['ls-remote', '--heads', 'origin', `refs/heads/${branch}`]).split(/\s+/)[0] ?? '';
|
|
80
|
+
if (!/^[0-9a-f]{40}$/i.test(advertised)) throw new Error(`${branch} is not on origin — this run was not cut as a release branch`);
|
|
81
|
+
git(['fetch', 'origin', `+refs/heads/${branch}:refs/remotes/origin/${branch}`]);
|
|
82
|
+
const to = git(['rev-parse', '--verify', `refs/remotes/origin/${branch}^{commit}`]);
|
|
83
|
+
if (to !== advertised) throw new Error(`${branch} moved while it was read (${advertised} → ${to})`);
|
|
84
|
+
if (to === recorded) {
|
|
85
|
+
debug.log('release-loop.resume', 'tip-unchanged', { runId, version, commit: to });
|
|
86
|
+
return { version, branch, from: recorded, to, changed: false, statePath };
|
|
87
|
+
}
|
|
88
|
+
// A repair only appends (`cut-branch --append`): anything but a fast-forward is a different release.
|
|
89
|
+
if (!(deps.isAncestor ?? ancestorRunner(deps.repo ?? process.cwd()))(recorded, to)) {
|
|
90
|
+
throw new Error(`${branch} tip ${to} does not descend from the recorded release commit ${recorded} — refusing to resume`);
|
|
91
|
+
}
|
|
92
|
+
// A new tip invalidates everything built from the old one (gate verdict, prepared archive, …): restart no later
|
|
93
|
+
// than the first node that consumed the commit, so each of them runs again on the new tip.
|
|
94
|
+
const firstConsumer = state.path.indexOf('gate');
|
|
95
|
+
if (restartAt >= 0 && firstConsumer >= 0 && restartAt > firstConsumer) {
|
|
96
|
+
throw new Error(`${branch} moved (${recorded.slice(0, 12)} → ${to.slice(0, 12)}): resume at gate or earlier — later nodes were built from the old tip`);
|
|
97
|
+
}
|
|
98
|
+
const stamp = (deps.now?.() ?? new Date()).toISOString().replace(/[:.]/g, '-');
|
|
99
|
+
const backup = `${statePath}.before-tip-${stamp}-${process.pid}-${to.slice(0, 12)}.bak`;
|
|
100
|
+
// Exclusive: a retry never overwrites an earlier backup.
|
|
101
|
+
copyFileSync(statePath, backup, constants.COPYFILE_EXCL);
|
|
102
|
+
const refreshed = { ...output, commit: to, refreshedFrom: recorded };
|
|
103
|
+
const text = typeof record.output === 'string' ? record.output.replace(/\s*$/, '') : '';
|
|
104
|
+
state.nodes[index] = { ...record, output: `${text}\n${JSON.stringify(refreshed)}\n` };
|
|
105
|
+
const temporary = `${statePath}.${process.pid}.tip.tmp`;
|
|
106
|
+
writeFileSync(temporary, `${JSON.stringify(state, null, 2)}\n`);
|
|
107
|
+
renameSync(temporary, statePath);
|
|
108
|
+
debug.log('release-loop.resume', 'tip-refreshed', { runId, version, branch, from: recorded, to, backup });
|
|
109
|
+
return { version, branch, from: recorded, to, changed: true, statePath, backup };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** GATE-PARTIAL outcome of a resume — applied (plan written) or the reason it fell back to a full gate. */
|
|
113
|
+
export type PartialOutcome = { requested: false } | { requested: true; applied: true; files: number; priorCommit: string } | { requested: true; applied: false; reason: string };
|
|
114
|
+
|
|
115
|
+
/** Build (or clear) the partial re-gate plan for the new tip. Fail-closed: any doubt leaves a full gate. */
|
|
116
|
+
function preparePartial(tip: TipRefresh, opts: { from: string; partial?: boolean }, deps: ResumeReleaseDeps): PartialOutcome {
|
|
117
|
+
const root = deps.root ?? effectiveInstanceRoot();
|
|
118
|
+
// Any resume starts from no plan: one without --partial (from whatever node) never inherits an older plan.
|
|
119
|
+
clearPartialPlan(root, tip.version);
|
|
120
|
+
if (!opts.partial) return { requested: false };
|
|
121
|
+
const fallback = (reason: string): PartialOutcome => {
|
|
122
|
+
debug.log('release-loop.resume', 'partial-fallback', { version: tip.version, reason });
|
|
123
|
+
return { requested: true, applied: false, reason };
|
|
124
|
+
};
|
|
125
|
+
if (opts.from !== 'gate') return fallback('--partial only applies to --from gate');
|
|
126
|
+
if (!tip.changed) return fallback('release branch tip unchanged — nothing new to re-check');
|
|
127
|
+
let prior: { commit?: unknown; failures?: unknown; errors?: unknown } | undefined;
|
|
128
|
+
for (const location of [...new Set([deps.ledgerRoot ?? releaseLedgerRoot(), root])]) {
|
|
129
|
+
const path = join(location, 'release', tip.version, 'gate-failures.json');
|
|
130
|
+
if (!existsSync(path)) continue;
|
|
131
|
+
let record: typeof prior;
|
|
132
|
+
try { record = JSON.parse(readFileSync(path, 'utf8')) as typeof prior; }
|
|
133
|
+
catch { return fallback(`unreadable gate record: ${path}`); }
|
|
134
|
+
if (record?.commit === tip.from) { prior = record; break; }
|
|
135
|
+
}
|
|
136
|
+
if (!prior || !Array.isArray(prior.failures) || !(prior.errors === undefined || Array.isArray(prior.errors))) {
|
|
137
|
+
return fallback(`no complete gate record for ${tip.from.slice(0, 12)} (a stalled or errored gate keeps none)`);
|
|
138
|
+
}
|
|
139
|
+
// Both sides of a rename count (`--no-renames`): a source moved to a test/Markdown name is still a source change.
|
|
140
|
+
let changed: string[];
|
|
141
|
+
try {
|
|
142
|
+
changed = deps.git
|
|
143
|
+
? deps.git(['diff', '--name-only', '--no-renames', `${tip.from}..${tip.to}`]).split(/\r?\n/).filter(Boolean)
|
|
144
|
+
: changedFilesBetween(deps.repo ?? process.cwd(), tip.from, tip.to);
|
|
145
|
+
} catch (error) {
|
|
146
|
+
// The tip is already refreshed — a failed diff must not strand the resume: run the full gate and say why.
|
|
147
|
+
return fallback(`changed files unreadable: ${String(error instanceof Error ? error.message : error).slice(0, 200)}`);
|
|
148
|
+
}
|
|
149
|
+
const planned = planPartialRegate({ version: tip.version, priorCommit: tip.from, forCommit: tip.to, changedFiles: changed,
|
|
150
|
+
priorFailures: prior.failures as string[], priorErrors: (prior.errors ?? []) as string[] });
|
|
151
|
+
if (!planned.ok) return fallback(planned.reason);
|
|
152
|
+
const path = writePartialPlan(root, planned.plan);
|
|
153
|
+
debug.log('release-loop.resume', 'partial-planned', { version: tip.version, priorCommit: tip.from, commit: tip.to, files: planned.plan.files.length, path });
|
|
154
|
+
return { requested: true, applied: true, files: planned.plan.files.length, priorCommit: tip.from };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** The graph file the run started from — a snapshot run refuses any other path (the installed CLI's own copy differs). */
|
|
158
|
+
function savedGraphPath(statePath: string): string | undefined {
|
|
159
|
+
try {
|
|
160
|
+
const saved = JSON.parse(readFileSync(statePath, 'utf8')) as { graphPath?: unknown };
|
|
161
|
+
return typeof saved.graphPath === 'string' && saved.graphPath ? saved.graphPath : undefined;
|
|
162
|
+
} catch { return undefined; }
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Refresh the branch tip, then restart the failed run at `from` with the saved graph snapshot. */
|
|
166
|
+
export async function resumeReleaseRun(opts: { runId: string; from: string; partial?: boolean }, deps: ResumeReleaseDeps = {}): Promise<{ tip: TipRefresh; partial: PartialOutcome; state: GraphRunState }> {
|
|
167
|
+
if (opts.from === 'version-release') throw new Error('--from version-release would cut again — resume at a later node');
|
|
168
|
+
const tip = refreshReleaseBranchTip(opts.runId, deps, opts.from);
|
|
169
|
+
const partial = preparePartial(tip, opts, deps);
|
|
170
|
+
// Resume the graph at the path it was started from (its snapshot is used); only tests inject another path.
|
|
171
|
+
const graphPath = deps.graphPath ?? savedGraphPath(tip.statePath) ?? GRAPH;
|
|
172
|
+
const state = await (deps.graph ?? runGraph)(graphPath, { resumeRunId: opts.runId, fromNodeId: opts.from, ...(deps.root || deps.runBash ? { deps: { ...(deps.root ? { root: deps.root } : {}), ...(deps.runBash ? { runBash: deps.runBash } : {}) } } : {}) });
|
|
173
|
+
return { tip, partial, state };
|
|
174
|
+
}
|
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
import { readdirSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
+
import { debug } from '../../src/debug/log.js';
|
|
4
|
+
|
|
5
|
+
/** Plan weights for a Pod sweep: wall-scaled when the previous cut recorded shard wall times, junit seconds otherwise. */
|
|
6
|
+
export function planDurations(durationSource: string | undefined): { durations: Map<string, number>; wall?: { shards: number; scaledFiles: number } } {
|
|
7
|
+
if (!durationSource) return { durations: new Map() };
|
|
8
|
+
const junit = readFileDurations(durationSource);
|
|
9
|
+
const wall = readFileWallWeights(durationSource, junit);
|
|
10
|
+
return wall ? { durations: wall.weights, wall: { shards: wall.shards, scaledFiles: wall.scaledFiles } } : { durations: junit };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Observation for GATE-PLAN-WALLTIME, emitted once the shards are planned so `maxPlannedMin` is the heaviest shard. */
|
|
14
|
+
export function logPlanWalltime(wall: { shards: number; scaledFiles: number } | undefined, shards: ReadonlyArray<{ plannedSeconds: number }>): void {
|
|
15
|
+
if (!wall) return;
|
|
16
|
+
const maxPlannedSeconds = Math.max(0, ...shards.map((shard) => shard.plannedSeconds));
|
|
17
|
+
debug.log('release-loop.gate', 'plan-walltime', { shards: wall.shards, scaledFiles: wall.scaledFiles, maxPlannedMin: Math.round(maxPlannedSeconds / 6) / 10 });
|
|
18
|
+
}
|
|
3
19
|
|
|
4
20
|
/** Read per-file seconds from previous cut Pod reports; missing or damaged reports are not estimates. */
|
|
5
21
|
export function readFileDurations(dir: string): Map<string, number> {
|
|
@@ -62,3 +78,52 @@ export function planShards(files: string[], durations: Map<string, number>, coun
|
|
|
62
78
|
}
|
|
63
79
|
return shards.filter((shard) => shard.files.length > 0);
|
|
64
80
|
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* GATE-PLAN-WALLTIME: junit testsuite times run far below a shard's real wall time (0.2.18 pod-23: planned 2.7 min,
|
|
84
|
+
* measured 33.3 min), so LPT balanced the wrong quantity. When the previous cut left `pod-*.json` records
|
|
85
|
+
* (`durationMs` ⊕ `files`), each file's junit seconds are scaled so its shard's planned total equals the measured wall
|
|
86
|
+
* time; files without junit take that shard's average per-file wall time. A file measured in several records takes
|
|
87
|
+
* its first-pass shard (`pod-<n>.json` — the shape the next cut plans) over descent/retry children, whose per-Pod
|
|
88
|
+
* install overhead lands on one or two files; among equals, the record with the fewest files.
|
|
89
|
+
* Returns undefined when no usable record exists, so callers keep the junit-only behaviour.
|
|
90
|
+
*/
|
|
91
|
+
export function readFileWallWeights(dir: string, junit: Map<string, number>): { weights: Map<string, number>; shards: number; scaledFiles: number } | undefined {
|
|
92
|
+
let names: string[];
|
|
93
|
+
try { names = readdirSync(dir).filter((name) => /^pod-.*\.json$/.test(name)).sort(); }
|
|
94
|
+
catch { return undefined; }
|
|
95
|
+
const records: Array<{ wallSeconds: number; files: string[]; firstPass: boolean }> = [];
|
|
96
|
+
for (const name of names) {
|
|
97
|
+
try {
|
|
98
|
+
const meta = JSON.parse(readFileSync(join(dir, name), 'utf8')) as { durationMs?: unknown; files?: unknown };
|
|
99
|
+
if (typeof meta.durationMs !== 'number' || !Number.isFinite(meta.durationMs) || meta.durationMs <= 0) continue;
|
|
100
|
+
if (!Array.isArray(meta.files) || !meta.files.length || !meta.files.every((file) => typeof file === 'string')) continue;
|
|
101
|
+
records.push({ wallSeconds: meta.durationMs / 1000, files: [...new Set(meta.files as string[])], firstPass: /^pod-\d+\.json$/.test(name) });
|
|
102
|
+
} catch { /* One damaged record must not discard the others. */ }
|
|
103
|
+
}
|
|
104
|
+
if (!records.length) return undefined;
|
|
105
|
+
const valid = (value: number | undefined): value is number => value !== undefined && Number.isFinite(value) && value >= 0;
|
|
106
|
+
const best = new Map<string, { rank: number; seconds: number }>();
|
|
107
|
+
for (const record of records) {
|
|
108
|
+
const unknownCount = record.files.filter((file) => !valid(junit.get(file))).length;
|
|
109
|
+
const junitSum = record.files.reduce((sum, file) => { const value = junit.get(file); return sum + (valid(value) ? value : 0); }, 0);
|
|
110
|
+
const average = record.wallSeconds / record.files.length;
|
|
111
|
+
const remaining = record.wallSeconds - unknownCount * average;
|
|
112
|
+
for (const file of record.files) {
|
|
113
|
+
const seconds = junit.get(file);
|
|
114
|
+
const weight = valid(seconds) && junitSum > 0 && remaining > 0 ? seconds * (remaining / junitSum) : average;
|
|
115
|
+
const prior = best.get(file);
|
|
116
|
+
const rank = (record.firstPass ? 0 : 1_000_000) + record.files.length;
|
|
117
|
+
if (!prior || rank < prior.rank) best.set(file, { rank, seconds: weight });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Files timed by junit but absent from every record (new or renamed since the cut) take the overall scale, so the
|
|
121
|
+
// plan never mixes wall seconds with raw junit seconds.
|
|
122
|
+
let scaledSum = 0;
|
|
123
|
+
let junitSum = 0;
|
|
124
|
+
for (const [file, { seconds }] of best) { const value = junit.get(file); if (valid(value) && value > 0) { scaledSum += seconds; junitSum += value; } }
|
|
125
|
+
const overall = junitSum > 0 ? scaledSum / junitSum : 1;
|
|
126
|
+
const weights = new Map([...junit].map(([file, seconds]) => [file, seconds * overall] as [string, number]));
|
|
127
|
+
for (const [file, { seconds }] of best) weights.set(file, seconds);
|
|
128
|
+
return { weights, shards: records.length, scaledFiles: best.size };
|
|
129
|
+
}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
1
2
|
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
2
3
|
import { join } from 'node:path';
|
|
3
4
|
import { getElanousConfigDirOverride } from '../../src/elanous-config-dir.js';
|
|
4
5
|
import { effectiveInstanceRoot, releaseLedgerRoot } from '../../src/instance/resolve.js';
|
|
5
6
|
import { userConfigPath } from '../../src/user-config.js';
|
|
6
|
-
import {
|
|
7
|
+
import { cutChecklistGate } from '../../src/release-loop/checklist.js';
|
|
8
|
+
import { getSchedule } from '../../src/release-loop/release-schedule.js';
|
|
7
9
|
import { isLandingFreezeRefusal, readLandingFreeze, LandingFrozenError } from '../../src/release-loop/landing-freeze.js';
|
|
8
10
|
import { debug } from '../../src/debug/log.js';
|
|
9
11
|
import { runGraph, type GraphRunState } from '../../src/graph-runner/runner.js';
|
|
12
|
+
import { baseVersion, isStableVersion, nextPrereleaseVersion, type PrereleaseKind } from './release-version.js';
|
|
10
13
|
|
|
11
14
|
const VERSION = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/;
|
|
12
15
|
const GRAPH = join(import.meta.dir, '../../graphs/release/release-loop.yaml');
|
|
@@ -16,6 +19,8 @@ export interface ReleaseLoopConfig {
|
|
|
16
19
|
gatePodBunCache?: string;
|
|
17
20
|
gatePodShards?: number;
|
|
18
21
|
gatePodShardTimeoutSeconds?: number;
|
|
22
|
+
/** GATE-SPEED A3①: gate shard Pod CPU — `2` (request = limit) or `{ request, limit }`. Omitted = request 1 / limit 4. */
|
|
23
|
+
gatePodCpu?: number | string | { request?: number | string; limit?: number | string };
|
|
19
24
|
gateRemote?: string;
|
|
20
25
|
gateRemoteMirror?: string;
|
|
21
26
|
opsHosts?: string[];
|
|
@@ -29,6 +34,8 @@ export interface ReleaseRunInput extends ReleaseLoopConfig {
|
|
|
29
34
|
cutCommit?: string;
|
|
30
35
|
gatePodPool: string;
|
|
31
36
|
forceFreeze?: boolean;
|
|
37
|
+
/** RELEASE-BRANCH: version-release cuts release/<v> from main HEAD and bumps only the branch. */
|
|
38
|
+
branchCut?: boolean;
|
|
32
39
|
}
|
|
33
40
|
|
|
34
41
|
export interface UnattendedReleaseDeps {
|
|
@@ -36,8 +43,25 @@ export interface UnattendedReleaseDeps {
|
|
|
36
43
|
config?: ReleaseLoopConfig;
|
|
37
44
|
configPath?: string;
|
|
38
45
|
graph?: (path: string, options: { input: ReleaseRunInput }) => Promise<GraphRunState>;
|
|
39
|
-
checklist?: typeof
|
|
46
|
+
checklist?: (version: string) => Pick<ReturnType<typeof cutChecklistGate>, 'ok' | 'red' | 'undecided' | 'blocked'>;
|
|
40
47
|
freezeRoot?: string;
|
|
48
|
+
/** Names already taken for prerelease numbering (remote release branches and tags). */
|
|
49
|
+
releaseRefs?: (base: string, kind: PrereleaseKind) => string[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Remote `release/<base>-<kind>.*` branches and `v<base>-<kind>.*` tags — read-only. */
|
|
53
|
+
export function remoteReleaseRefs(base: string, kind: PrereleaseKind, repo = process.cwd()): string[] {
|
|
54
|
+
// git-spawn-allow: read-only ls-remote to number a prerelease.
|
|
55
|
+
const result = spawnSync('git', ['ls-remote', 'origin', `refs/heads/release/${base}-${kind}.*`, `refs/tags/v${base}-${kind}.*`], { cwd: repo, encoding: 'utf8' });
|
|
56
|
+
if (result.status !== 0 || result.error) throw new Error(`git ls-remote failed: ${(result.stderr || result.error || 'no output').toString().trim()}`);
|
|
57
|
+
return result.stdout.split('\n').map((line) => line.split(/\s+/)[1] ?? '').filter(Boolean);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** `0.2.18` + `rc` → the next free `0.2.18-rc.<n>`; a stable run keeps its version. */
|
|
61
|
+
export function resolveRunVersion(version: string, prerelease: PrereleaseKind | undefined, deps: UnattendedReleaseDeps = {}): string {
|
|
62
|
+
if (!prerelease) return version;
|
|
63
|
+
if (!isStableVersion(version)) throw new Error(`--prerelease takes the base version x.y.z: ${version}`);
|
|
64
|
+
return nextPrereleaseVersion(version, prerelease, (deps.releaseRefs ?? remoteReleaseRefs)(version, prerelease));
|
|
41
65
|
}
|
|
42
66
|
|
|
43
67
|
function compareVersions(a: string, b: string): number {
|
|
@@ -76,7 +100,7 @@ export function buildReleaseRunInput(version: string, deps: UnattendedReleaseDep
|
|
|
76
100
|
const config = deps.config ?? releaseLoopConfig(deps.configPath);
|
|
77
101
|
const pool = config.gatePodPool;
|
|
78
102
|
if (typeof pool !== 'string' || !pool.trim()) throw new Error('release.loop.gatePodPool is required before graph execution');
|
|
79
|
-
const previousVersion = latestPublishedPreviousVersion(version, deps.ledgerRoot);
|
|
103
|
+
const previousVersion = latestPublishedPreviousVersion(baseVersion(version), deps.ledgerRoot);
|
|
80
104
|
return {
|
|
81
105
|
...config,
|
|
82
106
|
version, previousVersion,
|
|
@@ -87,9 +111,14 @@ export function buildReleaseRunInput(version: string, deps: UnattendedReleaseDep
|
|
|
87
111
|
|
|
88
112
|
/** Fail closed at the entry boundary, before any graph node can change a release. */
|
|
89
113
|
export async function runUnattendedRelease(
|
|
90
|
-
opts: { version: string; dryRun?: boolean; cutCommit?: string; forceFreeze?: boolean },
|
|
114
|
+
opts: { version: string; dryRun?: boolean; cutCommit?: string; forceFreeze?: boolean; mainCut?: boolean; prerelease?: PrereleaseKind },
|
|
91
115
|
deps: UnattendedReleaseDeps = {},
|
|
92
116
|
): Promise<{ input: ReleaseRunInput; dryRun: boolean; state?: GraphRunState }> {
|
|
117
|
+
// RELEASE-BRANCH (10-06): the default cut is a release branch, so main keeps landing and the run no longer needs a
|
|
118
|
+
// landing freeze. A freeze stays the emergency switch: when one is on, it still stops the run (`--force-freeze` overrides).
|
|
119
|
+
// `--cut-commit` (an already-cut branch) and `--main-cut` keep the old main-bump path.
|
|
120
|
+
const branchCut = opts.cutCommit === undefined && !opts.mainCut;
|
|
121
|
+
if (opts.prerelease && !branchCut) throw new Error('--prerelease needs the release-branch cut (no --cut-commit / --main-cut): a rehearsal must never bump main');
|
|
93
122
|
if (!opts.dryRun) {
|
|
94
123
|
const frozen = readLandingFreeze(deps.freezeRoot);
|
|
95
124
|
if (frozen) {
|
|
@@ -97,11 +126,20 @@ export async function runUnattendedRelease(
|
|
|
97
126
|
if (!opts.forceFreeze) throw new LandingFrozenError(frozen);
|
|
98
127
|
}
|
|
99
128
|
}
|
|
100
|
-
const
|
|
129
|
+
const version = resolveRunVersion(opts.version, opts.prerelease, deps);
|
|
130
|
+
const input = buildReleaseRunInput(version, deps, opts.cutCommit);
|
|
101
131
|
if (opts.forceFreeze) input.forceFreeze = true;
|
|
132
|
+
if (branchCut) input.branchCut = true;
|
|
133
|
+
debug.log('release.run', 'input', { version, requested: opts.version, branchCut, prerelease: opts.prerelease ?? null, dryRun: opts.dryRun === true });
|
|
102
134
|
if (opts.dryRun) return { input, dryRun: true };
|
|
103
|
-
|
|
104
|
-
|
|
135
|
+
if (opts.prerelease) {
|
|
136
|
+
// A rehearsal neither judges nor carries the stable version's checklist cells.
|
|
137
|
+
debug.log('release.run', 'checklist-skipped', { version, reason: 'prerelease' });
|
|
138
|
+
} else {
|
|
139
|
+
// Same judgement as the checklist-gate node: past the landing deadline non-P0 yellows carry; P0 yellows block.
|
|
140
|
+
const gate = (deps.checklist ?? ((v: string) => cutChecklistGate(v, getSchedule(v)?.landBy)))(version);
|
|
141
|
+
if (!gate.ok) throw new Error(`release checklist blocked: ${[...gate.red, ...gate.undecided, ...gate.blocked].join(', ')}`);
|
|
142
|
+
}
|
|
105
143
|
const beforeGraph = readLandingFreeze(deps.freezeRoot);
|
|
106
144
|
if (beforeGraph && !opts.forceFreeze) throw new LandingFrozenError(beforeGraph);
|
|
107
145
|
const state = await (deps.graph ?? runGraph)(GRAPH, { input });
|