elanous 0.2.12 → 0.2.14
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/chunks/{7334.a6679d9168f37aa8.js → 1268.0840ff91e083fcbd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1307-6e7e0377c1db0bdc.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/4562-071d8bac44a01069.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-2df489622a52a33a.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/{9906-482c6619b3fdda20.js → 8060-cc008cc6fedfa818.js} +1 -1
- 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/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-74383f890f075511.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-7b2b4f378354869a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-c496bb16da85385f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-1abfe3aec3be6bdc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-1512d91c7491ed62.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-a627db94ec6904a0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-fb260b9283dbf646.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-e6b84ab07d3567d5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-61ec1d2e92bdd50a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-fed69f9759d248d0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-a67e11ba962337b5.js → page-15374909d8f1aebd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-012a742f81218b01.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-fd7e200d4d83fbd7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-99effd9d893a43dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-938de57ae0315e46.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-d47266766766d227.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-160089123a40dd91.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-629e1ede482ec6eb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-793fa536b3a6ef37.js → page-2ce5471926761fdd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-27e99b4e54a849d4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-e995d30f6800b6bf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-39f89c53038a51d4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-32cde713be47cddb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-e7e10cd3386222e5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-5a753bb4ec0cc511.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-04250f0e2c0d03df.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-341480ed8e3c1ea8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-e64caff084f7ce21.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-db07eaffba0df7e3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-8ac8d71c2adf9c8a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-e1e47d8d940aba3d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-86f91034bd5cabe5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-94dc383d3d54db1a.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-b3759cc14da80908.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-ce4d8a1215ce53a3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-58043db78ba519ae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-357750dfb91bf960.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/{page-e297866051595d81.js → page-274703bc41b71333.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-194dce4a83143fce.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-35bdeae2e2c17147.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-3b7e821dc492bdfd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-16bef3da05a900c3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-f8da5dc6f3752276.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-2f810b18d89ecba6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-134c1bc762aee62c.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/2e33713c444d5b39.css +1 -0
- package/apps/pwa/out/_next/static/x6mjI6vqOkVVu0jFuDkz1/_buildManifest.js +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/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/examples/teams/personas/backend.yaml +4 -0
- package/examples/teams/personas/pm.yaml +4 -0
- package/examples/teams/personas/qa.yaml +4 -0
- package/examples/teams/small-product.yaml +20 -0
- package/graphs/lecture/lecture-note.yaml +28 -0
- package/graphs/lecture/recipes.yaml +17 -0
- package/graphs/orchestrator/orchestrator.yaml +59 -0
- package/graphs/orchestrator/recipes.yaml +18 -0
- package/graphs/release/recipes.yaml +3 -0
- package/graphs/release/release-loop.yaml +4 -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 +2 -1
- package/scripts/audit-test-state-writes.ts +72 -17
- package/scripts/brand/check.ts +186 -0
- package/scripts/context-hooks/emit.ts +36 -14
- package/scripts/coord-channel-watch.sh +1 -0
- package/scripts/coord-post.sh +11 -1
- package/scripts/discord-seat-probe.ts +144 -0
- package/scripts/docs-cli-check.ts +23 -6
- package/scripts/expose-rubric-check.ts +118 -0
- package/scripts/expose-rubric-suggest.ts +163 -0
- package/scripts/heal/run-role.ts +14 -0
- package/scripts/hq/hq-standby.sh +65 -0
- package/scripts/hq/standby-promote.ts +173 -0
- package/scripts/hq/standby-role.ts +59 -0
- package/scripts/hq/standby-snapshot.ts +226 -0
- package/scripts/hq/standby-verify.ts +223 -0
- package/scripts/lecture-note/node.ts +256 -0
- package/scripts/lessons/handbook-page.ts +85 -0
- package/scripts/release-loop/gate-node.ts +62 -15
- package/scripts/release-loop/ops-upgrade-node.ts +17 -5
- 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 +196 -0
- package/scripts/release-story/draft.ts +172 -0
- package/scripts/rhythm/daily.ts +383 -0
- package/scripts/rhythm/weekly.ts +294 -0
- package/scripts/skills/public-check.ts +169 -0
- package/scripts/test-deterministic.ts +57 -11
- 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/omni-crawl/SKILL.md +16 -16
- package/skills/omni-crawl/package-lock.json +1 -544
- package/skills/omni-crawl/package.json +3 -4
- package/skills/omni-crawl/scripts/main.ts +13 -13
- package/skills/omni-crawl/scripts/monitor.ts +8 -8
- package/skills/omni-crawl/src/apify.ts +13 -2
- package/skills/omni-crawl/src/capture.ts +6 -0
- package/skills/omni-crawl/src/registry.ts +2 -2
- package/skills/omni-crawl/src/stdout-json.ts +4 -0
- package/skills/omni-digest/SKILL.md +10 -11
- package/skills/omni-digest/package-lock.json +0 -545
- package/skills/omni-digest/package.json +2 -5
- package/skills/omni-digest/scripts/main.ts +3 -3
- package/skills/omni-digest/src/delegate.ts +5 -5
- package/skills/omni-digest/src/fetch.ts +2 -2
- package/skills/omni-digest/src/media.ts +25 -11
- package/skills/omni-digest/src/visual.ts +3 -2
- package/skills/omni-market/scripts/main.ts +1 -1
- package/skills/omni-market/scripts/stdout-json.ts +4 -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/identity-env.ts +11 -0
- package/src/agent-mission/driver.ts +15 -10
- package/src/agent-mission/duo.ts +94 -0
- package/src/agent-mission/pty-decision.ts +1 -1
- package/src/autopilot/build/llm-conflict-merge.ts +45 -8
- package/src/autopilot/build/merge-intent.ts +53 -0
- package/src/autopilot/track-agent-shadow.ts +2 -1
- package/src/autopilot/track-agent.ts +4 -1
- package/src/boot/daemon-prompt-request.ts +3 -0
- package/src/boot/daemon-prompt-turn.ts +6 -2
- package/src/boot/daemon-runtime.ts +48 -16
- package/src/chat/index.ts +2 -0
- package/src/claims/claims-ledger.ts +232 -0
- package/src/claims/claims-render.ts +157 -0
- package/src/cli/claims-cli.ts +94 -0
- package/src/cli/decisions-cli.ts +84 -20
- package/src/cli/docs-cli.ts +2 -0
- package/src/cli/flow-cli.ts +11 -0
- package/src/cli/freeze-cli.ts +45 -0
- 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/outputs-cli.ts +92 -0
- package/src/cli/persona-cli.ts +29 -0
- package/src/cli/pod-cli.ts +10 -3
- package/src/cli/pr-cli.ts +44 -2
- package/src/cli/private-ledger-commands.ts +54 -4
- package/src/cli/pty-drive-verdict.ts +11 -2
- package/src/cli/release-cli.ts +248 -45
- package/src/cli/rfc-status.ts +91 -0
- package/src/connectors/linear.ts +5 -3
- package/src/context-bus/condense.ts +189 -0
- package/src/context-bus/context-day.ts +82 -0
- package/src/context-bus/context-now-public.ts +86 -0
- package/src/context-bus/context-now-surfaces.ts +70 -4
- package/src/context-bus/context-now.ts +4 -3
- package/src/context-bus/long-term-memory.ts +201 -24
- package/src/context-bus/nightly-condense.ts +1 -1
- 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 +13 -7
- package/src/decisions/decision-ledger.ts +83 -18
- 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/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-kinds/registry.ts +2 -2
- package/src/graph-runner/graph-approval-card.ts +2 -2
- package/src/graph-runner/graph-grow.ts +64 -11
- package/src/graph-runner/graph-tick.ts +13 -2
- package/src/graph-runner/runner.ts +111 -22
- package/src/harness/harness-cli-command.ts +178 -27
- package/src/harness/harness-incidents.ts +43 -12
- package/src/harness/harness-pod-dispatch.ts +2 -0
- package/src/harness/harness-queue-child.ts +37 -0
- package/src/harness/harness-queue.ts +494 -0
- package/src/harness/helper-scan.ts +139 -0
- package/src/harness/model-input-observation.ts +37 -0
- package/src/hooks/github-shadow.ts +72 -2
- package/src/hooks/receiver.ts +4 -2
- package/src/hq/hq.ts +318 -0
- package/src/hq/lease.ts +210 -0
- package/src/hq/ledger-write-target.ts +41 -0
- package/src/hq/promote.ts +301 -0
- package/src/index.ts +281 -27
- package/src/intake-plane/discord-seat-work.ts +13 -6
- package/src/intake-plane/linear-wish.ts +72 -0
- package/src/intake-plane/persona-answer.ts +5 -0
- package/src/intake-plane/telegram-seat-work.ts +13 -6
- 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/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 +32 -0
- package/src/loops/checker.ts +180 -0
- package/src/loops/contract/examples/orchestrator.loop.yaml +1 -1
- package/src/loops/loop-cli.ts +72 -6
- package/src/loops/neighbors.ts +40 -0
- package/src/loops/observe.ts +59 -0
- package/src/loops/orchestrator/author-depth.ts +164 -0
- package/src/loops/orchestrator/author-input-check.ts +77 -0
- package/src/loops/orchestrator/author-ledger.ts +230 -0
- package/src/loops/orchestrator/finish-rate.ts +175 -0
- package/src/loops/orchestrator/overlap.ts +180 -0
- package/src/loops/orchestrator/resync-shadow.ts +227 -0
- package/src/loops/orchestrator/tick.ts +302 -0
- package/src/loops/orchestrator/traffic.ts +165 -0
- package/src/loops/package/verify.ts +125 -0
- package/src/loops/registry.ts +454 -9
- package/src/loops/status-rows.ts +118 -0
- package/src/loops/verdict.ts +34 -0
- package/src/maturity/bot-slash-catalog.ts +66 -0
- package/src/maturity/feature-maturity.ts +8 -5
- package/src/memory.ts +37 -4
- package/src/nexus/api/card-followup-route.ts +118 -0
- package/src/nexus/api/dashboard.ts +6 -1
- package/src/nexus/api/decisions-route.ts +68 -0
- package/src/nexus/api/http-server.ts +65 -2
- package/src/nexus/api/merge-approvals-gate.ts +1 -1
- package/src/nexus/api/personas.ts +2 -1
- package/src/nexus/api/schedules-read.ts +5 -1
- package/src/nexus/api/skill-exec-route.ts +97 -0
- package/src/nexus/api/task-cards-api.ts +44 -5
- package/src/nexus/api/workflows.ts +2 -1
- package/src/persona/loader.ts +18 -0
- package/src/persona/persona-todo.ts +14 -9
- package/src/persona/presets.ts +2 -1
- package/src/persona/team.ts +105 -0
- package/src/persona/types.ts +4 -0
- package/src/pod-lease/host-lease.ts +130 -20
- package/src/release-loop/auto-start.ts +48 -0
- package/src/release-loop/checklist.ts +13 -4
- package/src/release-loop/feature-store.ts +28 -16
- package/src/release-loop/landed-but-yellow.ts +12 -0
- package/src/release-loop/landing-freeze.ts +121 -0
- package/src/release-loop/placement.ts +242 -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 +73 -15
- package/src/seat-dispatch/seat-questions.ts +58 -0
- package/src/seat-loop/seat-loop.ts +495 -46
- package/src/seat-loop/seat-pack.ts +127 -0
- package/src/self-dev/ask-launch-flow.ts +3 -0
- package/src/self-dev/dev-pipeline.ts +6 -2
- package/src/self-dev/draft-sweep.ts +76 -0
- package/src/self-dev/run-store.ts +73 -1
- package/src/self-dev/run-supervisor.ts +8 -0
- 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 +294 -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-lookup.ts +147 -0
- package/src/self-implement/heal-intake.ts +79 -0
- package/src/self-implement/host-regate.ts +25 -2
- package/src/self-implement/l8-merge-queue.ts +157 -1
- package/src/self-implement/orchestrator.ts +114 -10
- package/src/self-implement/revert-guard.ts +62 -0
- package/src/self-implement/run-ledger.ts +1 -1
- package/src/self-implement/seams.ts +28 -8
- package/src/session/index.ts +2 -0
- package/src/steward/launch.ts +5 -1
- package/src/steward/readiness.ts +125 -0
- package/src/steward/rescue.ts +75 -0
- package/src/steward/triage.ts +48 -9
- package/src/task-cards/card-cli.ts +86 -0
- package/src/task-cards/card-store.ts +10 -6
- package/src/task-orchestrator/surfaces/pod-lease.ts +23 -8
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +46 -6
- package/src/task-orchestrator/surfaces/pod-pool.ts +27 -10
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +112 -18
- package/src/telegram-commands.ts +27 -3
- package/src/telegram-dispatch-forward.ts +1 -1
- package/src/telegram-fabric-plan.ts +90 -0
- package/src/telegram-loops-command.ts +44 -0
- package/src/telegram-project-command.ts +10 -4
- package/src/telegram.ts +5 -0
- package/src/tool-runtime/elanous-control-runtimes.ts +2 -1
- package/src/user-config.ts +204 -16
- package/src/version/packed-revision.json +1 -1
- package/src/workflow-runtime/executor.ts +4 -0
- package/src/workflow-runtime/node-catalog.ts +13 -0
- package/src/workflow-runtime/nodes/knowledge.ts +38 -0
- package/src/workflow-runtime/schema.ts +17 -1
- package/src/workflow-runtime/storage.ts +31 -13
- package/src/workflow-runtime/types.ts +13 -1
- package/apps/pwa/out/_next/static/-6XrAoD3eU7NyMHAE4xPS/_buildManifest.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1147-466ec15afacc1a65.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1159-0c5ef8bc21cb6afb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1394-ee3795b112907122.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-2e522a8ac5f2afde.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/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/6751-bb3595948b2e311b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7274-19b7279f18b47ab3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7277-0b2fc7c16ba2d64b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7551-a0e0707b79164ddd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7758-0b018a9e4fc56200.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-0259994816604c49.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/9920-5cfdc5e8fd209825.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-cb169620945d1952.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-cefe8169e36cf62a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-d2e35228bc497119.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-5c05b0da4bc1392c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-e0ccdb576812b523.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-813c4a52c9d05ca8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-4731f2c215358937.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-a022b257950f39e6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-a2ffc717e04b8b0a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-b59ee4e70fc18fcf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-c172dabd796eb7a3.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-1efd0cb9e0ed9d13.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-d5dc7ff69a37b4cf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-e71c84189e8c683e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-1d6a533ce5a6efcb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-dfca9b7d2de0b135.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-6dd3bfbbde2a5c2c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-490653e8d4cc8852.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-77447356baad75d2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-29e961191f6057df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-c822f55b884d9104.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fa4ccfc432424e52.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-6d604227e1664b71.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-9c7e8f744a765daf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-4048543b4b7ad5e0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-4c3a3126abed551a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-64b0aecb1ca75e75.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-4292d0149020e8a7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-db14d0be7f1c460c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-e5d8072cd7cfef73.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-9547b5cec7cf76bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-e6dcb83265530636.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-45248f08d6aecae5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-2cddeb9e2aeedc55.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-baba2e047872fb0f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-e895da4ec9cac345.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-4344faf20216233b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-698bb29f11864b89.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-bd6a6dc1ddc273f2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-2e9e2fb01c9da1c9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-d62ceb13411ebba4.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/{-6XrAoD3eU7NyMHAE4xPS → x6mjI6vqOkVVu0jFuDkz1}/_ssgManifest.js +0 -0
|
@@ -5,6 +5,7 @@ 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,7 +19,7 @@ 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[] }
|
|
22
|
+
interface CommandResult { rc: number; output: string; passedIds?: string[]; stalledEnv?: Array<{ file: string; reason: 'no-output' }> }
|
|
22
23
|
export interface GateRunner {
|
|
23
24
|
command(cmd: string, args: string[], cwd: string): Promise<CommandResult>;
|
|
24
25
|
localCommand(cmd: string, args: string[], cwd: string): Promise<CommandResult>;
|
|
@@ -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,6 +52,7 @@ export interface GateResult {
|
|
|
50
52
|
fixed: number;
|
|
51
53
|
knownEnv: number;
|
|
52
54
|
knownEnvCleared: string[];
|
|
55
|
+
stalledEnv: Array<{ file: string; reason: 'no-output'; local: 'passed' | 'failed' }>;
|
|
53
56
|
baselineSource?: 'ledger' | 'instance' | 'cut-logs' | 'swept';
|
|
54
57
|
durationMs: number;
|
|
55
58
|
error?: string;
|
|
@@ -322,13 +325,15 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
322
325
|
const ran = clean && lastMatch(clean, /Ran (\d+) tests? across (\d+) files?/);
|
|
323
326
|
const passes = clean === undefined ? NaN : summaryCount(clean, 'pass');
|
|
324
327
|
const fails = clean === undefined ? NaN : summaryCount(clean, 'fail');
|
|
328
|
+
const errors = clean === undefined ? NaN : summaryCount(clean, 'error');
|
|
325
329
|
let reason: ShardReason | undefined;
|
|
326
330
|
let namedFailures: number | undefined;
|
|
327
331
|
let unattributedDetail: string | undefined;
|
|
328
332
|
if (jobFailed || (jobExitCode !== undefined && jobExitCode !== 0) || (rc !== undefined && rc !== 0 && rc !== 1)) reason = 'job-failed';
|
|
329
333
|
else if (!clean || rc === undefined) reason = 'no-output';
|
|
330
334
|
else if (!ran || !Number.isFinite(passes) || !Number.isFinite(fails)
|
|
331
|
-
|| Number(ran[2]) === 0 || Number(ran[2]) > paths.length
|
|
335
|
+
|| Number(ran[2]) === 0 || Number(ran[2]) > paths.length
|
|
336
|
+
|| (Number(ran[1]) === 0 && !(errors > 0))) reason = 'incomplete';
|
|
332
337
|
else {
|
|
333
338
|
try {
|
|
334
339
|
const attributed = failuresOf({ rc, output: clean }, `pod-${shard} shard`);
|
|
@@ -400,7 +405,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
400
405
|
return { runs: children.flatMap((child) => child.runs), stalled: children.flatMap((child) => child.stalled) };
|
|
401
406
|
}
|
|
402
407
|
return { runs: [{ output: clean!, rc: rc!, pass: passes, fail: fails,
|
|
403
|
-
errors: Number.isNaN(
|
|
408
|
+
errors: Number.isNaN(errors) ? 0 : errors,
|
|
404
409
|
ran: Number(ran![1]), files: Number(ran![2]), passedIds: junit ? junitPassedIds(junit) : [] }], stalled: [] };
|
|
405
410
|
};
|
|
406
411
|
// 모든 조각이 끝난 뒤 판정한다 — 한 조각의 예외로 먼저 돌아가면 다른 Pod 가 도는 채로 정리가 시작된다(#22002 리뷰 R3).
|
|
@@ -413,11 +418,12 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
413
418
|
const total = measured.reduce((sum, run) => ({ pass: sum.pass + run.pass, fail: sum.fail + run.fail,
|
|
414
419
|
errors: sum.errors + run.errors, ran: sum.ran + run.ran, files: sum.files + run.files }),
|
|
415
420
|
{ pass: 0, fail: 0, errors: 0, ran: 0, files: 0 });
|
|
416
|
-
if (stalledShards.length) throw new StalledPodShards(stalledShards, total);
|
|
417
|
-
if (total.ran === 0 && total.errors === 0) throw new Error('sweep incomplete: no tests ran');
|
|
421
|
+
if (stalledShards.some((item) => item.reason !== 'no-output' || item.files.length !== 1)) throw new StalledPodShards(stalledShards, total);
|
|
422
|
+
if (total.ran === 0 && total.errors === 0 && !stalledShards.length) throw new Error('sweep incomplete: no tests ran');
|
|
418
423
|
const outputs = measured.map((run) => run.output.replace(/(?:^|\n)\s*\d+ (?:pass|fail|errors?)\s*(?=\n|$)/g, '\n').replace(/Ran \d+ tests? across \d+ files?\.?/g, ''));
|
|
419
424
|
return { rc: total.fail + total.errors ? 1 : 0, output: outputs.join('\n') + `\n${total.pass} pass\n${total.fail} fail\n${total.errors} errors\nRan ${total.ran} tests across ${total.files} files.\n`,
|
|
420
|
-
passedIds: [...new Set(measured.flatMap((run) => run.passedIds))].sort()
|
|
425
|
+
passedIds: [...new Set(measured.flatMap((run) => run.passedIds))].sort(),
|
|
426
|
+
stalledEnv: stalledShards.flatMap((item) => item.files.map((file) => ({ file, reason: 'no-output' as const }))) };
|
|
421
427
|
};
|
|
422
428
|
return {
|
|
423
429
|
command,
|
|
@@ -533,6 +539,12 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
533
539
|
const start = Date.now();
|
|
534
540
|
const explicitConfig = getElanousConfigDirOverride();
|
|
535
541
|
const root = explicitConfig ? effectiveInstanceRoot() : (opts.instanceRoot ?? effectiveInstanceRoot());
|
|
542
|
+
// The freeze is read from the same root the gate runs in (a config-dir override must not move it elsewhere).
|
|
543
|
+
const frozen = readLandingFreeze(root);
|
|
544
|
+
if (frozen) {
|
|
545
|
+
debug.log('release.run', opts.forceFreeze ? 'freeze-forced' : 'frozen', { version: opts.version, reason: frozen.reason, until: frozen.until, node: 'gate' });
|
|
546
|
+
if (!opts.forceFreeze) throw new Error(landingFreezeMessage(frozen));
|
|
547
|
+
}
|
|
536
548
|
const ledger = explicitConfig ? root : (opts.ledgerRoot ?? releaseLedgerRoot());
|
|
537
549
|
const roots = [...new Set([ledger, root])];
|
|
538
550
|
const cachedBaseline = (version: string, commit: string) => {
|
|
@@ -550,7 +562,7 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
550
562
|
return baselineCommit(root, version);
|
|
551
563
|
};
|
|
552
564
|
const repo = resolve(opts.repo ?? process.cwd());
|
|
553
|
-
const result: GateResult = { outcome: 'error', commit: opts.commit, introduced: [], preexisting: 0, fixed: 0, knownEnv: 0, knownEnvCleared: [], durationMs: 0 };
|
|
565
|
+
const result: GateResult = { outcome: 'error', commit: opts.commit, introduced: [], preexisting: 0, fixed: 0, knownEnv: 0, knownEnvCleared: [], stalledEnv: [], durationMs: 0 };
|
|
554
566
|
let work: string | undefined;
|
|
555
567
|
let baseSnapshot = false;
|
|
556
568
|
let cutFailures: SweepFailures | undefined;
|
|
@@ -601,7 +613,38 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
601
613
|
for (const dir of [cutTree, join(cutTree, 'apps/pwa')]) check(await runner.command('bun', ['install'], dir), `bun install ${dir}`);
|
|
602
614
|
const cutRun = await runner.sweep(cutTree, join(root, 'release', opts.version, 'gate-logs', 'cut'),
|
|
603
615
|
opts.pod ? { ...opts.pod, durationSource: join(ledger, 'release', opts.baselineVersion, 'gate-logs', 'cut') } : undefined);
|
|
604
|
-
const
|
|
616
|
+
const localStalled = async (run: CommandResult, tree: string, commit: string, label: string) => {
|
|
617
|
+
const stalled = run.stalledEnv ?? [];
|
|
618
|
+
const noCompletedShard = stalled.length && run.rc === 0
|
|
619
|
+
&& /(?:^|\n)0 pass\n0 fail\n0 errors\nRan 0 tests across 0 files\./.test(run.output);
|
|
620
|
+
const measured: SweepFailures = noCompletedShard ? { failures: [], errors: [] } : failuresOf(run, `${label} sweep`);
|
|
621
|
+
if (!stalled.length) return measured;
|
|
622
|
+
let hostTree = tree;
|
|
623
|
+
const local: GateResult['stalledEnv'] = [];
|
|
624
|
+
try {
|
|
625
|
+
if (opts.remote) {
|
|
626
|
+
hostTree = join(mkdtempSync(join(tmpdir(), 'release-gate-local-')), 'cut');
|
|
627
|
+
check(await runner.localCommand('git', ['clone', '--quiet', '--shared', '--no-checkout', repo, hostTree], repo), 'host cut clone');
|
|
628
|
+
check(await runner.localCommand('git', ['checkout', '--quiet', '--detach', commit], hostTree), 'host cut checkout');
|
|
629
|
+
for (const dir of [hostTree, join(hostTree, 'apps/pwa')]) check(await runner.localCommand('bun', ['install'], dir), `host bun install ${dir}`);
|
|
630
|
+
}
|
|
631
|
+
for (const { file, reason } of stalled) {
|
|
632
|
+
if (!/^(?:[\w.-]+\/)+[\w.-]+\.test\.tsx?$/.test(file) || file.split('/').includes('..')) throw new Error(`unsafe test path: ${file}`);
|
|
633
|
+
const isolated = await runner.localCommand('bun', ['run', 'test:deterministic', asPath(file)], hostTree);
|
|
634
|
+
const parsed = failuresOf(isolated, `${label} host isolated ${file}`);
|
|
635
|
+
if ([...parsed.failures, ...parsed.errors].some((id) => fileOf(id) !== file)) throw new Error(`host isolated run attributed to another file: ${file}`);
|
|
636
|
+
measured.failures.push(...parsed.failures);
|
|
637
|
+
measured.errors.push(...parsed.errors);
|
|
638
|
+
local.push({ file, reason, local: parsed.failures.length || parsed.errors.length ? 'failed' : 'passed' });
|
|
639
|
+
}
|
|
640
|
+
} finally {
|
|
641
|
+
if (opts.remote && hostTree !== tree) rmSync(dirname(hostTree), { recursive: true, force: true });
|
|
642
|
+
}
|
|
643
|
+
result.stalledEnv.push(...local);
|
|
644
|
+
debug.log('release-loop.gate', 'pod-shard-stalled-env', { files: stalled.map(({ file, reason }) => ({ file, reason })), local });
|
|
645
|
+
return measured;
|
|
646
|
+
};
|
|
647
|
+
const cut = await localStalled(cutRun, cutTree, opts.commit, 'cut');
|
|
605
648
|
cutFailures = cut;
|
|
606
649
|
const baseSha = opts.baselineCommit ?? baselineReleaseCommit(opts.baselineVersion);
|
|
607
650
|
const cached = cachedBaseline(opts.baselineVersion, baseSha);
|
|
@@ -630,22 +673,24 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
630
673
|
// hour of local baseline after a 23-minute Pod cut).
|
|
631
674
|
const baseRun = await runner.sweep(await getBaseTree(), join(root, 'release', opts.version, 'gate-logs', 'baseline'),
|
|
632
675
|
opts.pod ? { ...opts.pod, durationSource: join(ledger, 'release', opts.baselineVersion, 'gate-logs', 'cut') } : undefined);
|
|
633
|
-
baseline =
|
|
676
|
+
baseline = await localStalled(baseRun, baseTree!, baseSha, 'baseline');
|
|
634
677
|
}
|
|
635
678
|
for (const id of [...baseline.failures, ...baseline.errors, ...cut.failures, ...cut.errors]) fileOf(id);
|
|
636
679
|
const known = opts.pod ? loadEnvKnownFailures(repo) : new Set<string>();
|
|
637
680
|
const cutIds = [...cut.failures, ...cut.errors];
|
|
681
|
+
const cutHostFailed = new Set(cutRun.stalledEnv?.map(({ file }) => file) ?? []);
|
|
638
682
|
const baselineIds = [...baseline.failures, ...baseline.errors];
|
|
639
683
|
const countedCut = splitKnownEnv(cutIds, known);
|
|
640
684
|
const countedBaseline = splitKnownEnv(baselineIds, known);
|
|
641
685
|
result.knownEnv = countedCut.knownEnv.length;
|
|
642
|
-
result.knownEnvCleared = [...new Set(cutRun.passedIds ?? [])].filter((id) => known.has(id) && !cutIds.includes(id)).sort();
|
|
686
|
+
result.knownEnvCleared = [...new Set(cutRun.passedIds ?? [])].filter((id) => known.has(id) && !cutIds.includes(id) && !cutRun.stalledEnv?.some(({ file }) => fileOf(id) === file)).sort();
|
|
643
687
|
const diff = diffFailures(countedCut.counted, countedBaseline.counted);
|
|
644
688
|
result.fixed = diff.fixed.length;
|
|
645
689
|
result.preexisting = diff.common.length;
|
|
646
690
|
for (const file of new Set(diff.newFailures.map(fileOf))) {
|
|
647
|
-
const
|
|
648
|
-
|
|
691
|
+
const isolatedCut = cutHostFailed.has(file)
|
|
692
|
+
? { failures: cut.failures.filter((id) => fileOf(id) === file), errors: cut.errors.filter((id) => fileOf(id) === file) }
|
|
693
|
+
: failuresOf(await runner.command('bun', ['run', 'test:deterministic', asPath(file)], cutTree), `cut isolated ${file}`);
|
|
649
694
|
const reproduced = new Set([...isolatedCut.failures, ...isolatedCut.errors]);
|
|
650
695
|
const candidates = diff.newFailures.filter((id) => fileOf(id) === file && reproduced.has(id));
|
|
651
696
|
if (candidates.length === 0) continue;
|
|
@@ -690,7 +735,7 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
690
735
|
if (opts.remote) runner.remoteMirror = undefined;
|
|
691
736
|
result.durationMs = Date.now() - start;
|
|
692
737
|
}
|
|
693
|
-
if (cutFailures && !result.stalledShards?.length) {
|
|
738
|
+
if (cutFailures && !result.stalledShards?.length && !result.stalledEnv.length) {
|
|
694
739
|
try {
|
|
695
740
|
for (const location of roots) {
|
|
696
741
|
const path = failureFile(location, opts.version);
|
|
@@ -714,7 +759,8 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
714
759
|
export function graphGateResult(result: GateResult, graph: boolean) {
|
|
715
760
|
return { ...result, ...(graph && result.outcome === 'regression' ? { outcome: 'fail' as const } : {}),
|
|
716
761
|
verdict: result.outcome === 'ok' ? 'pass' as const : 'fail' as const,
|
|
717
|
-
summary: (result.outcome === 'ok' ? `새 회귀 ${result.introduced.length} · 기존 ${result.preexisting} · 고침 ${result.fixed}` : `게이트 ${result.outcome}: ${result.error ?? result.introduced.length + ' new regressions'}`) + ` · 환경 알려진 실패 ${result.knownEnv}`
|
|
762
|
+
summary: (result.outcome === 'ok' ? `새 회귀 ${result.introduced.length} · 기존 ${result.preexisting} · 고침 ${result.fixed}` : `게이트 ${result.outcome}: ${result.error ?? result.introduced.length + ' new regressions'}`) + ` · 환경 알려진 실패 ${result.knownEnv}`
|
|
763
|
+
+ (result.stalledEnv.length ? ` · 환경 멈춤 ${result.stalledEnv.length} (Pod 밖 통과 ${result.stalledEnv.filter((item) => item.local === 'passed').length})` : '') };
|
|
718
764
|
}
|
|
719
765
|
|
|
720
766
|
export function parseOptions(args: string[], env: NodeJS.ProcessEnv): GateOptions | 'help' {
|
|
@@ -728,6 +774,7 @@ export function parseOptions(args: string[], env: NodeJS.ProcessEnv): GateOption
|
|
|
728
774
|
commit: typeof fromGraph.commit === 'string' ? fromGraph.commit : '',
|
|
729
775
|
version: typeof fromGraph.version === 'string' ? fromGraph.version : '',
|
|
730
776
|
baselineVersion: typeof fromGraph.previousVersion === 'string' ? fromGraph.previousVersion : undefined,
|
|
777
|
+
forceFreeze: fromGraph.forceFreeze === true,
|
|
731
778
|
baselineCommit: typeof fromGraph.previousCommit === 'string' ? fromGraph.previousCommit : undefined,
|
|
732
779
|
remote: typeof fromGraph.gateRemote === 'string' ? fromGraph.gateRemote : undefined,
|
|
733
780
|
remoteMirror: typeof fromGraph.gateRemoteMirror === 'string' ? fromGraph.gateRemoteMirror : undefined,
|
|
@@ -772,7 +819,7 @@ if (import.meta.main) {
|
|
|
772
819
|
version = opts.version;
|
|
773
820
|
result = await judgeGate(opts);
|
|
774
821
|
} catch (error) {
|
|
775
|
-
result = { outcome: 'error', commit: '', introduced: [], preexisting: 0, fixed: 0, knownEnv: 0, knownEnvCleared: [], durationMs: Date.now() - start, error: String(error) };
|
|
822
|
+
result = { outcome: 'error', commit: '', introduced: [], preexisting: 0, fixed: 0, knownEnv: 0, knownEnvCleared: [], stalledEnv: [], durationMs: Date.now() - start, error: String(error) };
|
|
776
823
|
}
|
|
777
824
|
if (result.error) console.error(result.error);
|
|
778
825
|
debug.log('release-loop.gate', 'result', { version, outcome: result.outcome });
|
|
@@ -7,7 +7,7 @@ import { errorResult, finishNode, lastResult, nodeOutput, readGraphContext, runC
|
|
|
7
7
|
|
|
8
8
|
interface HostResult { host: string; ok: boolean; before: string; after: string; error?: string; skipped?: string; hooks?: string; restart?: 'no-service' }
|
|
9
9
|
/** Where the internal feed is packed from: a checkout of the release tag, never the run's working tree. */
|
|
10
|
-
export interface FeedSource { checkout: string; commit: string; cleanup: () => void }
|
|
10
|
+
export interface FeedSource { checkout: string; commit: string; deps: 'linked' | 'installed'; cleanup: () => void }
|
|
11
11
|
interface OpsUpgradeDeps { exists?: (path: string) => boolean; installSource?: () => string | null; feedSource?: (version: string) => FeedSource }
|
|
12
12
|
|
|
13
13
|
/** Detached worktree at refs/tags/v<version> with the PWA built — the feed ships what was released (10-04 0.2.11:
|
|
@@ -26,16 +26,28 @@ export function tagFeedSource(run: CommandRunner, repo: string = process.cwd()):
|
|
|
26
26
|
try {
|
|
27
27
|
const added = run('git', ['worktree', 'add', '--detach', tree, commit], repo);
|
|
28
28
|
if (added.status !== 0) throw new Error(`태그 체크아웃 실패: ${reason(added)}`);
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
const sameDeps = ['bun.lock', 'apps/pwa/bun.lock', 'package.json', 'apps/pwa/package.json'].every((file) => {
|
|
30
|
+
try { return readFileSync(join(repo, file), 'utf8') === readFileSync(join(tree, file), 'utf8'); }
|
|
31
|
+
catch { return false; }
|
|
32
|
+
});
|
|
33
|
+
const deps: FeedSource['deps'] = sameDeps ? 'linked' : 'installed';
|
|
34
|
+
if (sameDeps) {
|
|
35
|
+
symlinkSync(join(repo, 'node_modules'), join(tree, 'node_modules'));
|
|
36
|
+
if (existsSync(join(repo, 'apps/pwa/node_modules'))) symlinkSync(join(repo, 'apps/pwa/node_modules'), join(tree, 'apps/pwa/node_modules'));
|
|
37
|
+
} else {
|
|
38
|
+
for (const cwd of [tree, join(tree, 'apps/pwa')]) {
|
|
39
|
+
const installed = run('bun', ['install', '--frozen-lockfile'], cwd, 900_000);
|
|
40
|
+
if (installed.status !== 0) throw new Error(`태그 의존성 설치 실패: ${reason(installed)}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
31
43
|
// The feed carries the PWA build (apps/pwa/out is ignored, so a fresh tag checkout has none until built).
|
|
32
44
|
const built = run('bun', ['bin/elanous.mjs', 'nexus', 'build'], tree, 900_000);
|
|
33
45
|
if (built.status !== 0) throw new Error(`태그 PWA 빌드 실패: ${reason(built)}`);
|
|
46
|
+
return { checkout: tree, commit, deps, cleanup };
|
|
34
47
|
} catch (error) {
|
|
35
48
|
cleanup();
|
|
36
49
|
throw error;
|
|
37
50
|
}
|
|
38
|
-
return { checkout: tree, commit, cleanup };
|
|
39
51
|
};
|
|
40
52
|
}
|
|
41
53
|
|
|
@@ -96,7 +108,7 @@ export function runOpsUpgrade(run: CommandRunner = runCommand, deps: OpsUpgradeD
|
|
|
96
108
|
const data = lastResult(feed);
|
|
97
109
|
if (feed.status !== 0 || data?.ok !== true || data.version !== version) feedError = `내부 피드 발행 실패: ${feed.status === 0 && data?.ok === true ? `판 불일치 (${String(data.version)})` : reason(feed)}`;
|
|
98
110
|
else if (data.commit !== source.commit) feedError = `내부 피드 발행 실패: 묶은 커밋 ${String(data.commit).slice(0, 12)} ≠ 태그 ${source.commit.slice(0, 12)}`;
|
|
99
|
-
debug.log('release-loop.ops-upgrade', 'feed-source', { version, commit: source.commit, source: `refs/tags/v${version}`, packed: data?.commit, pwaBuild: data?.pwaBuild });
|
|
111
|
+
debug.log('release-loop.ops-upgrade', 'feed-source', { version, commit: source.commit, source: `refs/tags/v${version}`, deps: source.deps, packed: data?.commit, pwaBuild: data?.pwaBuild });
|
|
100
112
|
}
|
|
101
113
|
} catch (error) { feedError = `내부 피드 발행 실패: ${firstLine(String(error))}`; }
|
|
102
114
|
finally { source?.cleanup(); }
|
|
@@ -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
|
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { debug } from '../../src/debug/log.js';
|
|
5
|
+
import { effectiveInstanceRoot, prodInstanceRoot } from '../../src/instance/resolve.js';
|
|
6
|
+
import { listChecklist, type ChecklistItem } from '../../src/release-loop/checklist.js';
|
|
7
|
+
import { userConfigPath } from '../../src/user-config.js';
|
|
8
|
+
import { finishNode, readGraphContext, type GraphContext } from './node-verdict.js';
|
|
9
|
+
|
|
10
|
+
const directory = '40. Project/엘라누스 릴리스';
|
|
11
|
+
const indexName = '_색인 — 엘라누스 릴리스.md';
|
|
12
|
+
const kst = new Intl.DateTimeFormat('en-CA', { timeZone: 'Asia/Seoul', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hourCycle: 'h23' });
|
|
13
|
+
|
|
14
|
+
function publishedTime(value: string): { date: string; short: string; full: string } {
|
|
15
|
+
const instant = new Date(value);
|
|
16
|
+
if (Number.isNaN(instant.getTime())) throw new Error(`invalid publishedAt: ${value}`);
|
|
17
|
+
const parts = Object.fromEntries(kst.formatToParts(instant).map(({ type, value: part }) => [type, part]));
|
|
18
|
+
const date = `${parts.year}-${parts.month}-${parts.day}`;
|
|
19
|
+
return { date, short: `${parts.month}-${parts.day} ${parts.hour}:${parts.minute}`, full: `${date} ${parts.hour}:${parts.minute} KST` };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function firstClause(title: string): string {
|
|
23
|
+
return title.split(/\s+[—–·]\s+|[\n。]/, 1)[0]!.trim().replace(/\s+/g, ' ');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function changeCount(draft: string): number {
|
|
27
|
+
return draft.split(/\r?\n/).filter((line) => /^\s*(?:[-*] |\d+\. )/.test(line)).length;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function configuredVaultRoot(configPath: string): string | undefined {
|
|
31
|
+
if (!existsSync(configPath)) return undefined;
|
|
32
|
+
const raw = JSON.parse(readFileSync(configPath, 'utf8')) as { obsidian?: { vaultRoot?: unknown; vault?: unknown } };
|
|
33
|
+
const value = raw.obsidian?.vaultRoot ?? raw.obsidian?.vault;
|
|
34
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function releaseHistory(context: GraphContext, root: string): string[] {
|
|
38
|
+
const identity = context as GraphContext & { graphId?: string; runId?: string };
|
|
39
|
+
if (identity.graphId !== 'release-loop' || !identity.runId || !/^[\w.-]+$/.test(identity.runId)) return [];
|
|
40
|
+
const dir = join(root, 'graph-runs', 'release-loop');
|
|
41
|
+
type Run = { graphId: string; runId: string; startedAt?: string; input?: { version?: string };
|
|
42
|
+
nodes?: Array<{ nodeId: string; ok: boolean; output?: string; error?: string }> };
|
|
43
|
+
const current = JSON.parse(readFileSync(join(dir, `${identity.runId}.json`), 'utf8')) as Run;
|
|
44
|
+
if (current.graphId !== 'release-loop' || current.runId !== identity.runId || current.input?.version !== context.input.version || !Array.isArray(current.nodes)) {
|
|
45
|
+
throw new Error('release graph run identity mismatch');
|
|
46
|
+
}
|
|
47
|
+
const runs = readdirSync(dir).filter((file) => file.endsWith('.json') && !file.includes('.decision.json') && /^[\w.-]+\.json$/.test(file))
|
|
48
|
+
.map((file) => {
|
|
49
|
+
try { return JSON.parse(readFileSync(join(dir, file), 'utf8')) as Run; }
|
|
50
|
+
catch { return undefined; }
|
|
51
|
+
}).filter((run): run is Run => !!run && run.graphId === 'release-loop' && run.input?.version === context.input.version
|
|
52
|
+
&& Array.isArray(run.nodes) && (run.runId === identity.runId || !!current.startedAt && !!run.startedAt && run.startedAt <= current.startedAt));
|
|
53
|
+
const history: string[] = [];
|
|
54
|
+
for (const nodeId of ['gate', 'cutoff']) {
|
|
55
|
+
const attempts = runs.flatMap((run) => run.nodes!.filter((node) => node.nodeId === nodeId));
|
|
56
|
+
if (attempts.length < 2 || !current.nodes.some((node) => node.nodeId === nodeId && node.ok)) continue;
|
|
57
|
+
const failed = attempts.find((node) => !node.ok);
|
|
58
|
+
if (!failed) continue;
|
|
59
|
+
let reason = failed.error;
|
|
60
|
+
if (failed.output) {
|
|
61
|
+
try {
|
|
62
|
+
const data = JSON.parse(failed.output.trim().split('\n').at(-1)!) as { reason?: string; summary?: string };
|
|
63
|
+
reason = data.reason ?? data.summary ?? reason;
|
|
64
|
+
} catch { /* The graph retained an unstructured command failure. */ }
|
|
65
|
+
}
|
|
66
|
+
history.push(`- ${nodeId === 'gate' ? '게이트 재시도' : '재컷'}: ${reason ?? '사유 기록 없음'}`);
|
|
67
|
+
}
|
|
68
|
+
return history;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface ReleaseRecord { version: string; tag: string; sourceCommit: string; publishedAt: string; publicRepo?: string }
|
|
72
|
+
export interface VaultNoteDeps {
|
|
73
|
+
instanceRoot?: string;
|
|
74
|
+
productionRoot?: string;
|
|
75
|
+
vaultRoot?: string;
|
|
76
|
+
configPath?: string;
|
|
77
|
+
checklist?: (version: string) => Pick<ChecklistItem, 'id' | 'title' | 'owner' | 'status'>[];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function featureFamily(id: string): string {
|
|
81
|
+
const stem = id.replace(/[-_]?\d+[a-z]?$/i, '');
|
|
82
|
+
return (stem.split('-')[0] || id).toUpperCase();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function runVaultNote(context: GraphContext = readGraphContext(), deps: VaultNoteDeps = {}) {
|
|
86
|
+
const version = context.input.version;
|
|
87
|
+
try {
|
|
88
|
+
if ((deps.instanceRoot ?? effectiveInstanceRoot()) !== (deps.productionRoot ?? prodInstanceRoot())) {
|
|
89
|
+
debug.log('release.vault-note', 'skipped', { version, reason: 'isolated' });
|
|
90
|
+
return { outcome: 'ok' as const, verdict: 'pass' as const, summary: 'skipped: isolated', skipped: 'isolated' };
|
|
91
|
+
}
|
|
92
|
+
const vault = deps.vaultRoot ?? configuredVaultRoot(deps.configPath ?? userConfigPath());
|
|
93
|
+
if (!vault) {
|
|
94
|
+
// OP 10-04 19:10 ③: no vault configured is not a failure — skip and say so.
|
|
95
|
+
debug.log('release.vault-note', 'skipped', { version, reason: 'no-vault' });
|
|
96
|
+
return { outcome: 'ok' as const, verdict: 'pass' as const, summary: 'skipped: no-vault', skipped: 'no-vault' };
|
|
97
|
+
}
|
|
98
|
+
const root = deps.instanceRoot ?? effectiveInstanceRoot();
|
|
99
|
+
const record: ReleaseRecord = JSON.parse(readFileSync(join(root, 'release', version, 'release.json'), 'utf8'));
|
|
100
|
+
if (record.version !== version || record.tag !== `v${version}` || !/^[0-9a-f]{8,40}$/i.test(record.sourceCommit))
|
|
101
|
+
throw new Error('invalid release.json version, tag or sourceCommit');
|
|
102
|
+
const publicRepo = record.publicRepo;
|
|
103
|
+
if (typeof publicRepo !== 'string' || !/^[\w.-]+\/[\w.-]+$/.test(publicRepo)) throw new Error('release.json publicRepo missing or invalid');
|
|
104
|
+
const published = publishedTime(record.publishedAt);
|
|
105
|
+
const draft = readFileSync(join(root, 'release', version, 'prepared', 'notes-draft.md'), 'utf8');
|
|
106
|
+
const items = (deps.checklist ?? ((v) => listChecklist(v).items))(version).filter((item) => item.status === 'green');
|
|
107
|
+
const oneLine = items.slice(0, 3).map((item) => firstClause(item.title)).join(' · ');
|
|
108
|
+
const location = process.env.ELANOUS_GRAPH_CONTEXT;
|
|
109
|
+
const identity = location && !('runId' in context)
|
|
110
|
+
? JSON.parse(location.trimStart().startsWith('{') ? location : readFileSync(location, 'utf8')) as { graphId?: string; runId?: string }
|
|
111
|
+
: {};
|
|
112
|
+
const history = releaseHistory({ ...identity, ...context }, root);
|
|
113
|
+
// OP 10-04 19:10 ①: group by feature family (cell id family: HQ-FENCE2 → HQ · LOOP-OBS1 → LOOP · K10i → K), not by seat.
|
|
114
|
+
const groups = new Map<string, typeof items>();
|
|
115
|
+
for (const item of items) {
|
|
116
|
+
const heading = featureFamily(item.id);
|
|
117
|
+
if (!groups.has(heading)) groups.set(heading, []);
|
|
118
|
+
groups.get(heading)!.push(item);
|
|
119
|
+
}
|
|
120
|
+
const abilities = [...groups].flatMap(([heading, group]) => [
|
|
121
|
+
`### ${heading}`, '',
|
|
122
|
+
...group.flatMap((item) => [
|
|
123
|
+
`- ${item.id} — ${firstClause(item.title)}`,
|
|
124
|
+
...[...item.title.matchAll(/`([^`]+)`/g)].map((match) => ` - 명령: \`${match[1]}\``),
|
|
125
|
+
'',
|
|
126
|
+
]),
|
|
127
|
+
]);
|
|
128
|
+
const target = join(vault, directory);
|
|
129
|
+
const name = `엘라누스 v${version} (${published.date})`;
|
|
130
|
+
// 대표 10-04 19:25: notes live under <major>.x/<major>.<minor>/ · the index stays at the top.
|
|
131
|
+
const [major, minor] = version.split('.');
|
|
132
|
+
const noteDir = join(target, `${major}.x`, `${major}.${minor}`);
|
|
133
|
+
const note = join(noteDir, `${name}.md`);
|
|
134
|
+
const indexPath = join(target, indexName);
|
|
135
|
+
const current = existsSync(indexPath) ? readFileSync(indexPath, 'utf8') : '# 엘라누스 릴리스\n\n| 판 | 발행 | 한 줄 |\n|---|---|---|\n';
|
|
136
|
+
const row = `| [[${name}\\|v${version}]] | ${published.short} | ${oneLine.replace(/\|/g, '\\|')} |`;
|
|
137
|
+
const rowExists = current.split(/\r?\n/).some((line) => line.startsWith('| [[') && line.includes(`|v${version}]]`));
|
|
138
|
+
const header = /\| 판 \| 발행 \| 한 줄 \|\r?\n\|[- :|]+\|\r?\n/;
|
|
139
|
+
if (!rowExists && !header.test(current)) throw new Error(`index table header missing: ${indexPath}`);
|
|
140
|
+
mkdirSync(noteDir, { recursive: true });
|
|
141
|
+
const content = [
|
|
142
|
+
'---', `title: "엘라누스 v${version} — ${oneLine.replace(/"/g, '\\"')}"`,
|
|
143
|
+
`version: ${version}`, `published: ${published.full}`, `cut: ${record.sourceCommit.slice(0, 8)}`,
|
|
144
|
+
'tags: [elanous, release]', 'links:', ` github: https://github.com/${publicRepo}/releases/tag/${record.tag}`,
|
|
145
|
+
` npm: elanous@${version}`, '---', '', `# 엘라누스 v${version} — ${oneLine}`, '',
|
|
146
|
+
'## 한 줄', '', oneLine, '',
|
|
147
|
+
'## 발행 경과', '', `- ${published.full} 발행`, ...history, '',
|
|
148
|
+
'## 이제 할 수 있는 것', '', ...abilities, '## 숫자', '',
|
|
149
|
+
`- 변경 ${changeCount(draft)}건`, `- green 칸 ${items.length}개`, '',
|
|
150
|
+
].join('\n');
|
|
151
|
+
// A handwritten note at the old flat location (e.g. OP's v0.2.13 sample) counts as existing — never duplicate it.
|
|
152
|
+
let existed = existsSync(join(target, `${name}.md`));
|
|
153
|
+
if (!existed) {
|
|
154
|
+
try { writeFileSync(note, content, { flag: 'wx' }); }
|
|
155
|
+
catch (error) {
|
|
156
|
+
if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;
|
|
157
|
+
existed = true;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (!rowExists) {
|
|
161
|
+
const tableHeader = header.exec(current)!;
|
|
162
|
+
const tableStart = tableHeader.index + tableHeader[0].length;
|
|
163
|
+
let insertAt = tableStart;
|
|
164
|
+
const publishedKey = `${published.date} ${published.short.slice(6)}`;
|
|
165
|
+
const rows = current.slice(tableStart).matchAll(/^\| \[\[.*?\((\d{4}-\d{2}-\d{2})\)\\?\|v([^\]]+)\]\] \| (\d{2}-\d{2} \d{2}:\d{2}) \|[^\r\n]*(?:\r?\n|$)/gm);
|
|
166
|
+
for (const existing of rows) {
|
|
167
|
+
if (existing.index !== insertAt - tableStart) break;
|
|
168
|
+
const existingKey = `${existing[1]} ${existing[3]!.slice(6)}`;
|
|
169
|
+
if (existingKey < publishedKey || (existingKey === publishedKey && existing[2]!.localeCompare(version, 'en', { numeric: true }) < 0)) break;
|
|
170
|
+
insertAt += existing[0].length;
|
|
171
|
+
}
|
|
172
|
+
const newline = tableHeader[0].includes('\r\n') ? '\r\n' : '\n';
|
|
173
|
+
const separator = insertAt === current.length && !current.endsWith('\n') ? newline : '';
|
|
174
|
+
writeFileSync(indexPath, `${current.slice(0, insertAt)}${separator}${row}${newline}${current.slice(insertAt)}`);
|
|
175
|
+
}
|
|
176
|
+
debug.log('release.vault-note', existed ? 'skipped' : 'written', { version, reason: existed ? 'exists' : 'created' });
|
|
177
|
+
return { outcome: 'ok' as const, verdict: 'pass' as const, summary: existed ? 'skipped: exists' : `vault note written: ${name}`, ...(existed ? { skipped: 'exists' } : {}) };
|
|
178
|
+
} catch (error) {
|
|
179
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
180
|
+
debug.log('release.vault-note', 'failed', { version, reason });
|
|
181
|
+
return { outcome: 'fail' as const, verdict: 'fail' as const, summary: `vault note failed: ${reason}`, reason };
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (import.meta.main) {
|
|
186
|
+
let version = '';
|
|
187
|
+
try {
|
|
188
|
+
const context = readGraphContext();
|
|
189
|
+
version = context.input.version;
|
|
190
|
+
process.exitCode = finishNode('vault-note', version, runVaultNote(context));
|
|
191
|
+
} catch (error) {
|
|
192
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
193
|
+
debug.log('release.vault-note', 'failed', { version, reason });
|
|
194
|
+
process.exitCode = finishNode('vault-note', version, { outcome: 'fail', verdict: 'fail', summary: `vault note failed: ${reason}`, reason });
|
|
195
|
+
}
|
|
196
|
+
}
|