elanous 0.2.13 → 0.2.15
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/LZcstaO9_NXypPFmujolv/_buildManifest.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7334.a6679d9168f37aa8.js → 1268.0840ff91e083fcbd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1307-33ffcc49ab45123b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9656-c1540088f5859c80.js → 1844-9ec4b1d12f6e80eb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{390.c373e31b3b9d0c41.js → 1948.15f356b15a625597.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/211-9c9090c59e2c5907.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +1 -0
- package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4554-3565eea1f1495ba7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{4685-c3c1c625b71accd1.js → 4614-21403aac730d4586.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{8039-df92e08fede5de8f.js → 4795-f372490b178583fc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5135-44008fe8b1f2aa41.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5233-bf28bd6bd7a32099.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1452.93700483b9d4fc61.js → 6110.1297d4093fdfbb0d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6158.9c0449a9a8d0064b.js +2 -0
- package/apps/pwa/out/_next/static/chunks/6877-7c6208705c284124.js +1 -0
- package/apps/pwa/out/_next/static/chunks/726.83532d69452ee24b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1829-b453a1b22d3999bb.js → 7327-2d69fb321ba73389.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7395-164e238f471b5d7b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7787-02a1c0b04e25e811.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8827-e2f5b09a8d37e83b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +25 -0
- package/apps/pwa/out/_next/static/chunks/{8421-1c4acfb1395a0edf.js → 8949-ecb4128a0858b911.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/899-46946fe8363e1262.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9103-7a3ae7f60af3b99f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3869-3cd46526879f454c.js → 9247-29d889b809288e65.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/935.4ddaa99fc1d1126c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{79f8be9e-957d68913b590b08.js → a9f2b54d-7865aba0f4d2ee49.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-0e2c57fe6add4b2a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-e05a77df7e005b04.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-f544338193a9a762.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-5491c115b1f9a923.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-b8c49f6b4fa2df91.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-96398e364fbd4548.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-976813c91d7394ea.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-28b9448091776c06.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-1d4901eb7c2d8822.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-72d3e7ff35a8c954.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-60c7a8e1a4fe62ce.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-2a52b316cb143d9c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-c3ce483c8d5db370.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-13c7b53e0c2519a9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-96f074f6538e8a2c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-e97be5334d83a9f5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-65e484302921a932.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-34d08facaf08f556.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-955ec2da3be4c560.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-9f88fbc15d9058d1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-c1343f9499d86f3f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-8739a2e0ae793130.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-c9fc5706b8abaa11.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-d23b12bb92242558.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-8d47f45a0dc044ac.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-c7fea475488201a8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-e2ee2896811e98d0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-83b19dae8619de05.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-5efbf81124c474ae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-85f1a1fc0a82738c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-da6c08aae0f92768.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-06ee266953d9c879.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-153c2b3bcc89e8de.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-87a11e6a05f15199.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-7d4182964407c09c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-0d6d23b40263a7ea.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-7e8c39241f8eb6d0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-008c1a96618a4887.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/{page-63d14a4d8fe1e1c1.js → page-f5597e894c02cd33.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-0337fdf7fb5e37b6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-73735b64bfe1ae67.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-8fcec4c9ea2e7f70.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-f7e631e9b4daf66e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-4881b8f9f63694b4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-9817c7f9007466cc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-97f9f43172a3a90e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5cd7e3a9-404fd6b8d0ecd44c.js → f6231284-45726550aba08fca.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{f48170cc-d05d28bf87a43bf4.js → f6b7c9ba.64a895b8b2fedbdc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-539cde99554be20f.js +1 -0
- package/apps/pwa/out/_next/static/css/1f7c958c41ffa3b9.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 -0
- package/apps/pwa/out/ceo/index.txt +33 -0
- 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/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 +23 -23
- 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 +22 -22
- 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 -0
- package/apps/pwa/out/loops/index.txt +30 -0
- 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/heal/heal-loop.yaml +1 -1
- package/graphs/implement-loop.yaml +1 -1
- package/graphs/orchestrator/orchestrator.yaml +59 -0
- package/graphs/orchestrator/recipes.yaml +18 -0
- package/graphs/release/recipes.yaml +6 -0
- package/graphs/release/release-loop.yaml +6 -1
- package/graphs/rhythm/daily.yaml +38 -0
- package/graphs/rhythm/recipes.yaml +18 -0
- package/graphs/rhythm/weekly.yaml +39 -0
- package/graphs/video/recipes.yaml +3 -0
- package/graphs/video/youtube-summary.yaml +17 -0
- package/package.json +1 -1
- package/scripts/audit-test-state-writes.ts +72 -17
- package/scripts/brand/check.ts +188 -0
- package/scripts/ci-typecheck-changed.ts +50 -18
- package/scripts/coord-post.sh +11 -1
- package/scripts/docs/generate-graph-anatomy.ts +315 -0
- package/scripts/docs-cli-check.ts +23 -6
- package/scripts/expose-rubric-check.ts +118 -0
- package/scripts/expose-rubric-suggest.ts +169 -0
- package/scripts/heal/run-role.ts +14 -0
- package/scripts/hq/standby-verify.ts +11 -7
- package/scripts/lecture-note/node.ts +31 -11
- package/scripts/lessons/handbook-page.ts +112 -0
- package/scripts/marketing/generate-ax-brief.ts +140 -0
- package/scripts/marketing/generate-feature-map.ts +118 -0
- package/scripts/release-loop/checklist-gate-node.ts +53 -14
- package/scripts/release-loop/gate-node.ts +89 -14
- package/scripts/release-loop/publish-node.ts +14 -1
- package/scripts/release-loop/unattended-release.ts +23 -1
- package/scripts/release-loop/vault-note-node.ts +428 -0
- package/scripts/release-story/draft.ts +220 -0
- package/scripts/rhythm/daily.ts +570 -0
- package/scripts/rhythm/weekly.ts +315 -0
- package/scripts/skills/public-check.ts +169 -0
- package/scripts/smart-merge/resync-shadow.ts +147 -0
- package/scripts/test-deterministic.ts +4 -8
- package/scripts/test-diet/effectiveness.ts +195 -0
- package/scripts/test-diet/lib.ts +16 -8
- package/scripts/test-diet/node.ts +77 -9
- package/scripts/ux/demo-rehearsal.ts +2 -2
- package/scripts/ux/lib/rehearsal-verdict.ts +1 -0
- package/scripts/video/youtube-summary.ts +133 -0
- package/skills/diagram-master/SKILL.md +42 -49
- package/skills/diagram-master/references/pyproject.toml +4 -1
- package/skills/diagram-master/references/routing-guide.md +4 -2
- package/skills/grill-me/SKILL.md +1 -0
- package/skills/youtube-master/SKILL.md +13 -13
- package/skills/youtube-master/package-lock.json +0 -545
- package/skills/youtube-master/package.json +2 -5
- package/skills/youtube-master/scripts/main.ts +2 -2
- package/skills/youtube-master/scripts/transcribe-local.ts +1 -1
- package/skills/youtube-master/src/util.ts +30 -7
- package/src/agent-env/profile.ts +161 -0
- package/src/agent-mission/driver.ts +3 -3
- package/src/agent-substrate/pr-reviewer.ts +2 -0
- package/src/ask-user-question/pending-questions.ts +5 -0
- package/src/auth/github-app-token.ts +112 -13
- package/src/autopilot/build/llm-conflict-merge.ts +73 -10
- package/src/autopilot/build/merge-intent.ts +53 -0
- package/src/briefing/brief-items.ts +121 -0
- package/src/chat/index.ts +2 -0
- package/src/claims/claims-ledger.ts +232 -0
- package/src/claims/claims-render.ts +166 -0
- package/src/cli/agent-env-cli.ts +21 -0
- package/src/cli/claims-cli.ts +94 -0
- package/src/cli/decisions-cli.ts +44 -3
- package/src/cli/docs-cli.ts +2 -0
- package/src/cli/doctor-cli.ts +23 -1
- package/src/cli/doctor-node-modules.ts +147 -0
- package/src/cli/flow-cli.ts +11 -0
- package/src/cli/freeze-cli.ts +45 -0
- package/src/cli/intake-cli.ts +46 -17
- package/src/cli/lesson-cli.ts +108 -0
- package/src/cli/logs-cli.ts +104 -2
- package/src/cli/logs-model-input.ts +131 -0
- package/src/cli/pod-cli.ts +2 -0
- package/src/cli/pr-cli.ts +29 -1
- package/src/cli/private-ledger-commands.ts +54 -4
- package/src/cli/pty-drive-verdict.ts +11 -2
- package/src/cli/release-cli.ts +190 -33
- package/src/cli/rfc-status.ts +91 -0
- package/src/cli/seat-requests-cli.ts +52 -0
- package/src/connectors/linear.ts +5 -3
- package/src/context-bus/condense.ts +15 -20
- package/src/context-bus/context-now-public.ts +96 -0
- package/src/context-bus/context-now-surfaces.ts +55 -2
- package/src/context-bus/context-now.ts +6 -3
- package/src/context-bus/coord-events.ts +6 -4
- package/src/context-bus/long-term-memory.ts +92 -11
- package/src/dashboard/index.ts +75 -22
- package/src/dashboard/slash-runtime/chat-persona.ts +66 -0
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +99 -0
- package/src/dashboard/slash-runtime/loops-table.ts +77 -0
- package/src/decisions/decision-cards.ts +33 -7
- package/src/decisions/decision-ledger.ts +40 -1
- package/src/decisions/proact-meter.ts +286 -0
- package/src/decisions/proact1-lite.ts +86 -0
- package/src/decisions/web-push-decision-cards.ts +1 -1
- package/src/discord/sprint21-runtime.ts +16 -0
- package/src/discord-self-message.ts +8 -0
- package/src/discord-slash-wire.ts +31 -2
- package/src/domains/alert-audit.ts +271 -0
- package/src/domains/core-tools.ts +4 -3
- package/src/domains/ops-facts-tool.ts +31 -1
- package/src/domains/outbound-alert.ts +1 -1
- package/src/elanous-config-dir.ts +29 -4
- package/src/execution-loop/launch-gate.ts +29 -14
- package/src/flow/cards.ts +239 -0
- package/src/graph-runner/graph-tick.ts +4 -0
- package/src/graph-runner/runner.ts +30 -5
- package/src/harness/harness-cli-command.ts +259 -38
- package/src/harness/harness-incidents.ts +45 -12
- package/src/harness/harness-pod-dispatch.ts +102 -11
- package/src/harness/harness-queue-child.ts +3 -3
- package/src/harness/harness-queue.ts +583 -65
- package/src/harness/helper-repair.ts +87 -0
- package/src/harness/helper-scan.ts +151 -0
- package/src/harness/launch-stamp.ts +236 -0
- package/src/harness/ledger-write-guard-child.fixture.ts +12 -0
- package/src/harness/ledger-write-guard.ts +35 -0
- package/src/harness/model-input-observation.ts +37 -0
- package/src/harness/pod-memory-policy.ts +15 -0
- package/src/harness/repository-name.ts +55 -0
- package/src/hq/fence-audit.ts +90 -0
- package/src/hq/hq.ts +63 -5
- package/src/hq/lease.ts +4 -4
- package/src/hq/move-plan.ts +126 -0
- package/src/hq/promote.ts +332 -0
- package/src/index.ts +159 -27
- package/src/intake-plane/check.ts +63 -0
- package/src/intake-plane/digest.ts +37 -15
- package/src/intake-plane/front-classifier.ts +6 -0
- package/src/intake-plane/items.ts +3 -2
- package/src/intake-plane/lens.ts +177 -0
- package/src/intake-plane/linear-wish.ts +39 -11
- package/src/intake-plane/route.ts +145 -1
- package/src/intake-plane/wish-benchmark-ledger.ts +129 -0
- package/src/intake-plane/wish-card.ts +53 -2
- package/src/intake-plane/wish-folder.ts +5 -2
- package/src/intake-plane/wish-reply.ts +72 -0
- package/src/launch-head/seat-goal-queue-lock.fixture.ts +15 -0
- package/src/launch-head/seat-goal-queue.ts +198 -0
- package/src/lessons/lesson-import.ts +106 -0
- package/src/lessons/lesson-ledger.ts +206 -0
- package/src/loops/activity.ts +203 -0
- package/src/loops/budget.ts +63 -0
- package/src/loops/checker.ts +5 -3
- package/src/loops/contract/examples/orchestrator.loop.yaml +1 -1
- package/src/loops/loop-cli.ts +36 -4
- package/src/loops/neighbors.ts +40 -0
- package/src/loops/observe.ts +10 -0
- package/src/loops/orchestrator/author-depth.ts +220 -0
- package/src/loops/orchestrator/author-input-check.ts +82 -0
- package/src/loops/orchestrator/author-ledger.ts +232 -0
- package/src/loops/orchestrator/finish-rate.ts +175 -0
- package/src/loops/orchestrator/overlap.ts +51 -12
- package/src/loops/orchestrator/resync-shadow.ts +227 -0
- package/src/loops/orchestrator/seat-share.ts +56 -0
- package/src/loops/orchestrator/tick.ts +309 -0
- package/src/loops/orchestrator/traffic.ts +134 -13
- package/src/loops/registry.ts +535 -9
- package/src/loops/status-rows.ts +118 -0
- package/src/loops/verdict.ts +17 -5
- package/src/maturity/bot-slash-catalog.ts +66 -0
- package/src/maturity/feature-maturity.ts +9 -5
- package/src/maturity/tui-slash-availability.ts +31 -0
- package/src/mss/logging/log-store.ts +35 -4
- package/src/nexus/api/dashboard.ts +6 -1
- package/src/nexus/api/http-server.ts +33 -3
- package/src/nexus/api/loop-edges.ts +173 -0
- package/src/nexus/api/merge-approvals-gate.ts +1 -1
- package/src/nexus/api/schedules-read.ts +18 -1
- package/src/nexus/api/seat-requests.ts +79 -56
- package/src/nexus/api/task-cards-api.ts +44 -5
- package/src/nexus/api/workflows.ts +2 -1
- package/src/prompt-enhance/enhance.ts +6 -1
- package/src/release-loop/auto-start.ts +51 -0
- package/src/release-loop/checklist.ts +22 -4
- package/src/release-loop/feature-store.ts +71 -32
- package/src/release-loop/landing-freeze.ts +121 -0
- package/src/release-loop/merged-pr-checklist.ts +125 -0
- package/src/release-loop/placement.ts +302 -0
- package/src/release-loop/release-schedule.ts +3 -1
- package/src/seat-dispatch/ceo-commands.ts +2 -2
- package/src/seat-dispatch/seat-ask.ts +16 -3
- package/src/seat-dispatch/seat-questions.ts +58 -0
- package/src/seat-dispatch/seat-request-eligibility.ts +19 -0
- package/src/seat-dispatch/seat-request-ledger.ts +133 -0
- package/src/seat-loop/seat-loop.ts +531 -40
- package/src/self-dev/dev-pipeline.ts +14 -6
- package/src/self-dev/draft-sweep.ts +162 -6
- package/src/self-dev/draft-triage-rules.ts +25 -8
- package/src/self-dev/orchestrate.ts +15 -1
- package/src/self-dev/review-provider-fallback.ts +6 -1
- package/src/self-dev/run-store.ts +73 -1
- package/src/self-dev/run-supervisor.ts +57 -2
- package/src/self-dev/self-orchestrate-runtime.ts +5 -1
- package/src/self-implement/budget-gate.ts +29 -0
- package/src/self-implement/expose-gate.ts +47 -0
- package/src/self-implement/frozen-merges.ts +295 -0
- package/src/self-implement/gate-cli.ts +10 -4
- package/src/self-implement/gate-scope.ts +46 -9
- package/src/self-implement/goal-archive.ts +222 -0
- package/src/self-implement/goal-author.ts +43 -5
- package/src/self-implement/goal-clarification-escalation.ts +5 -0
- package/src/self-implement/goal-lookup.ts +147 -0
- package/src/self-implement/heal-intake.ts +197 -0
- package/src/self-implement/host-regate.ts +32 -2
- package/src/self-implement/l8-merge-queue.ts +132 -0
- package/src/self-implement/orchestrator.ts +299 -48
- package/src/self-implement/pipeline-shape.ts +1 -1
- package/src/self-implement/revert-guard.ts +97 -0
- package/src/self-implement/run-ledger.ts +3 -1
- package/src/self-implement/run-status-mapping.ts +2 -0
- package/src/self-implement/seams.ts +29 -9
- package/src/self-implement/self-implement-cli.ts +1 -0
- package/src/self-implement/speed-claims.ts +338 -0
- package/src/session/index.ts +2 -0
- package/src/steward/triage.ts +74 -3
- package/src/steward/working-backwards.ts +4 -1
- package/src/task-cards/card-cli.ts +10 -2
- package/src/task-cards/card-store.ts +10 -6
- package/src/task-orchestrator/surfaces/pod-artifact-return.ts +63 -48
- package/src/task-orchestrator/surfaces/pod-command-job.ts +84 -9
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +48 -0
- package/src/task-orchestrator/surfaces/pod-lease.ts +5 -2
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +46 -6
- package/src/task-orchestrator/surfaces/pod-pool.ts +60 -8
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +224 -49
- package/src/task-orchestrator/surfaces/self-implement.ts +5 -1
- package/src/telegram-commands.ts +27 -3
- package/src/telegram-loops-command.ts +44 -0
- package/src/telegram.ts +25 -4
- package/src/user-config.ts +146 -18
- package/src/version/packed-revision.json +1 -1
- package/src/workflow-runtime/nodes/bash.ts +9 -1
- package/src/workflow-runtime/storage.ts +31 -13
- package/apps/pwa/out/_next/static/D51l8jUBplJrIpWvENsDK/_buildManifest.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1159-0c5ef8bc21cb6afb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1394-931c9827067b2276.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2176-c379e3ebce874151.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2426.8c6ee0c668d53def.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3170-7ec60a56fa0e4728.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3278-ae6ef084769f6195.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3454-988a53dac60a1763.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3638-aa80b4641ea439db.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3735-fe3402553ac1c9c6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3992-fe5c06d6b31f8b25.js +0 -25
- package/apps/pwa/out/_next/static/chunks/4215-c9d91dd644ec1719.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4508-5d0161d1e724d735.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4733-623a79345b171e1c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4883.3bd969ea1d3aaaff.js +0 -2
- package/apps/pwa/out/_next/static/chunks/5250-c78ca295585bbf95.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5532.6899a12958a89b72.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5845-e6c003ce8d5fd36a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5978-b27004c677119115.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6048-4c5c33579bc4ef78.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6303-422dbf0f67c72aac.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6511-ff8360be3d65aaa4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6588-ba380869b67c4fa7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6751-bb3595948b2e311b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7274-19b7279f18b47ab3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7551-a0e0707b79164ddd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7758-85c2b53f731da024.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7777-79f845045aa1ed86.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8025-cc24ee5d8f2fb6f8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8206-c6684dcde12be721.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8605-9da4678f8a310d18.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8725.bf46bb1395dd0fa2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8734-252d511cd276c0dc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8834-ca1cb09c1cb1a6bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9269.d2a4795bc6812717.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9538-2816b90923606459.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9906-e7b8b5310a169dfe.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-d16993889b125c42.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-e5b4e1b256f41076.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-e49b9b76c565a0b1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-b4f032c893b21cc9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-f14987d5850a31e0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-b433308aad13ceb6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-4c1dbb21861beab2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-c85b816e3b269dbb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-f6bc66b919b2ea7d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-60603c1e056c9b25.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-a0ea8f5a2341ee6b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-45c5bf04b4a605e6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-36c6c4183353ac9e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-d16993889b125c42.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-cb2eb0fd809980da.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-31012264da8a341f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-50309aeb0bdd554a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-bc00e964e855f4df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-0696ee3fac9fe56c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66c27608f651b866.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-6ddd2109b7fc3a80.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-4e50b7298c1f69a5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-7a2bf830038a2264.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1f327f18694fabca.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-e5cef2fe6e4028df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cf596128d2b420ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-f672536f14f0969f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-b52365662232db89.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-e7a6c80f288b1948.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-29a709616df9b050.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-fee842d7c2151129.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-78b40a344ed22f24.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-75d199f448171014.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-a011dd5b0bff29c5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-d16993889b125c42.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-d92e32d51ab5157e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-5f21aed6932ef375.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-1e1a53946804debf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-a1833cd43d832601.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-2a511d320f762e06.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-1c3e03f88dac1e21.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-c4d5289c2ea97a89.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b91f8952deaeaf27.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-71b43a2588489987.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-2b256feed331546f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-505bb96d69a8622c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-172630e6aa5e2d7f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-f0fe2d7477845181.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-fc98ac70b44eccb0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-5bd7f10271f72413.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-97d0d85ea05fef9b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-9e4d02d567b98240.js +0 -1
- package/apps/pwa/out/_next/static/css/128fb7fb670b2fbf.css +0 -1
- /package/apps/pwa/out/_next/static/{D51l8jUBplJrIpWvENsDK → LZcstaO9_NXypPFmujolv}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { FEATURE_MATURITY, type Maturity } from '../../src/maturity/feature-maturity.js';
|
|
4
|
+
import type { ChecklistItem } from '../../src/release-loop/checklist.js';
|
|
5
|
+
|
|
6
|
+
const DEFAULT_MAP = resolve(import.meta.dir, '../../docs/marketing/MAP-value-props-to-features.md');
|
|
7
|
+
const HELD = /특허\s*(?:보류|후보)|GOAL-CASCADE/i;
|
|
8
|
+
const PR = /(?:^|[^\w])#(\d+)\b|\/pull\/(\d+)\b/g;
|
|
9
|
+
type Grade = '된다' | '베타' | '로드맵';
|
|
10
|
+
type Row = { feature: string; grade: Grade; id: string; pr: string };
|
|
11
|
+
type Registry = typeof FEATURE_MATURITY;
|
|
12
|
+
|
|
13
|
+
function cells(line: string): string[] {
|
|
14
|
+
return line.slice(1, -1).split(/(?<!\\)\|/).map((cell) => cell.trim().replace(/\\\|/g, '|'));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function clean(text: string): string {
|
|
18
|
+
return text.replace(/\*\*/g, '').replace(/\s+/g, ' ').trim();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function gradeFor(feature: string, hint: string, registry: Registry): Maturity | undefined {
|
|
22
|
+
// A surface alone ("cli", "telegram") does not identify a feature in the registry.
|
|
23
|
+
const key = /\/(?:[\w-]+)(?:\/[\w-]+)*/.exec(hint)?.[0];
|
|
24
|
+
if (key && Object.prototype.hasOwnProperty.call(registry.pwaRoute, key))
|
|
25
|
+
return registry.pwaRoute[key as keyof Registry['pwaRoute']].pwa;
|
|
26
|
+
const named = /\b(cliRoot|tuiSlash|telegramCommand|discordCommand):([\w:-]+)/.exec(hint);
|
|
27
|
+
if (named) {
|
|
28
|
+
const group = registry[named[1] as Exclude<keyof Registry, 'pwaRoute'>] as Record<string, Maturity>;
|
|
29
|
+
return Object.prototype.hasOwnProperty.call(group, named[2]!) ? group[named[2]!] : undefined;
|
|
30
|
+
}
|
|
31
|
+
// Explicit command spelling in a feature name is unambiguous; a prose word is not.
|
|
32
|
+
const command = /`(?:elanous )?([\w:-]+)`/.exec(feature)?.[1];
|
|
33
|
+
if (command && Object.prototype.hasOwnProperty.call(registry.cliRoot, command))
|
|
34
|
+
return registry.cliRoot[command as keyof Registry['cliRoot']];
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function prs(text: string): string[] {
|
|
39
|
+
return [...new Set([...text.matchAll(PR)].map((m) => `#${m[1] ?? m[2]}`))];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function escapeCell(text: string): string {
|
|
43
|
+
return text.replace(/\|/g, '\\|').replace(/[\r\n]+/g, ' ');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Checklist cells require green status; rows without checklist cells use the map's maturity and public marker. */
|
|
47
|
+
export function generateFeatureMap(input: { map: string; items: readonly ChecklistItem[]; registry: Registry; version: string }): string {
|
|
48
|
+
const checklist = new Map(input.items.map((item) => [item.id, item]));
|
|
49
|
+
const green = new Map(input.items.filter((item) => item.status === 'green').map((item) => [item.id, item]));
|
|
50
|
+
const rows: Row[] = [];
|
|
51
|
+
let section = '';
|
|
52
|
+
for (const line of input.map.split(/\r?\n/)) {
|
|
53
|
+
if (/^#{2,3} /.test(line)) section = line;
|
|
54
|
+
if (!section.startsWith('## 2. 기능 배치표 ') || !line.startsWith('|')) continue;
|
|
55
|
+
const columns = cells(line);
|
|
56
|
+
if (columns.length !== 8 || !/^P\d+(?:[·⊕]P\d+)*$/.test(clean(columns[0]!))) continue;
|
|
57
|
+
const [_, rawFeature, rawId, , hint, publication, evidence] = columns;
|
|
58
|
+
const feature = clean(rawFeature!);
|
|
59
|
+
if (!feature || feature === '—') continue;
|
|
60
|
+
const id = clean(rawId!);
|
|
61
|
+
const ids = id.split(/[·⊕]/).map((part) => part.trim()).filter(Boolean);
|
|
62
|
+
const hasChecklistCell = ids.some((part) => checklist.has(part));
|
|
63
|
+
const matched = ids.map((part) => green.get(part)).filter((item): item is ChecklistItem => !!item);
|
|
64
|
+
const complete = ids.length > 0 && ids.every((part) => green.has(part));
|
|
65
|
+
const maturity = hasChecklistCell ? gradeFor(feature, hint!, input.registry) : undefined;
|
|
66
|
+
const mapMaturity = /^(?:\/\S+\s+)?(stable|beta)\b/i.exec(clean(hint!))?.[1]?.toLowerCase();
|
|
67
|
+
const grade: Grade = hasChecklistCell
|
|
68
|
+
? complete && maturity === 'stable' ? '된다' : complete && maturity === 'beta' ? '베타' : '로드맵'
|
|
69
|
+
: !clean(publication!).startsWith('✅') ? '로드맵'
|
|
70
|
+
: mapMaturity === 'stable' ? '된다' : mapMaturity === 'beta' ? '베타' : '로드맵';
|
|
71
|
+
const evidencePrs = hasChecklistCell
|
|
72
|
+
? [...new Set(matched.flatMap((item) => prs(item.evidence ?? '')))] : prs(evidence!);
|
|
73
|
+
const held = HELD.test([...columns, ...ids.map((part) => {
|
|
74
|
+
const item = checklist.get(part);
|
|
75
|
+
return item ? `${item.title} ${item.evidence ?? ''}` : '';
|
|
76
|
+
})].join(' '));
|
|
77
|
+
rows.push({ feature: held ? '개념' : feature, grade: held ? '로드맵' : grade,
|
|
78
|
+
id: held ? '—' : hasChecklistCell ? id : '지도 판정', pr: held || !evidencePrs.length ? '근거 없음'
|
|
79
|
+
: !hasChecklistCell || complete ? evidencePrs.join(', ') : `부분: ${evidencePrs.join(', ')} · 나머지 칸 미완` });
|
|
80
|
+
}
|
|
81
|
+
const lines = [
|
|
82
|
+
'# FEATURE-MAP — 엘라누스가 할 수 있는 것',
|
|
83
|
+
'',
|
|
84
|
+
`판: ${escapeCell(input.version)} · 원천: src/maturity/feature-maturity.ts + 릴리스 체크리스트 green + docs/marketing/MAP-value-props-to-features.md §2`,
|
|
85
|
+
`재생성: \`bun scripts/marketing/generate-feature-map.ts --version ${escapeCell(input.version)} --out docs/marketing/FEATURE-MAP.md\``,
|
|
86
|
+
'내부 배치용: 로드맵은 현재 기능이 아니며, 된다/베타도 공개 판정·컷의 조상 PR 확인을 대신하지 않는다.',
|
|
87
|
+
'',
|
|
88
|
+
'| 기능 | 성숙도 | 칸 id | 근거 PR |',
|
|
89
|
+
'|---|---|---|---|',
|
|
90
|
+
...rows.map((row) => `| ${escapeCell(row.feature)} | ${row.grade} | ${escapeCell(row.id)} | ${escapeCell(row.pr)} |`),
|
|
91
|
+
'',
|
|
92
|
+
];
|
|
93
|
+
return lines.join('\n');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (import.meta.main) {
|
|
97
|
+
const args = process.argv.slice(2);
|
|
98
|
+
const option = (name: string) => {
|
|
99
|
+
const index = args.indexOf(name);
|
|
100
|
+
if (index < 0) return undefined;
|
|
101
|
+
const value = args[index + 1];
|
|
102
|
+
if (!value || value.startsWith('--')) throw new Error(`${name} 값이 필요하다`);
|
|
103
|
+
args.splice(index, 2);
|
|
104
|
+
return value;
|
|
105
|
+
};
|
|
106
|
+
const version = option('--version');
|
|
107
|
+
const mapFile = option('--map') ?? DEFAULT_MAP;
|
|
108
|
+
const checklistFile = option('--checklist');
|
|
109
|
+
const out = option('--out');
|
|
110
|
+
if (!version || args.length) throw new Error('Usage: bun scripts/marketing/generate-feature-map.ts --version <v> [--map <file>] [--checklist <snapshot.json>] [--out <file>]');
|
|
111
|
+
const items = checklistFile
|
|
112
|
+
? (JSON.parse(readFileSync(checklistFile, 'utf8')) as { items: ChecklistItem[] }).items
|
|
113
|
+
: (await import('../../src/release-loop/checklist.js')).listChecklist(version).items;
|
|
114
|
+
if (!Array.isArray(items)) throw new Error('체크리스트 items 배열이 없다');
|
|
115
|
+
const body = generateFeatureMap({ map: readFileSync(mapFile, 'utf8'), items, registry: FEATURE_MATURITY, version });
|
|
116
|
+
if (out) writeFileSync(out, body);
|
|
117
|
+
else process.stdout.write(body);
|
|
118
|
+
}
|
|
@@ -1,27 +1,66 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import {
|
|
2
|
+
import { checklistGate, listChecklist, summarizeChecklist } from '../../src/release-loop/checklist.js';
|
|
3
|
+
import * as store from '../../src/release-loop/feature-store.js';
|
|
4
|
+
import { getSchedule } from '../../src/release-loop/release-schedule.js';
|
|
5
|
+
import { debug } from '../../src/debug/log.js';
|
|
3
6
|
import { emitNodeResult, errorResult, readGraphContext, type GraphContext } from './node-verdict.js';
|
|
4
7
|
|
|
5
|
-
export function runChecklistGate(context: GraphContext = readGraphContext()) {
|
|
8
|
+
export function runChecklistGate(context: GraphContext = readGraphContext(), now: Date = new Date()) {
|
|
6
9
|
const version = context.input.version;
|
|
7
10
|
const data = listChecklist(version);
|
|
11
|
+
const schedule = getSchedule(version);
|
|
12
|
+
const deadline = schedule?.landBy;
|
|
13
|
+
const overdue = deadline !== null && deadline !== undefined && now.getTime() > Date.parse(deadline);
|
|
14
|
+
const autoMoved = overdue ? data.items.filter((item) => item.status === 'yellow' && item.priority !== 'P0' && item.disposition === undefined).map((item) => item.id) : [];
|
|
8
15
|
const gate = checklistGate(version);
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
16
|
+
const p0 = data.items.filter((item) => item.status === 'yellow' && item.priority === 'P0').map((item) => item.id);
|
|
17
|
+
gate.undecided = gate.undecided.filter((id) => !autoMoved.includes(id) && !p0.includes(id));
|
|
18
|
+
gate.moved = gate.moved.filter((id) => !p0.includes(id)).concat(autoMoved);
|
|
19
|
+
gate.knownIssues = gate.knownIssues.filter((item) => !p0.includes(item.id));
|
|
20
|
+
gate.blocked.push(...p0.filter((id) => !gate.blocked.includes(id)));
|
|
21
|
+
gate.ok = gate.red.length === 0 && gate.undecided.length === 0 && gate.blocked.length === 0;
|
|
22
|
+
const [major, minor, patch] = version.split('.').map(Number);
|
|
23
|
+
const next = `${major}.${minor}.${patch! + 1}`;
|
|
24
|
+
const carried: string[] = [];
|
|
25
|
+
let carryFailure: string | undefined;
|
|
26
|
+
if (gate.moved.length) {
|
|
27
|
+
const nextChecklist = listChecklist(next);
|
|
28
|
+
const existing = new Map(nextChecklist.items.map((item) => [item.id, item]));
|
|
29
|
+
const movedFromHere = new Set(nextChecklist.history.filter((entry) => entry.field === 'move' && entry.from === version).map((entry) => entry.id));
|
|
30
|
+
const sameCarry = new Set<string>();
|
|
15
31
|
for (const id of gate.moved) {
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
32
|
+
const original = data.items.find((item) => item.id === id)!;
|
|
33
|
+
const destination = existing.get(id);
|
|
34
|
+
if (!destination) continue;
|
|
35
|
+
if (destination.title === original.title && destination.owner === original.owner && (movedFromHere.has(id) || destination.evidence?.split('\n').some((line) => line.startsWith(`${version}에서 이월`)))) {
|
|
36
|
+
sameCarry.add(id);
|
|
37
|
+
} else {
|
|
38
|
+
carryFailure = `이월 충돌: ${id} — 다음 판에 다른 칸이 같은 ID`;
|
|
39
|
+
debug.log('release-loop.checklist-gate', 'carry-collision', { id, from: version, to: next });
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (!carryFailure) for (const id of gate.moved) {
|
|
44
|
+
try {
|
|
45
|
+
if (sameCarry.has(id)) {
|
|
46
|
+
store.remove(version, id, 'release-loop.checklist-gate');
|
|
47
|
+
debug.log('release-loop.checklist-gate', 'carry-closed', { id, from: version, to: next });
|
|
48
|
+
} else {
|
|
49
|
+
const reason = autoMoved.includes(id) ? `컷 자동 이월 · 마감 ${new Intl.DateTimeFormat('en-GB', { timeZone: 'Asia/Seoul', hour: '2-digit', minute: '2-digit', hourCycle: 'h23' }).format(new Date(deadline!))}` : '컷 자동 이월 · 처분 move';
|
|
50
|
+
store.move(id, version, next, 'release-loop.checklist-gate', undefined, undefined, reason, { clearDisposition: true });
|
|
51
|
+
carried.push(id);
|
|
52
|
+
debug.log('release-loop.checklist-gate', 'carried', { id, from: version, to: next });
|
|
53
|
+
}
|
|
54
|
+
} catch (error) {
|
|
55
|
+
carryFailure = `이월 실패: ${id} — ${error instanceof Error ? error.message : String(error)}`;
|
|
56
|
+
break;
|
|
21
57
|
}
|
|
22
58
|
}
|
|
23
59
|
}
|
|
24
|
-
|
|
60
|
+
if (carryFailure) gate.ok = false;
|
|
61
|
+
const counts = summarizeChecklist(data);
|
|
62
|
+
const summary = `${carryFailure ? `${carryFailure} · ` : ''}확인표 🟢${counts.green} 🟡${counts.yellow}(이동 ${gate.moved.length} · 알려진 문제 ${gate.knownIssues.length} · 막음 ${gate.blocked.length} · 판정 없음 ${gate.undecided.length}) 🔴${counts.red}${gate.red.length ? ` (${gate.red.join(', ')})` : ''}${gate.parity?.length ? ` · ⚠ 짝 경고 ${gate.parity.length}(${gate.parity.map((p) => p.id).join(', ')})` : ''} · 이월 ${carried.length}(${carried.join(', ') || '-'})`;
|
|
63
|
+
return { outcome: gate.ok ? 'ok' as const : 'fail' as const, verdict: gate.ok ? 'pass' as const : 'fail' as const, summary, ...gate, carried };
|
|
25
64
|
}
|
|
26
65
|
|
|
27
66
|
if (import.meta.main) {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
3
3
|
import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { randomUUID } from 'node:crypto';
|
|
6
6
|
import { dirname, join, resolve } from 'node:path';
|
|
7
7
|
import { debug } from '../../src/debug/log.js';
|
|
8
|
+
import { landingFreezeMessage, readLandingFreeze } from '../../src/release-loop/landing-freeze.js';
|
|
8
9
|
import { effectiveInstanceRoot, releaseLedgerRoot } from '../../src/instance/resolve.js';
|
|
9
10
|
import { getElanousConfigDirOverride } from '../../src/elanous-config-dir.js';
|
|
10
11
|
import { diffFailures, junitFailures, parseFailures } from './gate-diff';
|
|
@@ -18,10 +19,10 @@ import { POD_BUN_CACHE_HOST_PATH, parseInstallSeconds, podBunCacheVolume } from
|
|
|
18
19
|
import { PodPoolScheduler, parsePodPool, checkPodPool } from '../../src/task-orchestrator/surfaces/pod-pool.js';
|
|
19
20
|
import { defaultKubectl } from '../../src/task-orchestrator/surfaces/self-implement-pod.js';
|
|
20
21
|
|
|
21
|
-
interface CommandResult { rc: number; output: string; passedIds?: string[]; stalledEnv?: Array<{ file: string; reason: 'no-output' }
|
|
22
|
+
interface CommandResult { rc: number; output: string; passedIds?: string[]; stalledEnv?: Array<{ file: string; reason: 'no-output' }>; timedOut?: boolean }
|
|
22
23
|
export interface GateRunner {
|
|
23
|
-
command(cmd: string, args: string[], cwd: string): Promise<CommandResult>;
|
|
24
|
-
localCommand(cmd: string, args: string[], cwd: string): Promise<CommandResult>;
|
|
24
|
+
command(cmd: string, args: string[], cwd: string, limitMs?: number): Promise<CommandResult>;
|
|
25
|
+
localCommand(cmd: string, args: string[], cwd: string, limitMs?: number): Promise<CommandResult>;
|
|
25
26
|
sweep(tree: string, logDir?: string, pod?: GateOptions['pod']): Promise<CommandResult>;
|
|
26
27
|
add(tree: string, commit: string): Promise<void>;
|
|
27
28
|
remove(tree: string): Promise<void>;
|
|
@@ -32,6 +33,7 @@ export interface GateRunner {
|
|
|
32
33
|
}
|
|
33
34
|
export interface GateOptions {
|
|
34
35
|
commit: string;
|
|
36
|
+
forceFreeze?: boolean;
|
|
35
37
|
version: string;
|
|
36
38
|
baselineVersion?: string;
|
|
37
39
|
baselineCommit?: string;
|
|
@@ -50,7 +52,7 @@ export interface GateResult {
|
|
|
50
52
|
fixed: number;
|
|
51
53
|
knownEnv: number;
|
|
52
54
|
knownEnvCleared: string[];
|
|
53
|
-
stalledEnv: Array<{ file: string; reason: 'no-output'; local: 'passed' | 'failed' }>;
|
|
55
|
+
stalledEnv: Array<{ file: string; reason: 'no-output' | 'isolated-timeout'; local: 'passed' | 'failed' | 'timeout' }>;
|
|
54
56
|
baselineSource?: 'ledger' | 'instance' | 'cut-logs' | 'swept';
|
|
55
57
|
durationMs: number;
|
|
56
58
|
error?: string;
|
|
@@ -187,17 +189,63 @@ class StalledPodShards extends Error {
|
|
|
187
189
|
}
|
|
188
190
|
}
|
|
189
191
|
|
|
192
|
+
/** Isolated re-runs (host stall re-run · cut and baseline re-checks) are time-limited; sweeps and installs keep their full time. */
|
|
193
|
+
export const GATE_ISOLATED_TIMEOUT_MS = 300_000;
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Runs a limited local command in its own process group. On timeout: SIGTERM the group (test-deterministic forwards it to its own
|
|
197
|
+
* `bun test` group — Chrome included), SIGKILL after a grace, and return after a hard deadline even if a foreign-group process still holds the pipe.
|
|
198
|
+
*/
|
|
199
|
+
export function limitedLocalCommand(cmd: string, args: string[], cwd: string, limitMs: number, graceMs = 10_000): Promise<CommandResult> {
|
|
200
|
+
return new Promise((resolveRun) => {
|
|
201
|
+
const chunks: string[] = [];
|
|
202
|
+
let timedOut = false;
|
|
203
|
+
let done = false;
|
|
204
|
+
const timers: ReturnType<typeof setTimeout>[] = [];
|
|
205
|
+
const child = spawn(cmd, args, { cwd, detached: true, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
206
|
+
let kept = 0;
|
|
207
|
+
// Same ceiling as the unlimited spawnSync path (maxBuffer 128MB): drop the oldest output beyond it, keep the tail.
|
|
208
|
+
const keep = (d: string) => { chunks.push(d); kept += d.length; while (kept > 128 * 1024 * 1024 && chunks.length > 1) kept -= chunks.shift()!.length; };
|
|
209
|
+
child.stdout?.setEncoding('utf8').on('data', keep);
|
|
210
|
+
child.stderr?.setEncoding('utf8').on('data', keep);
|
|
211
|
+
const signalGroup = (signal: NodeJS.Signals) => { try { if (child.pid) process.kill(-child.pid, signal); } catch { /* group already gone */ } };
|
|
212
|
+
const finish = (code: number | null, error?: Error) => {
|
|
213
|
+
if (done) return;
|
|
214
|
+
done = true;
|
|
215
|
+
for (const timer of timers) clearTimeout(timer);
|
|
216
|
+
child.stdout?.destroy();
|
|
217
|
+
child.stderr?.destroy();
|
|
218
|
+
const tail = error ? `\n${error}` : timedOut ? `\nkilled after ${limitMs}ms (process group)` : '';
|
|
219
|
+
resolveRun({ rc: code ?? 2, output: `${chunks.join('')}${tail}`, ...(timedOut ? { timedOut } : {}) });
|
|
220
|
+
};
|
|
221
|
+
timers.push(setTimeout(() => {
|
|
222
|
+
timedOut = true;
|
|
223
|
+
signalGroup('SIGTERM');
|
|
224
|
+
timers.push(setTimeout(() => signalGroup('SIGKILL'), graceMs));
|
|
225
|
+
timers.push(setTimeout(() => finish(null), graceMs + 5_000));
|
|
226
|
+
}, limitMs));
|
|
227
|
+
child.on('error', (error) => finish(null, error));
|
|
228
|
+
child.on('close', (code) => finish(code));
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
190
232
|
export function createGateRunner(repo: string, remote?: string, commandOverride?: GateRunner['command'], podCommand: (options: RunPodCommandOptions) => Promise<PodCommandResult> = runPodCommand, poolOverride?: PodPoolScheduler, podLogTail?: (job: PodCommandResult) => string): GateRunner {
|
|
191
233
|
let remoteMirror: string | undefined;
|
|
192
|
-
const localCommand: GateRunner['
|
|
234
|
+
const localCommand: GateRunner['localCommand'] = async (cmd, args, cwd, limitMs) => {
|
|
235
|
+
if (limitMs) return limitedLocalCommand(cmd, args, cwd, limitMs);
|
|
193
236
|
const run = spawnSync(cmd, args, { cwd, encoding: 'utf8', maxBuffer: 128 * 1024 * 1024 });
|
|
194
237
|
return { rc: run.status ?? 2, output: `${run.stdout ?? ''}\n${run.stderr ?? ''}${run.error ? `\n${run.error}` : ''}` };
|
|
195
238
|
};
|
|
196
239
|
const command: GateRunner['command'] = commandOverride ?? (remote
|
|
197
|
-
? async (cmd, args, cwd) => {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
240
|
+
? async (cmd, args, cwd, limitMs) => {
|
|
241
|
+
// A limited remote run is killed on the remote host; the local ssh deadline is a backstop.
|
|
242
|
+
// GNU timeout signals its own process group: -s KILL would kill timeout too (ssh 255), so TERM first then KILL after 10s (124 / 137).
|
|
243
|
+
// No coreutils timeout on the remote → run unlimited there and rely on the local ssh deadline.
|
|
244
|
+
const limitPrefix = limitMs ? `$(command -v timeout >/dev/null 2>&1 && echo 'timeout -k 10 ${Math.ceil(limitMs / 1000)}') ` : '';
|
|
245
|
+
const run = spawnSync('ssh', [remote, `PATH=$HOME/.bun/bin:/opt/homebrew/bin:$PATH; export PATH; cd ${quote(cwd)} && ${limitPrefix}${[cmd, ...args].map(quote).join(' ')}`],
|
|
246
|
+
{ encoding: 'utf8', maxBuffer: 128 * 1024 * 1024, ...(limitMs ? { timeout: limitMs + 60_000, killSignal: 'SIGKILL' as const } : {}) });
|
|
247
|
+
const timedOut = !!limitMs && (run.status === 124 || run.status === 137 || (run.error as NodeJS.ErrnoException | undefined)?.code === 'ETIMEDOUT');
|
|
248
|
+
return { rc: run.status ?? 2, output: `${run.stdout ?? ''}\n${run.stderr ?? ''}${run.error ? `\n${run.error}` : ''}`, ...(timedOut ? { timedOut } : {}) };
|
|
201
249
|
}
|
|
202
250
|
: localCommand);
|
|
203
251
|
const podSweep = async (tree: string, logDir: string | undefined, pod: NonNullable<GateOptions['pod']>): Promise<CommandResult> => {
|
|
@@ -537,6 +585,12 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
537
585
|
const start = Date.now();
|
|
538
586
|
const explicitConfig = getElanousConfigDirOverride();
|
|
539
587
|
const root = explicitConfig ? effectiveInstanceRoot() : (opts.instanceRoot ?? effectiveInstanceRoot());
|
|
588
|
+
// The freeze is read from the same root the gate runs in (a config-dir override must not move it elsewhere).
|
|
589
|
+
const frozen = readLandingFreeze(root);
|
|
590
|
+
if (frozen) {
|
|
591
|
+
debug.log('release.run', opts.forceFreeze ? 'freeze-forced' : 'frozen', { version: opts.version, reason: frozen.reason, until: frozen.until, node: 'gate' });
|
|
592
|
+
if (!opts.forceFreeze) throw new Error(landingFreezeMessage(frozen));
|
|
593
|
+
}
|
|
540
594
|
const ledger = explicitConfig ? root : (opts.ledgerRoot ?? releaseLedgerRoot());
|
|
541
595
|
const roots = [...new Set([ledger, root])];
|
|
542
596
|
const cachedBaseline = (version: string, commit: string) => {
|
|
@@ -622,7 +676,15 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
622
676
|
}
|
|
623
677
|
for (const { file, reason } of stalled) {
|
|
624
678
|
if (!/^(?:[\w.-]+\/)+[\w.-]+\.test\.tsx?$/.test(file) || file.split('/').includes('..')) throw new Error(`unsafe test path: ${file}`);
|
|
625
|
-
|
|
679
|
+
// 0.2.15: InsidePage.test.tsx gave no Pod output and then hung on the host with headless Chrome — the run could only fail.
|
|
680
|
+
const isolated = await runner.localCommand('bun', ['run', 'test:deterministic', asPath(file)], hostTree, GATE_ISOLATED_TIMEOUT_MS);
|
|
681
|
+
// A file that stalls in its Pod shard and again on the host is an environment stall: record it, measure nothing from it.
|
|
682
|
+
if (isolated.timedOut) {
|
|
683
|
+
// Grandchildren (e.g. headless Chrome) can outlive the kill — the event lets the next release see a repeat.
|
|
684
|
+
debug.log('release-loop.gate', 'isolated-timeout', { file, label, limitMs: GATE_ISOLATED_TIMEOUT_MS });
|
|
685
|
+
local.push({ file, reason, local: 'timeout' });
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
626
688
|
const parsed = failuresOf(isolated, `${label} host isolated ${file}`);
|
|
627
689
|
if ([...parsed.failures, ...parsed.errors].some((id) => fileOf(id) !== file)) throw new Error(`host isolated run attributed to another file: ${file}`);
|
|
628
690
|
measured.failures.push(...parsed.failures);
|
|
@@ -680,14 +742,26 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
680
742
|
result.fixed = diff.fixed.length;
|
|
681
743
|
result.preexisting = diff.common.length;
|
|
682
744
|
for (const file of new Set(diff.newFailures.map(fileOf))) {
|
|
683
|
-
|
|
745
|
+
// 0.2.15: the cut re-check of a new-failure file (InsidePage.test.tsx · headless Chrome) hung for good — limit it like the host re-run.
|
|
746
|
+
const cutCheck = cutHostFailed.has(file) ? undefined : await runner.command('bun', ['run', 'test:deterministic', asPath(file)], cutTree, GATE_ISOLATED_TIMEOUT_MS);
|
|
747
|
+
if (cutCheck?.timedOut) {
|
|
748
|
+
debug.log('release-loop.gate', 'isolated-timeout', { file, label: 'cut isolated', limitMs: GATE_ISOLATED_TIMEOUT_MS });
|
|
749
|
+
result.stalledEnv.push({ file, reason: 'isolated-timeout', local: 'timeout' });
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
752
|
+
const isolatedCut = cutCheck === undefined
|
|
684
753
|
? { failures: cut.failures.filter((id) => fileOf(id) === file), errors: cut.errors.filter((id) => fileOf(id) === file) }
|
|
685
|
-
: failuresOf(
|
|
754
|
+
: failuresOf(cutCheck, `cut isolated ${file}`);
|
|
686
755
|
const reproduced = new Set([...isolatedCut.failures, ...isolatedCut.errors]);
|
|
687
756
|
const candidates = diff.newFailures.filter((id) => fileOf(id) === file && reproduced.has(id));
|
|
688
757
|
if (candidates.length === 0) continue;
|
|
689
758
|
const baselineTree = await getBaseTree();
|
|
690
|
-
const previous = await runner.command('bun', ['run', 'test:deterministic', asPath(file)], baselineTree);
|
|
759
|
+
const previous = await runner.command('bun', ['run', 'test:deterministic', asPath(file)], baselineTree, GATE_ISOLATED_TIMEOUT_MS);
|
|
760
|
+
if (previous.timedOut) {
|
|
761
|
+
debug.log('release-loop.gate', 'isolated-timeout', { file, label: 'baseline isolated', limitMs: GATE_ISOLATED_TIMEOUT_MS });
|
|
762
|
+
result.stalledEnv.push({ file, reason: 'isolated-timeout', local: 'timeout' });
|
|
763
|
+
continue;
|
|
764
|
+
}
|
|
691
765
|
let oldFailures: Set<string>;
|
|
692
766
|
if (/No tests found|had no matches/i.test(previous.output) && previous.rc === 1) {
|
|
693
767
|
const lookup = await runner.command('git', ['ls-tree', '--name-only', baseSha, '--', file], opts.remote ? baselineTree : repo);
|
|
@@ -766,6 +840,7 @@ export function parseOptions(args: string[], env: NodeJS.ProcessEnv): GateOption
|
|
|
766
840
|
commit: typeof fromGraph.commit === 'string' ? fromGraph.commit : '',
|
|
767
841
|
version: typeof fromGraph.version === 'string' ? fromGraph.version : '',
|
|
768
842
|
baselineVersion: typeof fromGraph.previousVersion === 'string' ? fromGraph.previousVersion : undefined,
|
|
843
|
+
forceFreeze: fromGraph.forceFreeze === true,
|
|
769
844
|
baselineCommit: typeof fromGraph.previousCommit === 'string' ? fromGraph.previousCommit : undefined,
|
|
770
845
|
remote: typeof fromGraph.gateRemote === 'string' ? fromGraph.gateRemote : undefined,
|
|
771
846
|
remoteMirror: typeof fromGraph.gateRemoteMirror === 'string' ? fromGraph.gateRemoteMirror : undefined,
|
|
@@ -3,6 +3,8 @@ import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
|
3
3
|
import { tmpdir } from 'node:os';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { WARNING_ONLY } from './auto-approve-node.js';
|
|
6
|
+
import { beginLandingMerge, landingFreezeMessage } from '../../src/release-loop/landing-freeze.js';
|
|
7
|
+
import { debug } from '../../src/debug/log.js';
|
|
6
8
|
import { errorResult, finishNode, lastResult, nodeOutput, readGraphContext, runCommand, type CommandRunner } from './node-verdict.js';
|
|
7
9
|
|
|
8
10
|
export function publicNotes(markdown: string, pages: { pages: Array<{ id: string; slug?: string; source?: string }> }): string {
|
|
@@ -64,7 +66,18 @@ export function runPublish(run: CommandRunner = runCommand) {
|
|
|
64
66
|
try {
|
|
65
67
|
const file = join(dir, 'notes.md');
|
|
66
68
|
writeFileSync(file, body);
|
|
67
|
-
|
|
69
|
+
// Same in-flight marker as merges: `freeze on` waits for a publication that already passed this check.
|
|
70
|
+
// Unlike a merge, a frozen publication is not queued: it fails, and the release run is run again after `freeze off`.
|
|
71
|
+
const landing = beginLandingMerge();
|
|
72
|
+
const frozen = landing.frozen;
|
|
73
|
+
if (frozen) {
|
|
74
|
+
landing.end(); // already released by beginLandingMerge when frozen; kept explicit so no path leaves a marker
|
|
75
|
+
debug.log('release.run', context.input.forceFreeze === true ? 'freeze-forced' : 'frozen', { version, reason: frozen.reason, until: frozen.until, node: 'publish' });
|
|
76
|
+
if (context.input.forceFreeze !== true) return { outcome: 'fail' as const, verdict: 'fail' as const, summary: `publish blocked: ${landingFreezeMessage(frozen)}` };
|
|
77
|
+
}
|
|
78
|
+
let published: ReturnType<typeof run>;
|
|
79
|
+
try { published = run('bun', ['bin/elanous.mjs', 'release', 'publish', '--dir', out, '--notes-file', file, '--yes', '--json']); }
|
|
80
|
+
finally { landing.end(); }
|
|
68
81
|
if (published.stderr) process.stderr.write(published.stderr);
|
|
69
82
|
const result = lastResult(published);
|
|
70
83
|
if (published.status === 1) return { outcome: 'fail' as const, verdict: 'fail' as const, summary: `release publish failed: ${result?.error ?? published.stderr.trim()}` };
|
|
@@ -4,6 +4,8 @@ import { getElanousConfigDirOverride } from '../../src/elanous-config-dir.js';
|
|
|
4
4
|
import { effectiveInstanceRoot, releaseLedgerRoot } from '../../src/instance/resolve.js';
|
|
5
5
|
import { userConfigPath } from '../../src/user-config.js';
|
|
6
6
|
import { checklistGate } from '../../src/release-loop/checklist.js';
|
|
7
|
+
import { isLandingFreezeRefusal, readLandingFreeze, LandingFrozenError } from '../../src/release-loop/landing-freeze.js';
|
|
8
|
+
import { debug } from '../../src/debug/log.js';
|
|
7
9
|
import { runGraph, type GraphRunState } from '../../src/graph-runner/runner.js';
|
|
8
10
|
|
|
9
11
|
const VERSION = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/;
|
|
@@ -26,6 +28,7 @@ export interface ReleaseRunInput extends ReleaseLoopConfig {
|
|
|
26
28
|
previousVersion: string;
|
|
27
29
|
cutCommit?: string;
|
|
28
30
|
gatePodPool: string;
|
|
31
|
+
forceFreeze?: boolean;
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
export interface UnattendedReleaseDeps {
|
|
@@ -34,6 +37,7 @@ export interface UnattendedReleaseDeps {
|
|
|
34
37
|
configPath?: string;
|
|
35
38
|
graph?: (path: string, options: { input: ReleaseRunInput }) => Promise<GraphRunState>;
|
|
36
39
|
checklist?: typeof checklistGate;
|
|
40
|
+
freezeRoot?: string;
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
function compareVersions(a: string, b: string): number {
|
|
@@ -83,13 +87,31 @@ export function buildReleaseRunInput(version: string, deps: UnattendedReleaseDep
|
|
|
83
87
|
|
|
84
88
|
/** Fail closed at the entry boundary, before any graph node can change a release. */
|
|
85
89
|
export async function runUnattendedRelease(
|
|
86
|
-
opts: { version: string; dryRun?: boolean; cutCommit?: string },
|
|
90
|
+
opts: { version: string; dryRun?: boolean; cutCommit?: string; forceFreeze?: boolean },
|
|
87
91
|
deps: UnattendedReleaseDeps = {},
|
|
88
92
|
): Promise<{ input: ReleaseRunInput; dryRun: boolean; state?: GraphRunState }> {
|
|
93
|
+
if (!opts.dryRun) {
|
|
94
|
+
const frozen = readLandingFreeze(deps.freezeRoot);
|
|
95
|
+
if (frozen) {
|
|
96
|
+
debug.log('release.run', opts.forceFreeze ? 'freeze-forced' : 'frozen', { version: opts.version, reason: frozen.reason, until: frozen.until });
|
|
97
|
+
if (!opts.forceFreeze) throw new LandingFrozenError(frozen);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
89
100
|
const input = buildReleaseRunInput(opts.version, deps, opts.cutCommit);
|
|
101
|
+
if (opts.forceFreeze) input.forceFreeze = true;
|
|
90
102
|
if (opts.dryRun) return { input, dryRun: true };
|
|
91
103
|
const gate = (deps.checklist ?? checklistGate)(opts.version);
|
|
92
104
|
if (!gate.ok) throw new Error(`release checklist blocked: ${[...gate.red, ...gate.undecided, ...gate.blocked].join(', ')}`);
|
|
105
|
+
const beforeGraph = readLandingFreeze(deps.freezeRoot);
|
|
106
|
+
if (beforeGraph && !opts.forceFreeze) throw new LandingFrozenError(beforeGraph);
|
|
93
107
|
const state = await (deps.graph ?? runGraph)(GRAPH, { input });
|
|
108
|
+
// A freeze switched on while the graph ran stops gate or publish inside it. Only a failed gate/publish node whose own
|
|
109
|
+
// record is the freeze refusal is reported as the freeze; any other failure keeps its own reason.
|
|
110
|
+
if (state.status === 'failed' && !opts.forceFreeze) {
|
|
111
|
+
const refused = (state.nodes ?? []).find((node) => (node.nodeId === 'gate' || node.nodeId === 'publish') && !node.ok
|
|
112
|
+
&& isLandingFreezeRefusal(`${typeof node.output === 'string' ? node.output : JSON.stringify(node.output ?? '')} ${node.error ?? ''}`));
|
|
113
|
+
const during = refused ? readLandingFreeze(deps.freezeRoot) : null;
|
|
114
|
+
if (refused) throw new LandingFrozenError(during ?? { reason: 'frozen during the run', startedAt: new Date().toISOString(), until: null, by: 'release-run' });
|
|
115
|
+
}
|
|
94
116
|
return { input, dryRun: false, state };
|
|
95
117
|
}
|