elanous 0.2.13 → 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/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/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-f14987d5850a31e0.js → page-1abfe3aec3be6bdc.js} +1 -1
- 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-15374909d8f1aebd.js +1 -0
- 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-66c27608f651b866.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-78b40a344ed22f24.js → page-e1e47d8d940aba3d.js} +1 -1
- 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-63d14a4d8fe1e1c1.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/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 +1 -1
- package/scripts/audit-test-state-writes.ts +72 -17
- package/scripts/brand/check.ts +186 -0
- package/scripts/coord-post.sh +11 -1
- 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/standby-verify.ts +11 -7
- package/scripts/lessons/handbook-page.ts +85 -0
- package/scripts/release-loop/gate-node.ts +9 -0
- 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 +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-mission/driver.ts +3 -3
- package/src/autopilot/build/llm-conflict-merge.ts +45 -8
- package/src/autopilot/build/merge-intent.ts +53 -0
- 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 +35 -3
- 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/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 +144 -21
- package/src/cli/rfc-status.ts +91 -0
- package/src/connectors/linear.ts +5 -3
- package/src/context-bus/condense.ts +15 -20
- package/src/context-bus/context-now-public.ts +86 -0
- package/src/context-bus/context-now-surfaces.ts +55 -2
- package/src/context-bus/context-now.ts +4 -3
- 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-ledger.ts +40 -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/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 +152 -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 +3 -3
- package/src/harness/harness-queue.ts +216 -28
- package/src/harness/helper-scan.ts +139 -0
- package/src/harness/model-input-observation.ts +37 -0
- package/src/hq/hq.ts +1 -1
- package/src/hq/promote.ts +301 -0
- package/src/index.ts +83 -21
- package/src/intake-plane/linear-wish.ts +39 -11
- 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 +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 +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 +51 -12
- package/src/loops/orchestrator/resync-shadow.ts +227 -0
- package/src/loops/orchestrator/tick.ts +302 -0
- package/src/loops/orchestrator/traffic.ts +18 -3
- package/src/loops/registry.ts +454 -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 +7 -4
- package/src/nexus/api/dashboard.ts +6 -1
- package/src/nexus/api/http-server.ts +18 -2
- package/src/nexus/api/merge-approvals-gate.ts +1 -1
- package/src/nexus/api/schedules-read.ts +5 -1
- package/src/nexus/api/task-cards-api.ts +44 -5
- package/src/nexus/api/workflows.ts +2 -1
- 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/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 +16 -3
- package/src/seat-dispatch/seat-questions.ts +58 -0
- package/src/seat-loop/seat-loop.ts +406 -31
- 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/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/task-cards/card-cli.ts +10 -2
- package/src/task-cards/card-store.ts +10 -6
- 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/self-implement-pod.ts +80 -16
- package/src/telegram-commands.ts +27 -3
- package/src/telegram-loops-command.ts +44 -0
- package/src/user-config.ts +120 -16
- package/src/version/packed-revision.json +1 -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/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/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/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 → x6mjI6vqOkVVu0jFuDkz1}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { appendFileSync, closeSync, copyFileSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, rmdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { basename, dirname, join, relative, resolve, sep } from 'node:path';
|
|
4
|
+
import { elanousStateRoot } from '../autopilot/state-paths.js';
|
|
5
|
+
import { debug } from '../debug/log.js';
|
|
6
|
+
import { isGoalAuthorFileName } from './goal-document.js';
|
|
7
|
+
import { hasTerminalRunStatus, loadRunLedger, queryUnfinishedRunLedgers, runLedgerDir } from './run-ledger.js';
|
|
8
|
+
|
|
9
|
+
export interface GoalArchiveOptions {
|
|
10
|
+
repoRoot?: string;
|
|
11
|
+
stateRoot?: string;
|
|
12
|
+
before?: string;
|
|
13
|
+
apply?: boolean;
|
|
14
|
+
trustDocRecord?: boolean;
|
|
15
|
+
/** Failure-injection seam for the two write stages (copy · index); defaults use the real filesystem.
|
|
16
|
+
* The repository is never written: removing the originals is a human PR from `filesForPr`. */
|
|
17
|
+
writes?: {
|
|
18
|
+
copy?: (source: string, destination: string) => void;
|
|
19
|
+
appendIndex?: (path: string, line: string) => void;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface GoalArchiveEntry {
|
|
24
|
+
goalId: string;
|
|
25
|
+
runId: string;
|
|
26
|
+
pr: number | null;
|
|
27
|
+
outcome: 'merged' | 'abandoned' | 'superseded';
|
|
28
|
+
source: 'ledger' | 'doc-record';
|
|
29
|
+
originalPath: string;
|
|
30
|
+
archivePath: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface GoalArchiveResult {
|
|
34
|
+
candidates: GoalArchiveEntry[];
|
|
35
|
+
applied: GoalArchiveEntry[];
|
|
36
|
+
skipped: Record<string, number>;
|
|
37
|
+
trustDocRecordAdds: number;
|
|
38
|
+
filesForPr: string[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function gitRead(root: string, ...args: string[]): string {
|
|
42
|
+
return execFileSync('git', args, {
|
|
43
|
+
cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'],
|
|
44
|
+
env: { ...process.env, GIT_OPTIONAL_LOCKS: '0' },
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function applyArchiveEntry(index: string, source: string,
|
|
49
|
+
entry: GoalArchiveEntry, document: string, writes: GoalArchiveOptions['writes']): void {
|
|
50
|
+
const directory = dirname(entry.archivePath);
|
|
51
|
+
const hadDirectory = existsSync(directory);
|
|
52
|
+
const priorIndex = existsSync(index) ? readFileSync(index) : null;
|
|
53
|
+
let ownedCopy = false;
|
|
54
|
+
let indexAttempted = false;
|
|
55
|
+
try {
|
|
56
|
+
mkdirSync(directory, { recursive: true });
|
|
57
|
+
const reservation = openSync(entry.archivePath, 'wx');
|
|
58
|
+
ownedCopy = true;
|
|
59
|
+
closeSync(reservation);
|
|
60
|
+
(writes?.copy ?? copyFileSync)(source, entry.archivePath);
|
|
61
|
+
if (readFileSync(entry.archivePath, 'utf8') !== document) throw new Error(`archive copy mismatch: ${entry.originalPath}`);
|
|
62
|
+
indexAttempted = true;
|
|
63
|
+
(writes?.appendIndex ?? ((path, line) => appendFileSync(path, line, 'utf8')))(index, `${JSON.stringify(entry)}\n`);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
try {
|
|
66
|
+
if (indexAttempted) {
|
|
67
|
+
if (priorIndex === null) rmSync(index, { force: true });
|
|
68
|
+
else if (!readFileSync(index).equals(priorIndex)) writeFileSync(index, priorIndex);
|
|
69
|
+
}
|
|
70
|
+
if (ownedCopy) rmSync(entry.archivePath, { force: true });
|
|
71
|
+
if (ownedCopy && !hadDirectory && existsSync(directory)) rmdirSync(directory);
|
|
72
|
+
} catch (rollbackError) {
|
|
73
|
+
throw new Error(`archive failed for ${entry.originalPath}: ${String(error)}; recovery required (archive rollback: ${String(rollbackError)}). Preserve ${entry.archivePath} and ${index}; reconcile the index before retry.`);
|
|
74
|
+
}
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function beforeDate(value: string | undefined): string | undefined {
|
|
80
|
+
if (value === undefined) return undefined;
|
|
81
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(value) || Number.isNaN(Date.parse(`${value}T00:00:00Z`))
|
|
82
|
+
|| new Date(`${value}T00:00:00Z`).toISOString().slice(0, 10) !== value) {
|
|
83
|
+
throw new Error(`--before requires a valid YYYY-MM-DD date: ${value}`);
|
|
84
|
+
}
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function ledgerGoalFileContradicts(goalFile: unknown, originalPath: string): boolean {
|
|
89
|
+
if (typeof goalFile !== 'string' || !goalFile.trim()) return false;
|
|
90
|
+
const normalized = goalFile.replaceAll('\\', '/');
|
|
91
|
+
return normalized !== originalPath && !normalized.endsWith(`/${originalPath}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function lastExecution(document: string): Record<string, string> | null {
|
|
95
|
+
const sections = document.split(/^## 실행 기록\s*$/m).slice(1);
|
|
96
|
+
const block = sections.at(-1)?.split(/^#{1,2} /m, 1)[0];
|
|
97
|
+
const run = block?.split(/^- runId: /m).slice(1).at(-1);
|
|
98
|
+
if (!run) return null;
|
|
99
|
+
const id = run.split(/\r?\n/, 1)[0]?.trim();
|
|
100
|
+
if (!id) return null;
|
|
101
|
+
const fields: Record<string, string> = { runId: id };
|
|
102
|
+
for (const [, key, value] of run.matchAll(/^ (stage|outcome|prNumber|completedAt): ([^\r\n]*)$/gm)) fields[key!] = value!.trim();
|
|
103
|
+
return fields;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Preview by default. --apply only copies to the state archive and indexes; it never removes or stages anything in the repository —
|
|
107
|
+
* `filesForPr` is the list a human removes in one PR. A missing ledger requires explicit trust in the document's finished-run record. */
|
|
108
|
+
export function archiveGoals(options: GoalArchiveOptions = {}): GoalArchiveResult {
|
|
109
|
+
const root = resolve(options.repoRoot ?? gitRead(process.cwd(), 'rev-parse', '--show-toplevel').trim());
|
|
110
|
+
const state = resolve(options.stateRoot ?? elanousStateRoot());
|
|
111
|
+
const cutoff = beforeDate(options.before);
|
|
112
|
+
const goals = join(root, 'docs', 'goals');
|
|
113
|
+
const archive = join(state, 'goal-archive');
|
|
114
|
+
const index = join(archive, 'index.jsonl');
|
|
115
|
+
const result: GoalArchiveResult = { candidates: [], applied: [], skipped: {}, trustDocRecordAdds: 0, filesForPr: [] };
|
|
116
|
+
const skip = (reason: string): void => { result.skipped[reason] = (result.skipped[reason] ?? 0) + 1; };
|
|
117
|
+
const lock = join(archive, '.apply.lock');
|
|
118
|
+
if (options.apply) {
|
|
119
|
+
mkdirSync(archive, { recursive: true });
|
|
120
|
+
try { mkdirSync(lock); }
|
|
121
|
+
catch (error) {
|
|
122
|
+
if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;
|
|
123
|
+
throw new Error(`EEXIST: goal archive --apply is already running or a previous run stopped mid-way (${lock}). `
|
|
124
|
+
+ `If no other archive is running: compare ${index} with ${archive}/<YYYY-MM>/ copies, drop index lines whose copy is missing, then remove ${lock} and retry.`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
const tracked = new Set(gitRead(root, 'ls-files', '-z', '--', 'docs/goals').split('\0').filter(Boolean));
|
|
129
|
+
const pendingRemovals = new Set(gitRead(root, 'diff', '--cached', '--name-only', '--diff-filter=D', '-z', '--', 'docs/goals').split('\0').filter(Boolean));
|
|
130
|
+
const unfinished = queryUnfinishedRunLedgers({ dir: runLedgerDir(state), goalsDir: goals, noCache: true });
|
|
131
|
+
if (unfinished.unreadableLedgerCount > 0) throw new Error('run ledger contains unreadable runs; archive refused');
|
|
132
|
+
const activePaths = new Set(unfinished.entries.filter((entry) => entry.goalDocumentPath)
|
|
133
|
+
.map((entry) => resolve(root, entry.goalDocumentPath!)));
|
|
134
|
+
const activeRunIds = new Set(unfinished.entries.map((entry) => entry.runId));
|
|
135
|
+
const indexed = new Set<string>();
|
|
136
|
+
if (existsSync(index)) {
|
|
137
|
+
for (const line of readFileSync(index, 'utf8').split('\n').filter(Boolean)) {
|
|
138
|
+
const entry = JSON.parse(line) as { originalPath: string };
|
|
139
|
+
indexed.add(entry.originalPath);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
let names: string[];
|
|
143
|
+
try { names = readdirSync(goals).sort(); }
|
|
144
|
+
catch (error) {
|
|
145
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
|
|
146
|
+
names = [];
|
|
147
|
+
}
|
|
148
|
+
for (const name of names) {
|
|
149
|
+
if (!isGoalAuthorFileName(name)) continue;
|
|
150
|
+
const path = join(goals, name);
|
|
151
|
+
const originalPath = relative(root, path).split(sep).join('/');
|
|
152
|
+
if (!tracked.has(originalPath) || pendingRemovals.has(originalPath)) { skip('untracked'); continue; }
|
|
153
|
+
const fileStat = lstatSync(path, { throwIfNoEntry: false });
|
|
154
|
+
if (!fileStat?.isFile() || fileStat.isSymbolicLink()) { skip('not-file'); continue; }
|
|
155
|
+
if (indexed.has(originalPath)) { skip('already-indexed'); continue; }
|
|
156
|
+
if (activePaths.has(path)) { skip('active-goal-run'); continue; }
|
|
157
|
+
const document = readFileSync(path, 'utf8');
|
|
158
|
+
const fields = lastExecution(document);
|
|
159
|
+
if (!fields) { skip('no-execution-record'); continue; }
|
|
160
|
+
const runId = fields.runId!;
|
|
161
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(runId)) { skip('invalid-run-id'); continue; }
|
|
162
|
+
if (activeRunIds.has(runId)) { skip('active-goal-run'); continue; }
|
|
163
|
+
const outcome = fields.stage === 'merged' && (fields.outcome === 'completed' || fields.outcome === 'merged') ? 'merged'
|
|
164
|
+
: fields.outcome === 'abandoned' ? 'abandoned'
|
|
165
|
+
: fields.outcome === 'superseded' ? 'superseded' : null;
|
|
166
|
+
if (!outcome) { skip('unfinished'); continue; }
|
|
167
|
+
let ledger;
|
|
168
|
+
try { ledger = loadRunLedger(runId, runLedgerDir(state)); }
|
|
169
|
+
catch (error) {
|
|
170
|
+
skip(error instanceof Error && /runId does not match /.test(error.message) ? 'ledger-run-id-mismatch' : 'unreadable-ledger');
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const missingLedger = !ledger?.length;
|
|
174
|
+
if (missingLedger && (!fields.stage || !fields.outcome)) { skip('unfinished'); continue; }
|
|
175
|
+
const documentGoalId = /^- GoalId:\s*([0-9a-f]{16})\s*$/m.exec(document.split(/^## 실행 기록\s*$/m, 1)[0] ?? '')?.[1];
|
|
176
|
+
if (!documentGoalId) { skip('missing-goal-id'); continue; }
|
|
177
|
+
if (!missingLedger) {
|
|
178
|
+
const entries = ledger!;
|
|
179
|
+
const ledgerGoalIds = new Set(entries.map((entry) => entry.goalId).filter((id): id is string => typeof id === 'string'));
|
|
180
|
+
if (ledgerGoalIds.size !== 1 || !ledgerGoalIds.has(documentGoalId)) { skip('ledger-goal-id-mismatch'); continue; }
|
|
181
|
+
const starts = entries.filter((entry) => entry.event === 'start');
|
|
182
|
+
if (starts.length === 0 || starts.some((entry) => ledgerGoalFileContradicts(entry.data.goalFile, originalPath))) {
|
|
183
|
+
skip('ledger-goal-mismatch'); continue;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const latest = ledger?.filter((entry) => entry.event === 'run-status').at(-1);
|
|
187
|
+
if (!missingLedger) {
|
|
188
|
+
if (!latest || !hasTerminalRunStatus(ledger!) || !['completed', 'failed', 'cancelled'].includes(String(latest.data.runStatus))) {
|
|
189
|
+
skip('running-or-unconfirmed'); continue;
|
|
190
|
+
}
|
|
191
|
+
if (outcome === 'merged' && (latest.data.runStatus !== 'completed' || (latest.data.stage !== undefined && latest.data.stage !== 'merged'))
|
|
192
|
+
|| outcome === 'abandoned' && latest.data.runStatus === 'completed'
|
|
193
|
+
|| outcome === 'superseded' && latest.data.runStatus === 'failed') {
|
|
194
|
+
skip('status-mismatch'); continue;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
const completedAt = missingLedger ? fields.completedAt : fields.completedAt ?? latest?.timestamp;
|
|
198
|
+
if (!completedAt || !Number.isFinite(Date.parse(completedAt))) { skip('no-completion-date'); continue; }
|
|
199
|
+
const date = new Date(completedAt).toISOString().slice(0, 10);
|
|
200
|
+
if (cutoff && date >= cutoff) { skip('after-before'); continue; }
|
|
201
|
+
const prRaw = fields.prNumber;
|
|
202
|
+
const pr = prRaw && /^[1-9]\d*$/.test(prRaw) && Number.isSafeInteger(Number(prRaw)) ? Number(prRaw) : null;
|
|
203
|
+
const archivePath = join(archive, date.slice(0, 7), basename(path));
|
|
204
|
+
if (existsSync(archivePath)) { skip('archive-collision'); continue; }
|
|
205
|
+
if (gitRead(root, 'status', '--porcelain', '--', originalPath).trim()) { skip('dirty'); continue; }
|
|
206
|
+
if (missingLedger && !options.trustDocRecord) { skip('missing-ledger'); result.trustDocRecordAdds += 1; continue; }
|
|
207
|
+
const entry: GoalArchiveEntry = { goalId: documentGoalId, runId, pr, outcome, source: missingLedger ? 'doc-record' : 'ledger', originalPath, archivePath };
|
|
208
|
+
result.candidates.push(entry);
|
|
209
|
+
if (options.apply) {
|
|
210
|
+
applyArchiveEntry(index, path, entry, document, options.writes);
|
|
211
|
+
result.applied.push(entry);
|
|
212
|
+
result.filesForPr.push(originalPath);
|
|
213
|
+
} else {
|
|
214
|
+
result.filesForPr.push(originalPath);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
try { debug.log('harness.goal', 'archive', { candidates: result.candidates.length, applied: result.applied.length, skipped: result.skipped }); } catch { /* observation is fail-soft */ }
|
|
218
|
+
return result;
|
|
219
|
+
} finally {
|
|
220
|
+
if (options.apply) rmdirSync(lock);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { readFileSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { join, resolve } from 'node:path';
|
|
3
|
+
import { selfDevRunsDir, type SelfDevRunState } from '../self-dev/run-store.js';
|
|
4
|
+
import { isGoalAuthorFileName } from './goal-document.js';
|
|
5
|
+
|
|
6
|
+
export interface GoalLookupOptions {
|
|
7
|
+
/** Defaults to the current repository's docs/goals directory. */
|
|
8
|
+
goalsDir?: string;
|
|
9
|
+
/** Defaults to the current instance's self-dev-runs directory. */
|
|
10
|
+
runsDir?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface GoalLookupRecord {
|
|
14
|
+
runId: string;
|
|
15
|
+
stage: string | null;
|
|
16
|
+
outcome: string | null;
|
|
17
|
+
prNumber: number | null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** The two sources are independent: null means that side did not match. */
|
|
21
|
+
export interface GoalLookupResult {
|
|
22
|
+
goalBody: string | null;
|
|
23
|
+
goalFile: string | null;
|
|
24
|
+
kanId: string | null;
|
|
25
|
+
runId: string | null;
|
|
26
|
+
record: GoalLookupRecord | null;
|
|
27
|
+
runState: SelfDevRunState | null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function filesIn(directory: string): string[] {
|
|
31
|
+
try {
|
|
32
|
+
return readdirSync(directory, { withFileTypes: true })
|
|
33
|
+
.filter((entry) => entry.isFile())
|
|
34
|
+
.map((entry) => entry.name)
|
|
35
|
+
.sort();
|
|
36
|
+
} catch (error) {
|
|
37
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return [];
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type ValidResult = { runId: string; stage?: string | null; prNumber?: number | null };
|
|
43
|
+
|
|
44
|
+
function isValidResult(result: unknown): result is ValidResult {
|
|
45
|
+
if (result === null || typeof result !== 'object' || Array.isArray(result)) return false;
|
|
46
|
+
const r = result as { runId?: unknown; stage?: unknown; prNumber?: unknown };
|
|
47
|
+
return typeof r.runId === 'string' && r.runId.length > 0
|
|
48
|
+
&& (r.stage == null || typeof r.stage === 'string')
|
|
49
|
+
&& (r.prNumber == null || (typeof r.prNumber === 'number' && Number.isSafeInteger(r.prNumber) && r.prNumber > 0));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function prNumber(value: string | undefined): number | null {
|
|
53
|
+
if (!value || !/^[1-9]\d*$/.test(value)) return null;
|
|
54
|
+
const number = Number(value);
|
|
55
|
+
return Number.isSafeInteger(number) ? number : null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function executionRecords(document: string): GoalLookupRecord[] {
|
|
59
|
+
const sections = document.split(/^## 실행 기록\s*$/m).slice(1);
|
|
60
|
+
return sections.flatMap((section) => {
|
|
61
|
+
const block = section.split(/^#{1,2} /m, 1)[0] ?? '';
|
|
62
|
+
const entries = block.split(/^- runId: /m).slice(1);
|
|
63
|
+
return entries.flatMap((entry) => {
|
|
64
|
+
const runId = entry.split(/\r?\n/, 1)[0]?.trim();
|
|
65
|
+
if (!runId) return [];
|
|
66
|
+
const field = (name: string): string | undefined =>
|
|
67
|
+
new RegExp(`^ ${name}: ([^\\r\\n]*)$`, 'm').exec(entry)?.[1]?.trim();
|
|
68
|
+
return [{ runId, stage: field('stage') ?? null, outcome: field('outcome') ?? null, prNumber: prNumber(field('prNumber')) }];
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Local, read-only lookup. A bare positive integer (or #number) is a PR; all other input is an exact run ID. */
|
|
74
|
+
export function lookupGoal(reference: string, options: GoalLookupOptions = {}): GoalLookupResult {
|
|
75
|
+
const input = reference.trim();
|
|
76
|
+
const requestedPr = prNumber(input.replace(/^#/, ''));
|
|
77
|
+
const requestedRunId = requestedPr === null ? input : null;
|
|
78
|
+
const runsDir = options.runsDir ?? selfDevRunsDir();
|
|
79
|
+
const findRun = (matches: (result: ValidResult) => boolean, wantedRunId: string | null) => {
|
|
80
|
+
for (const name of filesIn(runsDir)) {
|
|
81
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_-]*\.json$/.test(name)) continue;
|
|
82
|
+
let candidate: SelfDevRunState;
|
|
83
|
+
try {
|
|
84
|
+
candidate = JSON.parse(readFileSync(join(runsDir, name), 'utf8')) as SelfDevRunState;
|
|
85
|
+
} catch { continue; }
|
|
86
|
+
if (!candidate || candidate.runId !== name.slice(0, -'.json'.length) || !Array.isArray(candidate.results)) continue;
|
|
87
|
+
const matchingResult = (candidate.results as unknown[]).find((result): result is ValidResult => isValidResult(result) && matches(result));
|
|
88
|
+
if (matchingResult || (wantedRunId !== null && candidate.runId === wantedRunId)) {
|
|
89
|
+
const runId = matchingResult?.runId ?? candidate.runId;
|
|
90
|
+
const record: GoalLookupRecord | null = matchingResult
|
|
91
|
+
? { runId, stage: matchingResult.stage ?? null, outcome: null, prNumber: matchingResult.prNumber ?? null }
|
|
92
|
+
: null;
|
|
93
|
+
return { runState: candidate, runId, record };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
};
|
|
98
|
+
const found = findRun((result) => requestedPr !== null ? result.prNumber === requestedPr : result.runId === requestedRunId, requestedRunId);
|
|
99
|
+
let runState: SelfDevRunState | null = found?.runState ?? null;
|
|
100
|
+
const resultRunId: string | null = found?.runId ?? null;
|
|
101
|
+
let runRecord: GoalLookupRecord | null = found?.record ?? null;
|
|
102
|
+
|
|
103
|
+
const goalsDir = options.goalsDir ?? resolve(process.cwd(), 'docs', 'goals');
|
|
104
|
+
let fallback: { goalFile: string; document: string; record: GoalLookupRecord } | null = null;
|
|
105
|
+
let selected: { goalFile: string; document: string; record: GoalLookupRecord } | null = null;
|
|
106
|
+
for (const name of filesIn(goalsDir)) {
|
|
107
|
+
if (!isGoalAuthorFileName(name)) continue;
|
|
108
|
+
const goalFile = join(goalsDir, name);
|
|
109
|
+
let document: string;
|
|
110
|
+
try { document = readFileSync(goalFile, 'utf8'); } catch { continue; }
|
|
111
|
+
const records = executionRecords(document);
|
|
112
|
+
const direct = records.find((candidate) => requestedPr !== null
|
|
113
|
+
? candidate.prNumber === requestedPr
|
|
114
|
+
: candidate.runId === requestedRunId);
|
|
115
|
+
if (direct) { selected = { goalFile, document, record: direct }; break; }
|
|
116
|
+
if (requestedPr !== null && resultRunId !== null && fallback === null) {
|
|
117
|
+
const record = records.find((candidate) => candidate.prNumber === null && candidate.runId === resultRunId);
|
|
118
|
+
if (record) fallback = { goalFile, document, record };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const match = selected ?? fallback;
|
|
122
|
+
if (match) {
|
|
123
|
+
const { goalFile, document, record } = match;
|
|
124
|
+
// The returned run state must belong to the selected document record's run (review must-fix): re-resolve, or drop it.
|
|
125
|
+
if (resultRunId !== record.runId) {
|
|
126
|
+
const again = findRun((result) => result.runId === record.runId, record.runId);
|
|
127
|
+
runState = again?.runState ?? null;
|
|
128
|
+
runRecord = again?.record ?? null;
|
|
129
|
+
}
|
|
130
|
+
const body = document.split(/^## 실행 기록\s*$/m, 1)[0]!.replace(/\r?\n$/, '');
|
|
131
|
+
// A kan ID is only reported when the authored document explicitly names one.
|
|
132
|
+
const kanId = /^- Kan(?:Id|-ID):\s*(\S+)\s*$/im.exec(body)?.[1] ?? null;
|
|
133
|
+
return {
|
|
134
|
+
goalBody: body, goalFile, kanId, runId: record.runId,
|
|
135
|
+
record: {
|
|
136
|
+
...record,
|
|
137
|
+
...(runRecord?.runId === record.runId ? {
|
|
138
|
+
stage: record.stage ?? runRecord.stage,
|
|
139
|
+
outcome: record.outcome ?? runRecord.outcome,
|
|
140
|
+
prNumber: record.prNumber ?? runRecord.prNumber,
|
|
141
|
+
} : {}),
|
|
142
|
+
},
|
|
143
|
+
runState,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
return { goalBody: null, goalFile: null, kanId: null, runId: resultRunId ?? requestedRunId, record: runRecord, runState };
|
|
147
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { appendFileSync, mkdirSync, readFileSync, rmdirSync, statSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
4
|
+
import { debug } from '../debug/log.js';
|
|
5
|
+
|
|
6
|
+
export type FailureSource = 'release-run' | 'harness-run' | 'loop-tick' | 'cron';
|
|
7
|
+
|
|
8
|
+
export interface FailureEvent {
|
|
9
|
+
source: FailureSource;
|
|
10
|
+
kind: string;
|
|
11
|
+
ref: string;
|
|
12
|
+
summary: string;
|
|
13
|
+
at: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const FOLD_WINDOW_MS = 60 * 60 * 1_000;
|
|
17
|
+
|
|
18
|
+
function inboxPath(root: string): string {
|
|
19
|
+
return join(root, 'heal', 'inbox.jsonl');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function readInbox(file: string): FailureEvent[] {
|
|
23
|
+
let content: string;
|
|
24
|
+
try { content = readFileSync(file, 'utf8'); }
|
|
25
|
+
catch (error) {
|
|
26
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return [];
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
return content.split('\n').filter(Boolean).map(line => JSON.parse(line) as FailureEvent);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Lock the read/fold/append transaction across processes, not just within one runner. */
|
|
33
|
+
function withInboxLock<T>(file: string, work: () => T): T {
|
|
34
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
35
|
+
const lock = `${file}.lock`;
|
|
36
|
+
const deadline = Date.now() + 5_000;
|
|
37
|
+
for (;;) {
|
|
38
|
+
try { mkdirSync(lock); break; }
|
|
39
|
+
catch (error) {
|
|
40
|
+
if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;
|
|
41
|
+
try {
|
|
42
|
+
if (Date.now() - statSync(lock).mtimeMs > 60_000) {
|
|
43
|
+
rmdirSync(lock);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
} catch (cause) {
|
|
47
|
+
if ((cause as NodeJS.ErrnoException).code !== 'ENOENT') throw cause;
|
|
48
|
+
}
|
|
49
|
+
if (Date.now() >= deadline) throw new Error(`heal inbox lock is held: ${file}`);
|
|
50
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
try { return work(); }
|
|
54
|
+
finally { rmdirSync(lock); }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** One durable JSON line per failure; repeat source+ref within an hour is folded. */
|
|
58
|
+
export function recordFailureEvent(event: FailureEvent, root = effectiveInstanceRoot()): { folded: boolean } {
|
|
59
|
+
if (!['release-run', 'harness-run', 'loop-tick', 'cron'].includes(event.source) ||
|
|
60
|
+
!event.kind || !event.ref || !event.summary || !Number.isFinite(Date.parse(event.at))) {
|
|
61
|
+
throw new Error('invalid heal failure event');
|
|
62
|
+
}
|
|
63
|
+
const file = inboxPath(root);
|
|
64
|
+
const folded = withInboxLock(file, () => {
|
|
65
|
+
const incoming = Date.parse(event.at);
|
|
66
|
+
if (readInbox(file).some(previous => previous.source === event.source && previous.ref === event.ref &&
|
|
67
|
+
incoming - Date.parse(previous.at) >= 0 && incoming - Date.parse(previous.at) < FOLD_WINDOW_MS)) return true;
|
|
68
|
+
appendFileSync(file, JSON.stringify(event) + '\n');
|
|
69
|
+
return false;
|
|
70
|
+
});
|
|
71
|
+
debug.log('heal.intake', 'recorded', { source: event.source, kind: event.kind, ref: event.ref, folded });
|
|
72
|
+
return { folded };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function readFailureInbox({ since }: { since?: string } = {}, root = effectiveInstanceRoot()): FailureEvent[] {
|
|
76
|
+
const threshold = since === undefined ? -Infinity : Date.parse(since);
|
|
77
|
+
if (Number.isNaN(threshold)) throw new Error('invalid heal inbox since');
|
|
78
|
+
return readInbox(inboxPath(root)).filter(event => Date.parse(event.at) >= threshold);
|
|
79
|
+
}
|
|
@@ -3,6 +3,9 @@ import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync,
|
|
|
3
3
|
import { tmpdir } from 'node:os';
|
|
4
4
|
import { dirname, join } from 'node:path';
|
|
5
5
|
import { debug } from '../debug/log.js';
|
|
6
|
+
import { getUserConfig } from '../user-config.js';
|
|
7
|
+
import { publicExposureFiles, runExposeGate } from './expose-gate.js';
|
|
8
|
+
import { admitLandingMerge } from './frozen-merges.js';
|
|
6
9
|
import { releaseGitDiffPaths, releasePathHold, releasePathHoldShouldPost, releasePathHoldComment, releasePathHoldCommentsArgs, RELEASE_PATH_LABEL } from '../self-dev/release-path-guard.js';
|
|
7
10
|
import { detectTestInterference, parseFailureCount, runBunTest } from '../../scripts/detect-test-interference.js';
|
|
8
11
|
import { runTestInterferenceGate } from '../../scripts/ci-test-interference-gate.js';
|
|
@@ -17,8 +20,8 @@ export function needsPwaBuild(files: readonly string[]): boolean {
|
|
|
17
20
|
return files.some((file) => file.startsWith('apps/pwa/') || (file.startsWith('src/') && !/\.test\.tsx?$/.test(file)));
|
|
18
21
|
}
|
|
19
22
|
|
|
20
|
-
export interface HostRegateInput { prNumber: number; headCommit: string; repoRoot: string; verifyOnly?: boolean }
|
|
21
|
-
export interface HostRegateResult { passed: boolean; failures: Array<{ step: string; detail: string }>; os: string; status?: 'passed' | 'failed' | 'unmeasured'; /** verifyOnly: 실제로 얹어 잰 base 끝 */ baseCommit?: string }
|
|
23
|
+
export interface HostRegateInput { prNumber: number; headCommit: string; repoRoot: string; verifyOnly?: boolean; /** Called by a resume sweep that already holds this PR's claim. */ resumed?: true }
|
|
24
|
+
export interface HostRegateResult { passed: boolean; failures: Array<{ step: string; detail: string }>; os: string; status?: 'passed' | 'failed' | 'unmeasured' | 'frozen'; /** verifyOnly: 실제로 얹어 잰 base 끝 */ baseCommit?: string }
|
|
22
25
|
export type HostRegateDeps = {
|
|
23
26
|
command?: (bin: string, args: readonly string[], cwd: string, env?: NodeJS.ProcessEnv) => { status: number | null; stdout: string; stderr: string };
|
|
24
27
|
interference?: (files: readonly string[], cwd: string) => Promise<{ passed: boolean; detail?: string; unmeasured?: boolean }>;
|
|
@@ -26,6 +29,8 @@ export type HostRegateDeps = {
|
|
|
26
29
|
removeTemp?: (path: string) => void;
|
|
27
30
|
acquire?: (repoRoot: string) => Promise<() => void>;
|
|
28
31
|
log?: (event: 'passed' | 'failed' | 'unmeasured' | 'base-raced', data: Record<string, unknown>) => void;
|
|
32
|
+
freezeRoot?: string;
|
|
33
|
+
runExposeGate?: typeof runExposeGate;
|
|
29
34
|
};
|
|
30
35
|
|
|
31
36
|
const SPAWN_MAX_BUFFER = 256 * 1024 * 1024;
|
|
@@ -122,6 +127,8 @@ export async function runHostRegate(input: HostRegateInput, deps: HostRegateDeps
|
|
|
122
127
|
let worktree: string | undefined;
|
|
123
128
|
let attached = false;
|
|
124
129
|
let release: (() => void) | undefined;
|
|
130
|
+
let endLanding: ((merged?: boolean) => void) | undefined;
|
|
131
|
+
let landed = false;
|
|
125
132
|
let verifiedBase: string | undefined;
|
|
126
133
|
const result = (event: 'passed' | 'failed' | 'unmeasured', step?: string, detail?: string): HostRegateResult => {
|
|
127
134
|
if (step) failures.push({ step, detail: detail ?? 'unknown' });
|
|
@@ -203,6 +210,13 @@ export async function runHostRegate(input: HostRegateInput, deps: HostRegateDeps
|
|
|
203
210
|
const interference = await (deps.interference ?? ((f: readonly string[], c: string) => defaultInterference(f, c, { neighbors: !input.verifyOnly })))(files, worktree);
|
|
204
211
|
if (!interference.passed) return result(interference.unmeasured ? 'unmeasured' : 'failed', 'test-interference', interference.detail);
|
|
205
212
|
} catch (e) { return result('unmeasured', 'test-interference', String(e)); }
|
|
213
|
+
// Landing can also be initiated by the host after the child gate; judge the checked PR here too.
|
|
214
|
+
if (publicExposureFiles(files).length > 0) {
|
|
215
|
+
const exposure = (deps.runExposeGate ?? runExposeGate)(worktree, files,
|
|
216
|
+
getUserConfig().harness?.exposeGate === 'strict' ? 'strict' : 'warn',
|
|
217
|
+
(bin, args, dir) => command(bin, args, dir));
|
|
218
|
+
if (!exposure.passed) return result('failed', 'expose', exposure.log);
|
|
219
|
+
}
|
|
206
220
|
// verifyOnly: 통합 후보는 «지금 main 끝»(baseCommit)에 얹은 것이다 — 옛 merge-base 로 비교하면 그사이 main 착지분
|
|
207
221
|
// 수백 파일이 «이 PR 의 변경»으로 잡혀 저장소 전체 검사로 승격된다(2026-09-28 #21239 실측: 456파일 · PWA 636건).
|
|
208
222
|
try { run('bun', ['scripts/ci-typecheck-changed.ts'], worktree, { ...process.env, TSC_BASE_REF: input.verifyOnly ? baseCommit : base }); }
|
|
@@ -226,6 +240,13 @@ export async function runHostRegate(input: HostRegateInput, deps: HostRegateDeps
|
|
|
226
240
|
// --match-head-commit pins the head. gh has no base pin, so the base was re-read just
|
|
227
241
|
// above; the seconds between that read and the merge are checked after the fact below.
|
|
228
242
|
if (input.verifyOnly) return result('passed');
|
|
243
|
+
const landing = admitLandingMerge({ prNumber: input.prNumber, headCommit: input.headCommit, repoRoot: input.repoRoot }, deps.freezeRoot, {}, input.resumed ? undefined : { prNumber: input.prNumber, repoRoot: input.repoRoot, headCommit: input.headCommit });
|
|
244
|
+
if (landing.kind !== 'merge') {
|
|
245
|
+
debug.log('harness.merge', 'frozen', { pr: input.prNumber, ...(landing.kind === 'held' ? { reason: landing.freeze.reason, until: landing.freeze.until } : { resumedElsewhere: true }) });
|
|
246
|
+
return { passed: true, failures: [], os: process.platform, status: 'frozen' };
|
|
247
|
+
}
|
|
248
|
+
// The marker and claim are held through the merge «and» its confirmation; released in the outer finally.
|
|
249
|
+
endLanding = landing.end;
|
|
229
250
|
try { run('gh', ['pr', 'merge', String(input.prNumber), '--squash', '--match-head-commit', input.headCommit], input.repoRoot); }
|
|
230
251
|
catch (e) { return result('failed', 'merge', String(e)); }
|
|
231
252
|
type MergedView = { state?: string; mergeCommit?: { oid?: string } | null };
|
|
@@ -233,6 +254,7 @@ export async function runHostRegate(input: HostRegateInput, deps: HostRegateDeps
|
|
|
233
254
|
try { merged = JSON.parse(run('gh', ['pr', 'view', String(input.prNumber), '--json', 'state,mergeCommit'], input.repoRoot)); }
|
|
234
255
|
catch (e) { return result('unmeasured', 'merge-confirm', String(e)); }
|
|
235
256
|
if (merged.state !== 'MERGED') return result('unmeasured', 'merge-confirm', `PR state after merge is ${merged.state ?? 'unknown'}`);
|
|
257
|
+
landed = true;
|
|
236
258
|
const mergeCommit = merged.mergeCommit?.oid ?? '';
|
|
237
259
|
let mergedOnto = '';
|
|
238
260
|
try {
|
|
@@ -249,6 +271,7 @@ export async function runHostRegate(input: HostRegateInput, deps: HostRegateDeps
|
|
|
249
271
|
} catch (e) {
|
|
250
272
|
return result('unmeasured', 'host-regate', String(e));
|
|
251
273
|
} finally {
|
|
274
|
+
try { endLanding?.(landed); } catch (e) { log('unmeasured', { pr: input.prNumber, files, os: process.platform, failedStep: 'landing-release', detail: String(e) }); }
|
|
252
275
|
try { if (attached && worktree) run('git', ['worktree', 'remove', '--force', worktree], input.repoRoot); }
|
|
253
276
|
catch (e) { log('unmeasured', { pr: input.prNumber, files, os: process.platform, failedStep: 'cleanup', detail: String(e) }); }
|
|
254
277
|
try { if (worktree) (deps.removeTemp ?? ((path) => rmSync(path, { recursive: true, force: true })))(worktree); }
|