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
|
@@ -5,6 +5,8 @@ import { Database } from 'bun:sqlite';
|
|
|
5
5
|
import { releaseLedgerRoot } from '../instance/resolve.js';
|
|
6
6
|
import { CliUserError } from '../cli/cli-user-error.js';
|
|
7
7
|
import { debug } from '../debug/log.js';
|
|
8
|
+
import { getUserConfig } from '../user-config.js';
|
|
9
|
+
import { hqSeenGenerationPath, readHqLease, readSeenGeneration } from '../hq/hq.js';
|
|
8
10
|
import { devVersion, type Checklist, type ChecklistHistory, type ChecklistItem } from './checklist.js';
|
|
9
11
|
|
|
10
12
|
export function validateVersion(version: string): void {
|
|
@@ -32,8 +34,8 @@ export function releasedVersion(ledgerRoot = releaseLedgerRoot()): string {
|
|
|
32
34
|
const walWait = new Int32Array(new SharedArrayBuffer(4));
|
|
33
35
|
const SCHEMA_COLUMNS: Record<string, readonly string[]> = {
|
|
34
36
|
imported_versions: ['json_hash', 'imported_at'],
|
|
35
|
-
events: ['reason'],
|
|
36
|
-
assignments: ['priority', 'predecessors', 'deadline_version', 'ceo_minutes', 'ceo_date', 'accelerator'],
|
|
37
|
+
events: ['reason', 'generation'],
|
|
38
|
+
assignments: ['priority', 'predecessors', 'deadline_version', 'ceo_minutes', 'ceo_date', 'accelerator', 'refs'],
|
|
37
39
|
release_schedules: ['freeze_from', 'freeze_until'],
|
|
38
40
|
};
|
|
39
41
|
function schemaCurrent(db: Database): boolean {
|
|
@@ -72,10 +74,12 @@ function open(root = releaseLedgerRoot()): Database {
|
|
|
72
74
|
const columns = db.query('PRAGMA table_info(imported_versions)').all() as Array<{ name: string }>;
|
|
73
75
|
if (!columns.some((column) => column.name === 'json_hash')) db.exec('ALTER TABLE imported_versions ADD COLUMN json_hash TEXT');
|
|
74
76
|
if (!columns.some((column) => column.name === 'imported_at')) db.exec('ALTER TABLE imported_versions ADD COLUMN imported_at TEXT');
|
|
75
|
-
|
|
77
|
+
const eventColumns = db.query('PRAGMA table_info(events)').all() as Array<{ name: string }>;
|
|
78
|
+
if (!eventColumns.some((column) => column.name === 'reason')) db.exec('ALTER TABLE events ADD COLUMN reason TEXT');
|
|
79
|
+
if (!eventColumns.some((column) => column.name === 'generation')) db.exec('ALTER TABLE events ADD COLUMN generation INTEGER');
|
|
76
80
|
db.exec('CREATE TABLE IF NOT EXISTS release_schedules (version TEXT PRIMARY KEY, cut_at TEXT NOT NULL, land_by TEXT, updated_at TEXT NOT NULL, updated_by TEXT NOT NULL)');
|
|
77
81
|
const assignmentColumns = db.query('PRAGMA table_info(assignments)').all() as Array<{ name: string }>;
|
|
78
|
-
for (const [name, sql] of [['priority', 'TEXT'], ['predecessors', 'TEXT'], ['deadline_version', 'TEXT'], ['ceo_minutes', 'INTEGER'], ['ceo_date', 'TEXT'], ['accelerator', 'INTEGER']] as const) {
|
|
82
|
+
for (const [name, sql] of [['priority', 'TEXT'], ['predecessors', 'TEXT'], ['deadline_version', 'TEXT'], ['ceo_minutes', 'INTEGER'], ['ceo_date', 'TEXT'], ['accelerator', 'INTEGER'], ['refs', 'TEXT']] as const) {
|
|
79
83
|
if (!assignmentColumns.some((column) => column.name === name)) db.exec(`ALTER TABLE assignments ADD COLUMN ${name} ${sql}`);
|
|
80
84
|
}
|
|
81
85
|
const scheduleColumns = db.query('PRAGMA table_info(release_schedules)').all() as Array<{ name: string }>;
|
|
@@ -155,21 +159,21 @@ function readTransaction<T>(db: Database, work: () => T): T {
|
|
|
155
159
|
catch (error) { db.exec('ROLLBACK'); throw error; }
|
|
156
160
|
}
|
|
157
161
|
|
|
158
|
-
type EventRow = { seq: number; at: string; by: string; feature_id: string; version: string; field: string; from: string; to: string; released: string; dev: string; reason: string | null };
|
|
162
|
+
type EventRow = { seq: number; at: string; by: string; feature_id: string; version: string; field: string; from: string; to: string; released: string; dev: string; reason: string | null; generation: number | null };
|
|
159
163
|
function decode(value: string | null): unknown { return value === null ? null : JSON.parse(value); }
|
|
160
164
|
function historyRow(row: EventRow): ChecklistHistory {
|
|
161
165
|
return { at: row.at, by: row.by, id: row.feature_id, field: row.field, from: decode(row.from), to: decode(row.to), released: row.released, dev: row.dev,
|
|
162
|
-
...(row.reason !== null ? { reason: row.reason } : {}) };
|
|
166
|
+
...(row.reason !== null ? { reason: row.reason } : {}), ...(row.generation !== null ? { generation: row.generation } : {}) };
|
|
163
167
|
}
|
|
164
168
|
function record(db: Database, version: string, entry: ChecklistHistory): void {
|
|
165
|
-
db.query('INSERT INTO events (at, by, feature_id, version, field, "from", "to", released, dev, reason) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')
|
|
166
|
-
.run(entry.at, entry.by, entry.id, version, entry.field, JSON.stringify(entry.from ?? null), JSON.stringify(entry.to ?? null), entry.released, entry.dev, entry.reason ?? null);
|
|
169
|
+
db.query('INSERT INTO events (at, by, feature_id, version, field, "from", "to", released, dev, reason, generation) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')
|
|
170
|
+
.run(entry.at, entry.by, entry.id, version, entry.field, JSON.stringify(entry.from ?? null), JSON.stringify(entry.to ?? null), entry.released, entry.dev, entry.reason ?? null, entry.generation ?? null);
|
|
167
171
|
}
|
|
168
172
|
function evidenceRefs(db: Database, version: string, id: string): string[] {
|
|
169
173
|
return (db.query('SELECT ref FROM evidence WHERE feature_id = ? AND version = ? ORDER BY rowid').all(id, version) as Array<{ ref: string }>).map((row) => row.ref);
|
|
170
174
|
}
|
|
171
175
|
function items(db: Database, version: string): ChecklistItem[] {
|
|
172
|
-
const rows = db.query(`SELECT f.id, COALESCE(a.title_override, f.title) AS title, a.owner, a.kind, a.priority, a.predecessors, a.deadline_version, a.ceo_minutes, a.ceo_date, a.accelerator, a.status, a.disposition, a.evidence, a.updated_at, a.updated_by
|
|
176
|
+
const rows = db.query(`SELECT f.id, COALESCE(a.title_override, f.title) AS title, a.owner, a.kind, a.priority, a.predecessors, a.deadline_version, a.ceo_minutes, a.ceo_date, a.accelerator, a.refs, a.status, a.disposition, a.evidence, a.updated_at, a.updated_by
|
|
173
177
|
FROM assignments a JOIN features f ON f.id = a.feature_id WHERE a.version = ? ORDER BY a.rowid`).all(version) as Array<Record<string, string | number | null>>;
|
|
174
178
|
return rows.map((r) => {
|
|
175
179
|
const evidence = [r.evidence, ...evidenceRefs(db, version, r.id as string)].filter((value) => value !== null).join('\n');
|
|
@@ -179,6 +183,7 @@ function items(db: Database, version: string): ChecklistItem[] {
|
|
|
179
183
|
...(r.deadline_version !== null ? { deadlineVersion: r.deadline_version as string } : {}),
|
|
180
184
|
...(r.ceo_minutes !== null ? { ceoMinutes: r.ceo_minutes as number } : {}), ...(r.ceo_date !== null ? { ceoDate: r.ceo_date as string } : {}),
|
|
181
185
|
...(r.accelerator === 1 ? { accelerator: true as const } : {}),
|
|
186
|
+
...(r.refs !== null && r.refs !== undefined ? { refs: JSON.parse(r.refs as string) as string[] } : {}),
|
|
182
187
|
...(r.evidence !== null || evidence ? { evidence } : {}), ...(r.disposition !== null ? { disposition: r.disposition as ChecklistItem['disposition'] } : {}),
|
|
183
188
|
updatedAt: r.updated_at as string, updatedBy: r.updated_by as string };
|
|
184
189
|
});
|
|
@@ -192,9 +197,9 @@ function putItem(db: Database, version: string, item: ChecklistItem): void {
|
|
|
192
197
|
if (!feature) db.query('INSERT INTO features (id, title, owner, kind, created_at) VALUES (?, ?, ?, ?, ?)').run(item.id, item.title, item.owner ?? null, item.kind ?? null, item.updatedAt);
|
|
193
198
|
// features.owner/kind follow the latest assignment so details() and the checklist never disagree after a set.
|
|
194
199
|
else if (item.owner !== undefined || item.kind !== undefined) db.query('UPDATE features SET owner = COALESCE(?, owner), kind = COALESCE(?, kind) WHERE id = ?').run(item.owner ?? null, item.kind ?? null, item.id);
|
|
195
|
-
db.query(`INSERT INTO assignments (feature_id, version, status, disposition, evidence, title_override, owner, kind, priority, predecessors, deadline_version, ceo_minutes, ceo_date, accelerator, updated_at, updated_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
196
|
-
ON CONFLICT(feature_id, version) DO UPDATE SET status=excluded.status, disposition=excluded.disposition, evidence=excluded.evidence, owner=excluded.owner, kind=excluded.kind, priority=excluded.priority, predecessors=excluded.predecessors, deadline_version=excluded.deadline_version, ceo_minutes=excluded.ceo_minutes, ceo_date=excluded.ceo_date, accelerator=excluded.accelerator, updated_at=excluded.updated_at, updated_by=excluded.updated_by`)
|
|
197
|
-
.run(item.id, version, item.status, item.disposition ?? null, item.evidence ?? null, feature && feature.title !== item.title ? item.title : null, item.owner ?? null, item.kind ?? null, item.priority ?? null, item.predecessors ? JSON.stringify(item.predecessors) : null, item.deadlineVersion ?? null, item.ceoMinutes ?? null, item.ceoDate ?? null, item.accelerator === true ? 1 : null, item.updatedAt, item.updatedBy);
|
|
200
|
+
db.query(`INSERT INTO assignments (feature_id, version, status, disposition, evidence, title_override, owner, kind, priority, predecessors, deadline_version, ceo_minutes, ceo_date, accelerator, refs, updated_at, updated_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
201
|
+
ON CONFLICT(feature_id, version) DO UPDATE SET status=excluded.status, disposition=excluded.disposition, evidence=excluded.evidence, owner=excluded.owner, kind=excluded.kind, priority=excluded.priority, predecessors=excluded.predecessors, deadline_version=excluded.deadline_version, ceo_minutes=excluded.ceo_minutes, ceo_date=excluded.ceo_date, accelerator=excluded.accelerator, refs=excluded.refs, updated_at=excluded.updated_at, updated_by=excluded.updated_by`)
|
|
202
|
+
.run(item.id, version, item.status, item.disposition ?? null, item.evidence ?? null, feature && feature.title !== item.title ? item.title : null, item.owner ?? null, item.kind ?? null, item.priority ?? null, item.predecessors ? JSON.stringify(item.predecessors) : null, item.deadlineVersion ?? null, item.ceoMinutes ?? null, item.ceoDate ?? null, item.accelerator === true ? 1 : null, item.refs?.length ? JSON.stringify(item.refs) : null, item.updatedAt, item.updatedBy);
|
|
198
203
|
}
|
|
199
204
|
function jsonHash(contents: string | Buffer): string { return createHash('sha256').update(contents).digest('hex'); }
|
|
200
205
|
|
|
@@ -235,8 +240,8 @@ function importOnDb(db: Database, version: string): boolean {
|
|
|
235
240
|
// Keep a legacy per-version title until an explicit retitle unifies the feature identity.
|
|
236
241
|
if (!existing) putItem(db, version, item);
|
|
237
242
|
else {
|
|
238
|
-
db.query('INSERT OR IGNORE INTO assignments (feature_id, version, status, disposition, evidence, title_override, owner, kind, priority, predecessors, deadline_version, ceo_minutes, ceo_date, accelerator, updated_at, updated_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')
|
|
239
|
-
.run(item.id, version, item.status, item.disposition ?? null, item.evidence ?? null, item.title === existing.title ? null : item.title, item.owner ?? null, item.kind ?? null, item.priority ?? null, item.predecessors ? JSON.stringify(item.predecessors) : null, item.deadlineVersion ?? null, item.ceoMinutes ?? null, item.ceoDate ?? null, item.accelerator === true ? 1 : null, item.updatedAt, item.updatedBy);
|
|
243
|
+
db.query('INSERT OR IGNORE INTO assignments (feature_id, version, status, disposition, evidence, title_override, owner, kind, priority, predecessors, deadline_version, ceo_minutes, ceo_date, accelerator, refs, updated_at, updated_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')
|
|
244
|
+
.run(item.id, version, item.status, item.disposition ?? null, item.evidence ?? null, item.title === existing.title ? null : item.title, item.owner ?? null, item.kind ?? null, item.priority ?? null, item.predecessors ? JSON.stringify(item.predecessors) : null, item.deadlineVersion ?? null, item.ceoMinutes ?? null, item.ceoDate ?? null, item.accelerator === true ? 1 : null, item.refs?.length ? JSON.stringify(item.refs) : null, item.updatedAt, item.updatedBy);
|
|
240
245
|
}
|
|
241
246
|
}
|
|
242
247
|
for (const entry of data.history) record(db, version, entry);
|
|
@@ -263,7 +268,7 @@ function importOnDb(db: Database, version: string): boolean {
|
|
|
263
268
|
record(db, version, { at: new Date().toISOString(), by: item.updatedBy, id: item.id, field: 'reimport', from: null, to: item, released: data.released, dev: data.dev });
|
|
264
269
|
continue;
|
|
265
270
|
}
|
|
266
|
-
const fields = ['title', 'status', 'evidence', 'owner', 'disposition', 'kind', 'priority', 'predecessors', 'deadlineVersion', 'ceoMinutes', 'ceoDate', 'accelerator'] as const;
|
|
271
|
+
const fields = ['title', 'status', 'evidence', 'owner', 'disposition', 'kind', 'priority', 'predecessors', 'deadlineVersion', 'ceoMinutes', 'ceoDate', 'accelerator', 'refs'] as const;
|
|
267
272
|
const baseEvidence = (db.query('SELECT evidence FROM assignments WHERE feature_id = ? AND version = ?').get(item.id, version) as { evidence: string | null }).evidence ?? undefined;
|
|
268
273
|
const preserveRefs = item.evidence === baseEvidence || item.evidence === current.evidence;
|
|
269
274
|
const differs = fields.some((field) => field === 'evidence'
|
|
@@ -333,6 +338,30 @@ export interface ChecklistCollision { version: string; owner?: string; title: st
|
|
|
333
338
|
|
|
334
339
|
export function mutate(version: string, released: string, dev: string, apply: (data: Checklist, otherItems: (id: string) => ChecklistCollision[]) => boolean): Checklist {
|
|
335
340
|
validateVersion(version);
|
|
341
|
+
// COORD-HA ①(RFC-loop-agent-map §A8b): a write carries the generation this process saw; a stale one is refused only
|
|
342
|
+
// under hq.boardFencing 'enforce' (default shadow = write and log). The lease is read ONCE, before the write transaction —
|
|
343
|
+
// with an ssh arbiter a lease read inside the transaction would hold the ledger write lock across the network.
|
|
344
|
+
// Only 'enforce' (opt-in) re-reads once inside the transaction just before writing, so a lease taken over meanwhile
|
|
345
|
+
// still rolls the write back; shadow never touches the lease under the lock.
|
|
346
|
+
const hq = getUserConfig().hq;
|
|
347
|
+
const writerGeneration = hq ? readSeenGeneration(hqSeenGenerationPath(hq)) : null;
|
|
348
|
+
let recordedGeneration: number | null = null;
|
|
349
|
+
let firstMeasured = false;
|
|
350
|
+
if (hq) {
|
|
351
|
+
let lease: ReturnType<typeof readHqLease>['record'] | undefined;
|
|
352
|
+
try { lease = readHqLease({ config: hq }).record; }
|
|
353
|
+
catch (error) { debug.log('hq.fence', 'board-lease-unmeasured', { writer: writerGeneration, version, reason: String(error).slice(0, 200) }); }
|
|
354
|
+
// A missing lease (null) and a broken one (parseLease throws → caught above with its reason) are both «unmeasured».
|
|
355
|
+
if (lease === null) debug.log('hq.fence', 'board-lease-unmeasured', { writer: writerGeneration, version, reason: 'no-lease' });
|
|
356
|
+
if (lease) {
|
|
357
|
+
firstMeasured = true;
|
|
358
|
+
recordedGeneration = writerGeneration;
|
|
359
|
+
if (writerGeneration !== null && writerGeneration < lease.generation) {
|
|
360
|
+
debug.log('hq.fence', 'board-stale-write', { writer: writerGeneration, lease: lease.generation, version });
|
|
361
|
+
if (hq.boardFencing === 'enforce') throw new CliUserError(`낡은 세대 ${writerGeneration} < ${lease.generation} — 쓰기 거부`);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
336
365
|
const db = open();
|
|
337
366
|
try {
|
|
338
367
|
let imported = false;
|
|
@@ -351,6 +380,22 @@ export function mutate(version: string, released: string, dev: string, apply: (d
|
|
|
351
380
|
return { version: item.version, ...(item.owner !== null ? { owner: item.owner } : {}), title: item.title };
|
|
352
381
|
});
|
|
353
382
|
})) {
|
|
383
|
+
if (hq?.boardFencing === 'enforce') {
|
|
384
|
+
let late: ReturnType<typeof readHqLease>['record'] = null;
|
|
385
|
+
let lateReason = 'no-lease';
|
|
386
|
+
try { late = readHqLease({ config: hq }).record; }
|
|
387
|
+
catch (error) { late = null; lateReason = String(error).slice(0, 200); }
|
|
388
|
+
if (!late) {
|
|
389
|
+
// Unmeasured right before the write: the write still goes through, but carries no generation it cannot vouch for.
|
|
390
|
+
// (Logged only when the first read had measured — an unmeasured first read was already logged once.)
|
|
391
|
+
if (firstMeasured) debug.log('hq.fence', 'board-lease-unmeasured', { writer: writerGeneration, version, reason: lateReason, phase: 'before-write' });
|
|
392
|
+
recordedGeneration = null;
|
|
393
|
+
}
|
|
394
|
+
if (late && writerGeneration !== null && writerGeneration < late.generation) {
|
|
395
|
+
debug.log('hq.fence', 'board-stale-write', { writer: writerGeneration, lease: late.generation, version });
|
|
396
|
+
throw new CliUserError(`낡은 세대 ${writerGeneration} < ${late.generation} — 쓰기 거부`);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
354
399
|
for (const item of data.items) if (before.get(item.id) !== JSON.stringify(item)) {
|
|
355
400
|
const previous = before.get(item.id);
|
|
356
401
|
const evidenceChanged = previous && (JSON.parse(previous) as ChecklistItem).evidence !== item.evidence;
|
|
@@ -364,7 +409,10 @@ export function mutate(version: string, released: string, dev: string, apply: (d
|
|
|
364
409
|
db.query('DELETE FROM evidence WHERE feature_id = ? AND version = ?').run(id, version);
|
|
365
410
|
db.query('DELETE FROM assignments WHERE feature_id = ? AND version = ?').run(id, version);
|
|
366
411
|
}
|
|
367
|
-
for (const entry of data.history.slice(count))
|
|
412
|
+
for (const entry of data.history.slice(count)) {
|
|
413
|
+
if (recordedGeneration !== null) entry.generation = recordedGeneration;
|
|
414
|
+
record(db, version, entry);
|
|
415
|
+
}
|
|
368
416
|
}
|
|
369
417
|
return data;
|
|
370
418
|
});
|
|
@@ -8,6 +8,7 @@ export interface LandingFreeze {
|
|
|
8
8
|
startedAt: string;
|
|
9
9
|
until: string | null;
|
|
10
10
|
by: string;
|
|
11
|
+
holdLaunches?: boolean;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export function landingFreezePath(root = effectiveInstanceRoot()): string {
|
|
@@ -25,21 +26,22 @@ export function readLandingFreeze(root = effectiveInstanceRoot(), now = new Date
|
|
|
25
26
|
const freeze = record as Partial<LandingFreeze>;
|
|
26
27
|
if (typeof freeze.reason !== 'string' || typeof freeze.by !== 'string' ||
|
|
27
28
|
typeof freeze.startedAt !== 'string' || !Number.isFinite(Date.parse(freeze.startedAt)) ||
|
|
28
|
-
!(freeze.until === null || (typeof freeze.until === 'string' && Number.isFinite(Date.parse(freeze.until))))
|
|
29
|
+
!(freeze.until === null || (typeof freeze.until === 'string' && Number.isFinite(Date.parse(freeze.until)))) ||
|
|
30
|
+
(freeze.holdLaunches !== undefined && typeof freeze.holdLaunches !== 'boolean')) {
|
|
29
31
|
throw new Error(`invalid landing freeze: ${path}`);
|
|
30
32
|
}
|
|
31
33
|
// An expired freeze reads as «off». The file is left alone: deleting it here could race a concurrent `freeze on`
|
|
32
34
|
// that just wrote a new one (only `freeze off` removes it; the next `freeze on` overwrites it).
|
|
33
35
|
if (freeze.until && Date.parse(freeze.until) <= now.getTime()) return null;
|
|
34
|
-
return freeze as LandingFreeze;
|
|
36
|
+
return { ...freeze, holdLaunches: freeze.holdLaunches ?? false } as LandingFreeze;
|
|
35
37
|
}
|
|
36
38
|
|
|
37
|
-
export function enableLandingFreeze(opts: { reason?: string; until?: string; by?: string }, root = effectiveInstanceRoot(), now = new Date()): LandingFreeze {
|
|
39
|
+
export function enableLandingFreeze(opts: { reason?: string; until?: string; by?: string; holdLaunches?: boolean }, root = effectiveInstanceRoot(), now = new Date()): LandingFreeze {
|
|
38
40
|
const until = opts.until === undefined ? null : opts.until;
|
|
39
41
|
if (until !== null && (!/^\d{4}-\d\d-\d\dT\d\d:\d\d(?::\d\d(?:\.\d+)?)?(?:Z|[+-]\d\d:\d\d)$/.test(until) || !Number.isFinite(Date.parse(until)) || Date.parse(until) <= now.getTime())) {
|
|
40
42
|
throw new Error('--until must be a future ISO timestamp with timezone');
|
|
41
43
|
}
|
|
42
|
-
const freeze: LandingFreeze = { reason: opts.reason?.trim() || 'operator freeze', startedAt: now.toISOString(), until: until === null ? null : new Date(until).toISOString(), by: opts.by?.trim() || process.env.ELANOUS_TRACK || process.env.USER || 'cli' };
|
|
44
|
+
const freeze: LandingFreeze = { reason: opts.reason?.trim() || 'operator freeze', startedAt: now.toISOString(), until: until === null ? null : new Date(until).toISOString(), by: opts.by?.trim() || process.env.ELANOUS_TRACK || process.env.USER || 'cli', holdLaunches: opts.holdLaunches ?? false };
|
|
43
45
|
const path = landingFreezePath(root);
|
|
44
46
|
mkdirSync(root, { recursive: true });
|
|
45
47
|
const tmp = `${path}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/** LIGHT-RC (0.2.19) — a light release-candidate check between cuts: the changed-file self gate over everything that
|
|
2
|
+
* landed since the previous release's cut, plus an always-run test list (release/light-rc-always.txt), on the remote
|
|
3
|
+
* gate host (GATE-REMOTE). Read-only for the release ledger: it reads the previous published version and never writes.
|
|
4
|
+
* It does not touch the landing freeze or launch Pods. */
|
|
5
|
+
import { spawnSync } from 'node:child_process';
|
|
6
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { debug } from '../debug/log.js';
|
|
9
|
+
import { runSelfGateCli, type SelfGateCliOptions, type SelfGateCliResult } from '../self-implement/gate-cli.js';
|
|
10
|
+
import { dispatchHeavyCheck, isValidGateHost, type DispatchDeps, type DispatchOptions } from '../self-implement/gate-remote.js';
|
|
11
|
+
import { latestPublishedPreviousVersion } from '../../scripts/release-loop/unattended-release.js';
|
|
12
|
+
import { baseVersion, isReleaseVersion } from '../../scripts/release-loop/release-version.js';
|
|
13
|
+
|
|
14
|
+
export const LIGHT_RC_ALWAYS_FILE = 'release/light-rc-always.txt';
|
|
15
|
+
export const LIGHT_RC_DEFAULT_SHARDS = 4;
|
|
16
|
+
export const LIGHT_RC_NO_PREVIOUS_CUT = '직전 판 컷을 못 찾았다 — --base 로 준다';
|
|
17
|
+
|
|
18
|
+
export interface LightRcOptions {
|
|
19
|
+
base?: string;
|
|
20
|
+
/** Gate host; absent = config gateRemote.host (the dispatch's own default). */
|
|
21
|
+
remote?: string;
|
|
22
|
+
/** Run on this machine (the remote child is re-invoked with it). */
|
|
23
|
+
local?: boolean;
|
|
24
|
+
shards?: number;
|
|
25
|
+
/** Release ledger root for the previous-version lookup (release run's deps.ledgerRoot). */
|
|
26
|
+
ledgerRoot?: string;
|
|
27
|
+
log?: (line: string) => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface LightRcResult {
|
|
31
|
+
version: string;
|
|
32
|
+
base: string;
|
|
33
|
+
/** New failures vs. the base; null = the gate produced no measurement (never read as green). */
|
|
34
|
+
introduced: number | null;
|
|
35
|
+
preexisting: number | null;
|
|
36
|
+
/** Failures the baseline could not classify (unknown + precondition-unmet); null = unmeasured. Non-zero is not green. */
|
|
37
|
+
unclassified: number | null;
|
|
38
|
+
alwaysInclude: number;
|
|
39
|
+
alwaysIncludeMissing: string[];
|
|
40
|
+
shards: number;
|
|
41
|
+
/** Host the gate ran on; null = this machine. */
|
|
42
|
+
remote: string | null;
|
|
43
|
+
gateExitCode: number;
|
|
44
|
+
ok: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface LightRcDeps {
|
|
48
|
+
cwd?: string;
|
|
49
|
+
git?: (args: string[], cwd: string) => { rc: number | null; stdout: string };
|
|
50
|
+
previousVersion?: (version: string, ledgerRoot?: string) => string;
|
|
51
|
+
readFile?: (path: string) => string | undefined;
|
|
52
|
+
runGate?: (cwd: string, options: SelfGateCliOptions) => SelfGateCliResult;
|
|
53
|
+
dispatch?: (opts: DispatchOptions, deps?: DispatchDeps) => Promise<number>;
|
|
54
|
+
/** Extra dispatch deps (runner · settings) — tests drive the real dispatch with a fake host. */
|
|
55
|
+
dispatchDeps?: Omit<DispatchDeps, 'write'>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Shown when the gate ran remotely but the host name did not come back. */
|
|
59
|
+
export const LIGHT_RC_UNKNOWN_HOST = '(remote · host unknown)';
|
|
60
|
+
|
|
61
|
+
const defaultGit = (args: string[], cwd: string) => {
|
|
62
|
+
const run = spawnSync('git', args, { cwd, encoding: 'utf8' });
|
|
63
|
+
return { rc: run.error ? null : run.status, stdout: run.stdout ?? '' };
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/** One repository-relative path per line; `#` starts a comment; blank lines are skipped. */
|
|
67
|
+
export function parseAlwaysIncludeList(text: string): string[] {
|
|
68
|
+
return [...new Set(text.split('\n').map((line) => line.replace(/#.*/, '').trim()).filter(Boolean))];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** The previous release's cut: merge-base of origin/main and origin/release/<previous published version>. */
|
|
72
|
+
export function previousCutBase(version: string, deps: LightRcDeps & { cwd: string; ledgerRoot?: string }): string {
|
|
73
|
+
let previous: string;
|
|
74
|
+
try {
|
|
75
|
+
previous = (deps.previousVersion ?? latestPublishedPreviousVersion)(baseVersion(version), deps.ledgerRoot);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
throw new Error(`${LIGHT_RC_NO_PREVIOUS_CUT} (${error instanceof Error ? error.message : String(error)})`);
|
|
78
|
+
}
|
|
79
|
+
const mergeBase = (deps.git ?? defaultGit)(['merge-base', 'origin/main', `origin/release/${previous}`], deps.cwd);
|
|
80
|
+
const sha = mergeBase.stdout.trim();
|
|
81
|
+
if (mergeBase.rc !== 0 || !sha) throw new Error(`${LIGHT_RC_NO_PREVIOUS_CUT} (git merge-base origin/main origin/release/${previous} rc=${mergeBase.rc})`);
|
|
82
|
+
return sha;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function repoRoot(deps: LightRcDeps): string {
|
|
86
|
+
if (deps.cwd) return deps.cwd;
|
|
87
|
+
const top = defaultGit(['rev-parse', '--show-toplevel'], process.cwd());
|
|
88
|
+
if (top.rc !== 0 || !top.stdout.trim()) throw new Error('release light-rc: not inside a git repository');
|
|
89
|
+
return top.stdout.trim();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function parseRemoteResult(stdout: string): Partial<LightRcResult> | undefined {
|
|
93
|
+
const line = stdout.split('\n').map((l) => l.trim()).reverse().find((l) => l.startsWith('{'));
|
|
94
|
+
if (!line) return undefined;
|
|
95
|
+
try {
|
|
96
|
+
const parsed = JSON.parse(line) as Partial<LightRcResult> & { host?: unknown };
|
|
97
|
+
return parsed && typeof parsed === 'object' ? parsed : undefined;
|
|
98
|
+
} catch { return undefined; }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export async function runLightRc(version: string, opts: LightRcOptions = {}, deps: LightRcDeps = {}): Promise<LightRcResult> {
|
|
102
|
+
if (!isReleaseVersion(version)) throw new Error(`release version must be x.y.z: ${version}`);
|
|
103
|
+
const shards = opts.shards ?? LIGHT_RC_DEFAULT_SHARDS;
|
|
104
|
+
if (!Number.isSafeInteger(shards) || shards < 1) throw new Error(`invalid --shards count: ${opts.shards}`);
|
|
105
|
+
if (opts.remote !== undefined && !isValidGateHost(opts.remote)) throw new Error(`invalid --remote host: ${opts.remote}`);
|
|
106
|
+
const log = opts.log ?? ((line: string) => process.stderr.write(`${line}\n`));
|
|
107
|
+
const cwd = repoRoot(deps);
|
|
108
|
+
const base = opts.base ?? previousCutBase(version, { ...deps, cwd, ledgerRoot: opts.ledgerRoot });
|
|
109
|
+
const listPath = join(cwd, LIGHT_RC_ALWAYS_FILE);
|
|
110
|
+
const listText = (deps.readFile ?? ((path: string) => existsSync(path) ? readFileSync(path, 'utf8') : undefined))(listPath);
|
|
111
|
+
if (listText === undefined) throw new Error(`release light-rc: ${LIGHT_RC_ALWAYS_FILE} not found`);
|
|
112
|
+
const alwaysInclude = parseAlwaysIncludeList(listText);
|
|
113
|
+
|
|
114
|
+
let local: SelfGateCliResult | undefined;
|
|
115
|
+
const runLocal = () => {
|
|
116
|
+
local = (deps.runGate ?? runSelfGateCli)(cwd, { base, shards, alwaysInclude });
|
|
117
|
+
for (const line of local.lines) log(line);
|
|
118
|
+
return local.exitCode;
|
|
119
|
+
};
|
|
120
|
+
const captured: Buffer[] = [];
|
|
121
|
+
const rc = await (deps.dispatch ?? dispatchHeavyCheck)({
|
|
122
|
+
tool: 'self-gate', repo: cwd, installPwa: true, refs: [base], json: true,
|
|
123
|
+
// The host re-runs this command with --local (and ELANOUS_GATE_REMOTE_CHILD=1), so it never recurses.
|
|
124
|
+
remoteArgv: (sha) => ['bun', 'bin/elanous.mjs', 'release', 'light-rc', '--version', version, '--base', sha(base), '--shards', String(shards), '--local', '--json'],
|
|
125
|
+
flags: { remote: opts.local ? undefined : opts.remote ?? true, local: !!opts.local },
|
|
126
|
+
runLocal,
|
|
127
|
+
}, {
|
|
128
|
+
...deps.dispatchDeps,
|
|
129
|
+
write: {
|
|
130
|
+
out: (data) => { captured.push(Buffer.from(data)); },
|
|
131
|
+
err: (data) => { process.stderr.write(data); },
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
let result: LightRcResult;
|
|
136
|
+
if (local) {
|
|
137
|
+
result = {
|
|
138
|
+
version, base,
|
|
139
|
+
introduced: local.baseline?.introduced ?? null,
|
|
140
|
+
preexisting: local.baseline?.preexisting ?? null,
|
|
141
|
+
unclassified: local.baseline ? local.baseline.unknown + local.baseline.preconditionUnmet : null,
|
|
142
|
+
alwaysInclude: alwaysInclude.length,
|
|
143
|
+
alwaysIncludeMissing: [...local.alwaysIncludeMissing],
|
|
144
|
+
shards, remote: null, gateExitCode: local.exitCode, ok: false,
|
|
145
|
+
};
|
|
146
|
+
} else {
|
|
147
|
+
const remote = parseRemoteResult(Buffer.concat(captured).toString('utf8'));
|
|
148
|
+
if (!remote) throw new Error(`release light-rc: remote gate returned no result (rc=${rc})`);
|
|
149
|
+
const host = (remote as { host?: unknown }).host;
|
|
150
|
+
result = {
|
|
151
|
+
version, base,
|
|
152
|
+
introduced: typeof remote.introduced === 'number' ? remote.introduced : null,
|
|
153
|
+
preexisting: typeof remote.preexisting === 'number' ? remote.preexisting : null,
|
|
154
|
+
unclassified: typeof remote.unclassified === 'number' ? remote.unclassified : null,
|
|
155
|
+
alwaysInclude: alwaysInclude.length,
|
|
156
|
+
alwaysIncludeMissing: Array.isArray(remote.alwaysIncludeMissing) ? remote.alwaysIncludeMissing.map(String) : [],
|
|
157
|
+
shards, remote: typeof host === 'string' && host ? host : opts.remote ?? LIGHT_RC_UNKNOWN_HOST,
|
|
158
|
+
gateExitCode: typeof remote.gateExitCode === 'number' ? remote.gateExitCode : rc, ok: false,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
// Unmeasured (null) or unclassified failures are never green; policy-gate findings in the gate output are reported
|
|
162
|
+
// (gate rc), not a regression.
|
|
163
|
+
result.ok = result.introduced === 0 && result.unclassified === 0;
|
|
164
|
+
debug.log('release.light-rc', 'done', { version, base, introduced: result.introduced, preexisting: result.preexisting, unclassified: result.unclassified, shards, remote: result.remote });
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function formatLightRc(result: LightRcResult): string[] {
|
|
169
|
+
return [
|
|
170
|
+
`${result.ok ? '✅' : '⛔'} light-rc ${result.version} · base ${result.base.slice(0, 12)} · ${result.remote ? `host ${result.remote}` : 'local'} · shards ${result.shards}`,
|
|
171
|
+
` introduced ${result.introduced ?? '못 쟀다'} · preexisting ${result.preexisting ?? '못 쟀다'} · unclassified ${result.unclassified ?? '못 쟀다'} · always-include ${result.alwaysInclude}${result.alwaysIncludeMissing.length ? ` (missing: ${result.alwaysIncludeMissing.join(', ')})` : ''} · gate rc ${result.gateExitCode}`,
|
|
172
|
+
];
|
|
173
|
+
}
|
|
@@ -2,12 +2,14 @@ import { spawnSync } from 'node:child_process';
|
|
|
2
2
|
import { existsSync, readFileSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { CliUserError } from '../cli/cli-user-error.js';
|
|
5
|
+
import { resolveRepositoryName } from '../harness/repository-name.js';
|
|
5
6
|
import { getElanousConfigDirOverride } from '../elanous-config-dir.js';
|
|
6
7
|
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
7
8
|
import { userConfigPath } from '../user-config.js';
|
|
8
9
|
import { addItem, listChecklist, ownerMatches, parseOwner, setItem, validateCeoLoad, type ChecklistItem } from './checklist.js';
|
|
9
10
|
import { move, releasedVersion } from './feature-store.js';
|
|
10
11
|
import { listSchedules, type ReleaseSchedule } from './release-schedule.js';
|
|
12
|
+
import { parseRubric, rubricScore } from './rubric.js';
|
|
11
13
|
|
|
12
14
|
export type PlacementPriority = 'P0' | 'P1' | 'P2';
|
|
13
15
|
export interface PlacementCell { id: string; title: string; owner: string; priority: PlacementPriority; predecessors: string[]; deadlineVersion?: string; ceoMinutes?: number; ceoDate?: string; accelerator?: boolean }
|
|
@@ -39,10 +41,41 @@ function versionOrder(a: string, b: string): number {
|
|
|
39
41
|
return 0;
|
|
40
42
|
}
|
|
41
43
|
|
|
44
|
+
/** Where each cell id lives. An id may repeat when one copy is in an already released version — history kept two
|
|
45
|
+
* different cells under one id (IA1 · EV10a … in 0.2.6~0.2.15) and later cells reused old ids (INSIDE1 0.2.11 ⊕
|
|
46
|
+
* 0.2.19). A released copy is history: an open copy wins over it, and between released copies the latest wins. Only
|
|
47
|
+
* two open copies are a real conflict (ORCH-LIVE-1008 rehearsal 1: every placement failed on «여러 판의 같은 칸»). */
|
|
48
|
+
function cellLocations(snapshots: Iterable<[string, readonly { id: string }[]]>, released: string | null | undefined, skip: readonly string[] = []): { locations: Map<string, string>; conflict: string | null } {
|
|
49
|
+
const locations = new Map<string, string>();
|
|
50
|
+
const shipped = (version: string) => !!released && versionOrder(version, released) <= 0;
|
|
51
|
+
for (const [version, items] of snapshots) {
|
|
52
|
+
if (skip.includes(version)) continue;
|
|
53
|
+
for (const item of items) {
|
|
54
|
+
const previous = locations.get(item.id);
|
|
55
|
+
if (previous !== undefined) {
|
|
56
|
+
if (!shipped(previous) && !shipped(version)) return { locations, conflict: item.id };
|
|
57
|
+
// Keep the open copy; between two released copies keep the later one.
|
|
58
|
+
const keepPrevious = shipped(version) && (!shipped(previous) || versionOrder(version, previous) < 0);
|
|
59
|
+
if (keepPrevious) continue;
|
|
60
|
+
}
|
|
61
|
+
locations.set(item.id, version);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return { locations, conflict: null };
|
|
65
|
+
}
|
|
66
|
+
|
|
42
67
|
/** The merged PR list is a first-class GitHub query, not a count inferred from checklist status. */
|
|
68
|
+
// ORCH-LIVE-1008: the orchestrator tick runs from the installed package (not a git checkout), where a bare `gh pr list`
|
|
69
|
+
// fails «not a git repository» and placement silently left every cell unplaced — so the repository is named explicitly
|
|
70
|
+
// (--repo > harness.repo > GH_REPO > the checkout's remote · `resolveRepositoryName`).
|
|
43
71
|
export function mergedPrsLast24h(now = new Date()): number {
|
|
44
72
|
const since = new Date(now.getTime() - 86_400_000).toISOString();
|
|
45
|
-
|
|
73
|
+
let repo: string;
|
|
74
|
+
try { repo = resolveRepositoryName(); }
|
|
75
|
+
catch (error) {
|
|
76
|
+
throw new CliUserError(`병합 PR 속도 조회 실패: 저장소를 못 정했다 — ${(error instanceof Error ? error.message : String(error)).split('\n')[0]}`, 'harness.repo 설정(owner/name) 또는 GH_REPO');
|
|
77
|
+
}
|
|
78
|
+
const r = spawnSync('gh', ['pr', 'list', '--repo', repo, '--state', 'merged', '--search', `merged:>=${since.slice(0, 10)}`, '--limit', '1000', '--json', 'number,mergedAt'], { encoding: 'utf8' });
|
|
46
79
|
if (r.status !== 0) throw new CliUserError(`병합 PR 속도 조회 실패: ${(r.stderr || r.error || '').toString().trim()}`);
|
|
47
80
|
let rows: Array<{ number: number; mergedAt: string }>;
|
|
48
81
|
try { rows = JSON.parse(r.stdout) as typeof rows; }
|
|
@@ -91,10 +124,16 @@ function dependencyViolation(
|
|
|
91
124
|
locations: Map<string, string>,
|
|
92
125
|
overrides: Map<string, string>,
|
|
93
126
|
input: PlacementCell,
|
|
127
|
+
released?: string | null,
|
|
94
128
|
): string | null {
|
|
95
129
|
for (const [version, snapshot] of snapshots) for (const item of snapshot.items) {
|
|
96
|
-
|
|
130
|
+
// A released version is history — its cells (and what they once depended on) never constrain a new placement.
|
|
131
|
+
if (released && versionOrder(version, released) <= 0) continue;
|
|
97
132
|
const predecessors = item.id === input.id ? input.predecessors : item.predecessors ?? [];
|
|
133
|
+
// Only the cells this placement moves, and cells that depend on them, can be made wrong by it — a pre-existing
|
|
134
|
+
// violation elsewhere on the board must not make every placement infeasible (ORCH-LIVE-1008 rehearsal 1).
|
|
135
|
+
if (!overrides.has(item.id) && !predecessors.some((predecessor) => overrides.has(predecessor))) continue;
|
|
136
|
+
const dependentVersion = overrides.get(item.id) ?? version;
|
|
98
137
|
for (const predecessor of predecessors) {
|
|
99
138
|
const predecessorVersion = overrides.get(predecessor) ?? locations.get(predecessor);
|
|
100
139
|
if (!predecessorVersion || versionOrder(dependentVersion, predecessorVersion) <= 0)
|
|
@@ -130,12 +169,8 @@ export function placeCell(input: PlacementCell, deps: PlacementDeps = {}): Place
|
|
|
130
169
|
const open = schedules.filter((row) => (!released || versionOrder(row.version, released) > 0) && row.landBy && available(row, now, backlog));
|
|
131
170
|
const sources = [...new Set([...schedules.map((row) => row.version), ...backlog])];
|
|
132
171
|
const snapshots = new Map(sources.map((version) => [version, (deps.checklist ?? listChecklist)(version)]));
|
|
133
|
-
const locations =
|
|
134
|
-
|
|
135
|
-
if (backlog.includes(version)) continue;
|
|
136
|
-
if (locations.has(item.id)) throw new CliUserError(`여러 판의 같은 칸: ${item.id}`);
|
|
137
|
-
locations.set(item.id, version);
|
|
138
|
-
}
|
|
172
|
+
const { locations, conflict } = cellLocations([...snapshots].map(([version, snapshot]) => [version, snapshot.items] as [string, readonly { id: string }[]]), released, backlog);
|
|
173
|
+
if (conflict) throw new CliUserError(`여러 판의 같은 칸: ${conflict}`);
|
|
139
174
|
// A backlog copy never outranks a scheduled cell with the same id.
|
|
140
175
|
for (const version of backlog) for (const item of snapshots.get(version)?.items ?? []) if (!locations.has(item.id)) locations.set(item.id, version);
|
|
141
176
|
const from = locations.get(input.id) ?? null;
|
|
@@ -171,7 +206,7 @@ export function placeCell(input: PlacementCell, deps: PlacementDeps = {}): Place
|
|
|
171
206
|
const displaced: PlacementDecision['displaced'] = [];
|
|
172
207
|
let chosen: ReleaseSchedule | undefined;
|
|
173
208
|
let loadIssue: string | null = null;
|
|
174
|
-
const feasible = candidates.filter((row) => !dependencyViolation(snapshots, locations, new Map([[input.id, row.version]]), input));
|
|
209
|
+
const feasible = candidates.filter((row) => !dependencyViolation(snapshots, locations, new Map([[input.id, row.version]]), input, released));
|
|
175
210
|
// 판은 «마감 안 가장 이른 판»부터 본다(기존 불변식). 가속 등급은 그 판이 찼을 때 같은 우선순위 비가속 칸을 밀 수 있게만 한다.
|
|
176
211
|
for (const row of feasible) {
|
|
177
212
|
const placement = new Map([[input.id, row.version]]);
|
|
@@ -191,7 +226,7 @@ export function placeCell(input: PlacementCell, deps: PlacementDeps = {}): Place
|
|
|
191
226
|
|| (accelerator && priority === input.priority);
|
|
192
227
|
const p2 = [...items].reverse().find((item) => {
|
|
193
228
|
if (item.accelerator === true) return false;
|
|
194
|
-
if (!victimOk(item.priority) || dependencyViolation(snapshots, locations, new Map([...placement, [item.id, next.version]]), input)) return false;
|
|
229
|
+
if (!victimOk(item.priority) || dependencyViolation(snapshots, locations, new Map([...placement, [item.id, next.version]]), input, released)) return false;
|
|
195
230
|
if (item.deadlineVersion && versionOrder(next.version, item.deadlineVersion) > 0) return false;
|
|
196
231
|
if (items.length < capacity(row) && !(seatItems.length >= seatLimit && ownerMatches(item.owner, seat))) return false;
|
|
197
232
|
if (!item.owner) return false;
|
|
@@ -234,6 +269,7 @@ export function placeCell(input: PlacementCell, deps: PlacementDeps = {}): Place
|
|
|
234
269
|
function moveConstraint(
|
|
235
270
|
item: ChecklistItem, to: ReleaseSchedule, backlog: readonly string[],
|
|
236
271
|
snapshots: Map<string, ChecklistItem[]>, now: number, rate: number, caps: Record<string, number>, rows: ReleaseSchedule[], ceoCap: number,
|
|
272
|
+
released: string | null | undefined,
|
|
237
273
|
): string | null {
|
|
238
274
|
if (!to.landBy || !available(to, now, backlog)) return '착지 마감이 지났거나 백로그 판이라 이동 불가';
|
|
239
275
|
if (item.deadlineVersion && versionOrder(to.version, item.deadlineVersion) > 0) return '마감 판 뒤로 이월 불가';
|
|
@@ -245,16 +281,15 @@ function moveConstraint(
|
|
|
245
281
|
const loadIssue = ceoOverload(item, to, new Map([...snapshots].map(([version, items]) => [version, { items }])),
|
|
246
282
|
rows, ceoCap);
|
|
247
283
|
if (loadIssue) return loadIssue;
|
|
248
|
-
const locations =
|
|
249
|
-
|
|
250
|
-
if (locations.has(cell.id)) return `여러 판의 같은 칸: ${cell.id}`;
|
|
251
|
-
locations.set(cell.id, version);
|
|
252
|
-
}
|
|
284
|
+
const { locations, conflict } = cellLocations(snapshots, released);
|
|
285
|
+
if (conflict) return `여러 판의 같은 칸: ${conflict}`;
|
|
253
286
|
for (const predecessor of item.predecessors ?? []) {
|
|
254
287
|
const location = locations.get(predecessor);
|
|
255
288
|
if (!location || versionOrder(to.version, location) <= 0) return `선행 칸 ${predecessor} 이후 판으로만 이동 가능`;
|
|
256
289
|
}
|
|
257
290
|
for (const [version, items] of snapshots) for (const dependent of items) {
|
|
291
|
+
// A released version is history — its cells never pin a move (same rule as placement).
|
|
292
|
+
if (released && versionOrder(version, released) <= 0) continue;
|
|
258
293
|
if (dependent.id !== item.id && dependent.predecessors?.includes(item.id) && versionOrder(version, to.version) <= 0)
|
|
259
294
|
return `의존 칸 ${dependent.id} 이전 판으로만 이동 가능`;
|
|
260
295
|
}
|
|
@@ -271,20 +306,29 @@ export function rebalance(version: string, deps: PlacementDeps = {}): RebalanceR
|
|
|
271
306
|
const backlog = deps.backlog ?? BACKLOG_VERSIONS;
|
|
272
307
|
const next = rows.find((row) => versionOrder(row.version, version) > 0 && !backlog.includes(row.version));
|
|
273
308
|
const snapshots = new Map(rows.map((row) => [row.version, [...(deps.checklist ?? listChecklist)(row.version).items]]));
|
|
274
|
-
const unstarted = snapshots.get(version)!.filter((item) => item.status === 'yellow' && !item.evidence)
|
|
309
|
+
const unstarted = snapshots.get(version)!.filter((item) => item.status === 'yellow' && !item.evidence)
|
|
310
|
+
.map((item, index) => {
|
|
311
|
+
const rubric = parseRubric(`${item.title}\n${item.evidence ?? ''}`);
|
|
312
|
+
return { item, index, score: rubric ? rubricScore(rubric) : null };
|
|
313
|
+
})
|
|
314
|
+
.sort((a, b) => {
|
|
315
|
+
if (a.score === null) return b.score === null ? a.index - b.index : 1;
|
|
316
|
+
if (b.score === null) return -1;
|
|
317
|
+
return a.score - b.score || a.index - b.index;
|
|
318
|
+
});
|
|
275
319
|
if (unstarted.length && !next) throw new CliUserError(`${version} 다음 판이 없다`);
|
|
276
320
|
if (!unstarted.length) return { decisions: [], blocked: [] };
|
|
277
321
|
const rate = deps.merged24h ?? mergedPrsLast24h(new Date(now));
|
|
278
322
|
const caps = deps.seatCap ?? placementSeatCap();
|
|
279
323
|
const ceoCap = validCeoCap(deps.ceoDailyCap ?? placementCeoDailyCap());
|
|
280
324
|
const result: RebalanceResult = { decisions: [], blocked: [] };
|
|
281
|
-
for (const item of unstarted) {
|
|
282
|
-
const violation = moveConstraint(item, next!, backlog, snapshots, now, rate, caps, rows, ceoCap);
|
|
325
|
+
for (const { item, score } of unstarted) {
|
|
326
|
+
const violation = moveConstraint(item, next!, backlog, snapshots, now, rate, caps, rows, ceoCap, deps.released ?? releasedVersion());
|
|
283
327
|
if (violation) {
|
|
284
328
|
result.blocked.push({ id: item.id, from: version, to: next!.version, reason: violation });
|
|
285
329
|
continue;
|
|
286
330
|
}
|
|
287
|
-
const reason = `${version} 착지 마감 2시간 전 미시작 칸
|
|
331
|
+
const reason = `${version} 착지 마감 2시간 전 미시작 칸 이월${score === null ? '' : ` · 루브릭 ${score}`}`;
|
|
288
332
|
result.decisions.push({ id: item.id, from: version, version: next!.version, reason, displaced: [] });
|
|
289
333
|
snapshots.set(version, snapshots.get(version)!.filter((cell) => cell.id !== item.id));
|
|
290
334
|
snapshots.get(next!.version)!.push(item);
|
|
@@ -305,7 +349,7 @@ export function seatMove(id: string, from: string, to: string, by: string, reaso
|
|
|
305
349
|
if (!ownerMatches(item.owner, parseOwner(by).seat) || next?.version !== to) throw new CliUserError('남의 칸 당기기 거부 — COO 에 요청');
|
|
306
350
|
const now = (deps.now ?? new Date()).getTime();
|
|
307
351
|
const rate = deps.merged24h ?? mergedPrsLast24h(new Date(now));
|
|
308
|
-
const violation = moveConstraint(item, next, backlog, snapshots, now, rate, deps.seatCap ?? placementSeatCap(), rows, validCeoCap(deps.ceoDailyCap ?? placementCeoDailyCap()));
|
|
352
|
+
const violation = moveConstraint(item, next, backlog, snapshots, now, rate, deps.seatCap ?? placementSeatCap(), rows, validCeoCap(deps.ceoDailyCap ?? placementCeoDailyCap()), deps.released ?? releasedVersion());
|
|
309
353
|
if (violation) throw new CliUserError(`${id}: ${violation}`);
|
|
310
354
|
if (!deps.dryRun) move(id, from, to, by, undefined, undefined, reason);
|
|
311
355
|
}
|