elanous 0.2.13 → 0.2.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/LZcstaO9_NXypPFmujolv/_buildManifest.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7334.a6679d9168f37aa8.js → 1268.0840ff91e083fcbd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1307-33ffcc49ab45123b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9656-c1540088f5859c80.js → 1844-9ec4b1d12f6e80eb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{390.c373e31b3b9d0c41.js → 1948.15f356b15a625597.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/211-9c9090c59e2c5907.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +1 -0
- package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4554-3565eea1f1495ba7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{4685-c3c1c625b71accd1.js → 4614-21403aac730d4586.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{8039-df92e08fede5de8f.js → 4795-f372490b178583fc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5135-44008fe8b1f2aa41.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5233-bf28bd6bd7a32099.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1452.93700483b9d4fc61.js → 6110.1297d4093fdfbb0d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6158.9c0449a9a8d0064b.js +2 -0
- package/apps/pwa/out/_next/static/chunks/6877-7c6208705c284124.js +1 -0
- package/apps/pwa/out/_next/static/chunks/726.83532d69452ee24b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1829-b453a1b22d3999bb.js → 7327-2d69fb321ba73389.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7395-164e238f471b5d7b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7787-02a1c0b04e25e811.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8827-e2f5b09a8d37e83b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +25 -0
- package/apps/pwa/out/_next/static/chunks/{8421-1c4acfb1395a0edf.js → 8949-ecb4128a0858b911.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/899-46946fe8363e1262.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9103-7a3ae7f60af3b99f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3869-3cd46526879f454c.js → 9247-29d889b809288e65.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/935.4ddaa99fc1d1126c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{79f8be9e-957d68913b590b08.js → a9f2b54d-7865aba0f4d2ee49.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-0e2c57fe6add4b2a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-e05a77df7e005b04.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-f544338193a9a762.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-5491c115b1f9a923.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-b8c49f6b4fa2df91.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-96398e364fbd4548.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-976813c91d7394ea.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-28b9448091776c06.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-1d4901eb7c2d8822.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-72d3e7ff35a8c954.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-60c7a8e1a4fe62ce.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-2a52b316cb143d9c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-c3ce483c8d5db370.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-13c7b53e0c2519a9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-96f074f6538e8a2c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-e97be5334d83a9f5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-65e484302921a932.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-34d08facaf08f556.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-955ec2da3be4c560.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-9f88fbc15d9058d1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-c1343f9499d86f3f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-8739a2e0ae793130.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-c9fc5706b8abaa11.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-d23b12bb92242558.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-8d47f45a0dc044ac.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-c7fea475488201a8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-e2ee2896811e98d0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-83b19dae8619de05.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-5efbf81124c474ae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-85f1a1fc0a82738c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-da6c08aae0f92768.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-06ee266953d9c879.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-153c2b3bcc89e8de.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-87a11e6a05f15199.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-7d4182964407c09c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-0d6d23b40263a7ea.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-7e8c39241f8eb6d0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-008c1a96618a4887.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/{page-63d14a4d8fe1e1c1.js → page-f5597e894c02cd33.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-0337fdf7fb5e37b6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-73735b64bfe1ae67.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-8fcec4c9ea2e7f70.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-f7e631e9b4daf66e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-4881b8f9f63694b4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-9817c7f9007466cc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-97f9f43172a3a90e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5cd7e3a9-404fd6b8d0ecd44c.js → f6231284-45726550aba08fca.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{f48170cc-d05d28bf87a43bf4.js → f6b7c9ba.64a895b8b2fedbdc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-539cde99554be20f.js +1 -0
- package/apps/pwa/out/_next/static/css/1f7c958c41ffa3b9.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +23 -23
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +23 -23
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +23 -23
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +22 -22
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +22 -22
- package/apps/pwa/out/ceo/index.html +1 -0
- package/apps/pwa/out/ceo/index.txt +33 -0
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +22 -22
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +23 -23
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +23 -23
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +23 -23
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +23 -23
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +23 -23
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +23 -23
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +22 -22
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +22 -22
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +23 -23
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +23 -23
- package/apps/pwa/out/loops/index.html +1 -0
- package/apps/pwa/out/loops/index.txt +30 -0
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +22 -22
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +22 -22
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +23 -23
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +23 -23
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +22 -22
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +22 -22
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +22 -22
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +22 -22
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +23 -23
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +23 -23
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +22 -22
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +23 -23
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +23 -23
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +23 -23
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +23 -23
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +23 -23
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +22 -22
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +23 -23
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +23 -23
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +23 -23
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +23 -23
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +23 -23
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +23 -23
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +23 -23
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +23 -23
- package/graphs/heal/heal-loop.yaml +1 -1
- package/graphs/implement-loop.yaml +1 -1
- package/graphs/orchestrator/orchestrator.yaml +59 -0
- package/graphs/orchestrator/recipes.yaml +18 -0
- package/graphs/release/recipes.yaml +6 -0
- package/graphs/release/release-loop.yaml +6 -1
- package/graphs/rhythm/daily.yaml +38 -0
- package/graphs/rhythm/recipes.yaml +18 -0
- package/graphs/rhythm/weekly.yaml +39 -0
- package/graphs/video/recipes.yaml +3 -0
- package/graphs/video/youtube-summary.yaml +17 -0
- package/package.json +1 -1
- package/scripts/audit-test-state-writes.ts +72 -17
- package/scripts/brand/check.ts +188 -0
- package/scripts/ci-typecheck-changed.ts +50 -18
- package/scripts/coord-post.sh +11 -1
- package/scripts/docs/generate-graph-anatomy.ts +315 -0
- package/scripts/docs-cli-check.ts +23 -6
- package/scripts/expose-rubric-check.ts +118 -0
- package/scripts/expose-rubric-suggest.ts +169 -0
- package/scripts/heal/run-role.ts +14 -0
- package/scripts/hq/standby-verify.ts +11 -7
- package/scripts/lecture-note/node.ts +31 -11
- package/scripts/lessons/handbook-page.ts +112 -0
- package/scripts/marketing/generate-ax-brief.ts +140 -0
- package/scripts/marketing/generate-feature-map.ts +118 -0
- package/scripts/release-loop/checklist-gate-node.ts +53 -14
- package/scripts/release-loop/gate-node.ts +89 -14
- package/scripts/release-loop/publish-node.ts +14 -1
- package/scripts/release-loop/unattended-release.ts +23 -1
- package/scripts/release-loop/vault-note-node.ts +428 -0
- package/scripts/release-story/draft.ts +220 -0
- package/scripts/rhythm/daily.ts +570 -0
- package/scripts/rhythm/weekly.ts +315 -0
- package/scripts/skills/public-check.ts +169 -0
- package/scripts/smart-merge/resync-shadow.ts +147 -0
- package/scripts/test-deterministic.ts +4 -8
- package/scripts/test-diet/effectiveness.ts +195 -0
- package/scripts/test-diet/lib.ts +16 -8
- package/scripts/test-diet/node.ts +77 -9
- package/scripts/ux/demo-rehearsal.ts +2 -2
- package/scripts/ux/lib/rehearsal-verdict.ts +1 -0
- package/scripts/video/youtube-summary.ts +133 -0
- package/skills/diagram-master/SKILL.md +42 -49
- package/skills/diagram-master/references/pyproject.toml +4 -1
- package/skills/diagram-master/references/routing-guide.md +4 -2
- package/skills/grill-me/SKILL.md +1 -0
- package/skills/youtube-master/SKILL.md +13 -13
- package/skills/youtube-master/package-lock.json +0 -545
- package/skills/youtube-master/package.json +2 -5
- package/skills/youtube-master/scripts/main.ts +2 -2
- package/skills/youtube-master/scripts/transcribe-local.ts +1 -1
- package/skills/youtube-master/src/util.ts +30 -7
- package/src/agent-env/profile.ts +161 -0
- package/src/agent-mission/driver.ts +3 -3
- package/src/agent-substrate/pr-reviewer.ts +2 -0
- package/src/ask-user-question/pending-questions.ts +5 -0
- package/src/auth/github-app-token.ts +112 -13
- package/src/autopilot/build/llm-conflict-merge.ts +73 -10
- package/src/autopilot/build/merge-intent.ts +53 -0
- package/src/briefing/brief-items.ts +121 -0
- package/src/chat/index.ts +2 -0
- package/src/claims/claims-ledger.ts +232 -0
- package/src/claims/claims-render.ts +166 -0
- package/src/cli/agent-env-cli.ts +21 -0
- package/src/cli/claims-cli.ts +94 -0
- package/src/cli/decisions-cli.ts +44 -3
- package/src/cli/docs-cli.ts +2 -0
- package/src/cli/doctor-cli.ts +23 -1
- package/src/cli/doctor-node-modules.ts +147 -0
- package/src/cli/flow-cli.ts +11 -0
- package/src/cli/freeze-cli.ts +45 -0
- package/src/cli/intake-cli.ts +46 -17
- package/src/cli/lesson-cli.ts +108 -0
- package/src/cli/logs-cli.ts +104 -2
- package/src/cli/logs-model-input.ts +131 -0
- package/src/cli/pod-cli.ts +2 -0
- package/src/cli/pr-cli.ts +29 -1
- package/src/cli/private-ledger-commands.ts +54 -4
- package/src/cli/pty-drive-verdict.ts +11 -2
- package/src/cli/release-cli.ts +190 -33
- package/src/cli/rfc-status.ts +91 -0
- package/src/cli/seat-requests-cli.ts +52 -0
- package/src/connectors/linear.ts +5 -3
- package/src/context-bus/condense.ts +15 -20
- package/src/context-bus/context-now-public.ts +96 -0
- package/src/context-bus/context-now-surfaces.ts +55 -2
- package/src/context-bus/context-now.ts +6 -3
- package/src/context-bus/coord-events.ts +6 -4
- package/src/context-bus/long-term-memory.ts +92 -11
- package/src/dashboard/index.ts +75 -22
- package/src/dashboard/slash-runtime/chat-persona.ts +66 -0
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +99 -0
- package/src/dashboard/slash-runtime/loops-table.ts +77 -0
- package/src/decisions/decision-cards.ts +33 -7
- package/src/decisions/decision-ledger.ts +40 -1
- package/src/decisions/proact-meter.ts +286 -0
- package/src/decisions/proact1-lite.ts +86 -0
- package/src/decisions/web-push-decision-cards.ts +1 -1
- package/src/discord/sprint21-runtime.ts +16 -0
- package/src/discord-self-message.ts +8 -0
- package/src/discord-slash-wire.ts +31 -2
- package/src/domains/alert-audit.ts +271 -0
- package/src/domains/core-tools.ts +4 -3
- package/src/domains/ops-facts-tool.ts +31 -1
- package/src/domains/outbound-alert.ts +1 -1
- package/src/elanous-config-dir.ts +29 -4
- package/src/execution-loop/launch-gate.ts +29 -14
- package/src/flow/cards.ts +239 -0
- package/src/graph-runner/graph-tick.ts +4 -0
- package/src/graph-runner/runner.ts +30 -5
- package/src/harness/harness-cli-command.ts +259 -38
- package/src/harness/harness-incidents.ts +45 -12
- package/src/harness/harness-pod-dispatch.ts +102 -11
- package/src/harness/harness-queue-child.ts +3 -3
- package/src/harness/harness-queue.ts +583 -65
- package/src/harness/helper-repair.ts +87 -0
- package/src/harness/helper-scan.ts +151 -0
- package/src/harness/launch-stamp.ts +236 -0
- package/src/harness/ledger-write-guard-child.fixture.ts +12 -0
- package/src/harness/ledger-write-guard.ts +35 -0
- package/src/harness/model-input-observation.ts +37 -0
- package/src/harness/pod-memory-policy.ts +15 -0
- package/src/harness/repository-name.ts +55 -0
- package/src/hq/fence-audit.ts +90 -0
- package/src/hq/hq.ts +63 -5
- package/src/hq/lease.ts +4 -4
- package/src/hq/move-plan.ts +126 -0
- package/src/hq/promote.ts +332 -0
- package/src/index.ts +159 -27
- package/src/intake-plane/check.ts +63 -0
- package/src/intake-plane/digest.ts +37 -15
- package/src/intake-plane/front-classifier.ts +6 -0
- package/src/intake-plane/items.ts +3 -2
- package/src/intake-plane/lens.ts +177 -0
- package/src/intake-plane/linear-wish.ts +39 -11
- package/src/intake-plane/route.ts +145 -1
- package/src/intake-plane/wish-benchmark-ledger.ts +129 -0
- package/src/intake-plane/wish-card.ts +53 -2
- package/src/intake-plane/wish-folder.ts +5 -2
- package/src/intake-plane/wish-reply.ts +72 -0
- package/src/launch-head/seat-goal-queue-lock.fixture.ts +15 -0
- package/src/launch-head/seat-goal-queue.ts +198 -0
- package/src/lessons/lesson-import.ts +106 -0
- package/src/lessons/lesson-ledger.ts +206 -0
- package/src/loops/activity.ts +203 -0
- package/src/loops/budget.ts +63 -0
- package/src/loops/checker.ts +5 -3
- package/src/loops/contract/examples/orchestrator.loop.yaml +1 -1
- package/src/loops/loop-cli.ts +36 -4
- package/src/loops/neighbors.ts +40 -0
- package/src/loops/observe.ts +10 -0
- package/src/loops/orchestrator/author-depth.ts +220 -0
- package/src/loops/orchestrator/author-input-check.ts +82 -0
- package/src/loops/orchestrator/author-ledger.ts +232 -0
- package/src/loops/orchestrator/finish-rate.ts +175 -0
- package/src/loops/orchestrator/overlap.ts +51 -12
- package/src/loops/orchestrator/resync-shadow.ts +227 -0
- package/src/loops/orchestrator/seat-share.ts +56 -0
- package/src/loops/orchestrator/tick.ts +309 -0
- package/src/loops/orchestrator/traffic.ts +134 -13
- package/src/loops/registry.ts +535 -9
- package/src/loops/status-rows.ts +118 -0
- package/src/loops/verdict.ts +17 -5
- package/src/maturity/bot-slash-catalog.ts +66 -0
- package/src/maturity/feature-maturity.ts +9 -5
- package/src/maturity/tui-slash-availability.ts +31 -0
- package/src/mss/logging/log-store.ts +35 -4
- package/src/nexus/api/dashboard.ts +6 -1
- package/src/nexus/api/http-server.ts +33 -3
- package/src/nexus/api/loop-edges.ts +173 -0
- package/src/nexus/api/merge-approvals-gate.ts +1 -1
- package/src/nexus/api/schedules-read.ts +18 -1
- package/src/nexus/api/seat-requests.ts +79 -56
- package/src/nexus/api/task-cards-api.ts +44 -5
- package/src/nexus/api/workflows.ts +2 -1
- package/src/prompt-enhance/enhance.ts +6 -1
- package/src/release-loop/auto-start.ts +51 -0
- package/src/release-loop/checklist.ts +22 -4
- package/src/release-loop/feature-store.ts +71 -32
- package/src/release-loop/landing-freeze.ts +121 -0
- package/src/release-loop/merged-pr-checklist.ts +125 -0
- package/src/release-loop/placement.ts +302 -0
- package/src/release-loop/release-schedule.ts +3 -1
- package/src/seat-dispatch/ceo-commands.ts +2 -2
- package/src/seat-dispatch/seat-ask.ts +16 -3
- package/src/seat-dispatch/seat-questions.ts +58 -0
- package/src/seat-dispatch/seat-request-eligibility.ts +19 -0
- package/src/seat-dispatch/seat-request-ledger.ts +133 -0
- package/src/seat-loop/seat-loop.ts +531 -40
- package/src/self-dev/dev-pipeline.ts +14 -6
- package/src/self-dev/draft-sweep.ts +162 -6
- package/src/self-dev/draft-triage-rules.ts +25 -8
- package/src/self-dev/orchestrate.ts +15 -1
- package/src/self-dev/review-provider-fallback.ts +6 -1
- package/src/self-dev/run-store.ts +73 -1
- package/src/self-dev/run-supervisor.ts +57 -2
- package/src/self-dev/self-orchestrate-runtime.ts +5 -1
- package/src/self-implement/budget-gate.ts +29 -0
- package/src/self-implement/expose-gate.ts +47 -0
- package/src/self-implement/frozen-merges.ts +295 -0
- package/src/self-implement/gate-cli.ts +10 -4
- package/src/self-implement/gate-scope.ts +46 -9
- package/src/self-implement/goal-archive.ts +222 -0
- package/src/self-implement/goal-author.ts +43 -5
- package/src/self-implement/goal-clarification-escalation.ts +5 -0
- package/src/self-implement/goal-lookup.ts +147 -0
- package/src/self-implement/heal-intake.ts +197 -0
- package/src/self-implement/host-regate.ts +32 -2
- package/src/self-implement/l8-merge-queue.ts +132 -0
- package/src/self-implement/orchestrator.ts +299 -48
- package/src/self-implement/pipeline-shape.ts +1 -1
- package/src/self-implement/revert-guard.ts +97 -0
- package/src/self-implement/run-ledger.ts +3 -1
- package/src/self-implement/run-status-mapping.ts +2 -0
- package/src/self-implement/seams.ts +29 -9
- package/src/self-implement/self-implement-cli.ts +1 -0
- package/src/self-implement/speed-claims.ts +338 -0
- package/src/session/index.ts +2 -0
- package/src/steward/triage.ts +74 -3
- package/src/steward/working-backwards.ts +4 -1
- package/src/task-cards/card-cli.ts +10 -2
- package/src/task-cards/card-store.ts +10 -6
- package/src/task-orchestrator/surfaces/pod-artifact-return.ts +63 -48
- package/src/task-orchestrator/surfaces/pod-command-job.ts +84 -9
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +48 -0
- package/src/task-orchestrator/surfaces/pod-lease.ts +5 -2
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +46 -6
- package/src/task-orchestrator/surfaces/pod-pool.ts +60 -8
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +224 -49
- package/src/task-orchestrator/surfaces/self-implement.ts +5 -1
- package/src/telegram-commands.ts +27 -3
- package/src/telegram-loops-command.ts +44 -0
- package/src/telegram.ts +25 -4
- package/src/user-config.ts +146 -18
- package/src/version/packed-revision.json +1 -1
- package/src/workflow-runtime/nodes/bash.ts +9 -1
- package/src/workflow-runtime/storage.ts +31 -13
- package/apps/pwa/out/_next/static/D51l8jUBplJrIpWvENsDK/_buildManifest.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1159-0c5ef8bc21cb6afb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1394-931c9827067b2276.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2176-c379e3ebce874151.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2426.8c6ee0c668d53def.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3170-7ec60a56fa0e4728.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3278-ae6ef084769f6195.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3454-988a53dac60a1763.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3638-aa80b4641ea439db.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3735-fe3402553ac1c9c6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3992-fe5c06d6b31f8b25.js +0 -25
- package/apps/pwa/out/_next/static/chunks/4215-c9d91dd644ec1719.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4508-5d0161d1e724d735.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4733-623a79345b171e1c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4883.3bd969ea1d3aaaff.js +0 -2
- package/apps/pwa/out/_next/static/chunks/5250-c78ca295585bbf95.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5532.6899a12958a89b72.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5845-e6c003ce8d5fd36a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5978-b27004c677119115.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6048-4c5c33579bc4ef78.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6303-422dbf0f67c72aac.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6511-ff8360be3d65aaa4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6588-ba380869b67c4fa7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6751-bb3595948b2e311b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7274-19b7279f18b47ab3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7551-a0e0707b79164ddd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7758-85c2b53f731da024.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7777-79f845045aa1ed86.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8025-cc24ee5d8f2fb6f8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8206-c6684dcde12be721.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8605-9da4678f8a310d18.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8725.bf46bb1395dd0fa2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8734-252d511cd276c0dc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8834-ca1cb09c1cb1a6bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9269.d2a4795bc6812717.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9538-2816b90923606459.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9906-e7b8b5310a169dfe.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-d16993889b125c42.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-e5b4e1b256f41076.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-e49b9b76c565a0b1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-b4f032c893b21cc9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-f14987d5850a31e0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-b433308aad13ceb6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-4c1dbb21861beab2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-c85b816e3b269dbb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-f6bc66b919b2ea7d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-60603c1e056c9b25.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-a0ea8f5a2341ee6b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-45c5bf04b4a605e6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-36c6c4183353ac9e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-d16993889b125c42.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-cb2eb0fd809980da.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-31012264da8a341f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-50309aeb0bdd554a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-bc00e964e855f4df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-0696ee3fac9fe56c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66c27608f651b866.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-6ddd2109b7fc3a80.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-4e50b7298c1f69a5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-7a2bf830038a2264.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1f327f18694fabca.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-e5cef2fe6e4028df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cf596128d2b420ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-f672536f14f0969f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-b52365662232db89.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-e7a6c80f288b1948.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-29a709616df9b050.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-fee842d7c2151129.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-78b40a344ed22f24.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-75d199f448171014.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-a011dd5b0bff29c5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-d16993889b125c42.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-d92e32d51ab5157e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-5f21aed6932ef375.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-1e1a53946804debf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-a1833cd43d832601.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-2a511d320f762e06.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-1c3e03f88dac1e21.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-c4d5289c2ea97a89.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b91f8952deaeaf27.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-71b43a2588489987.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-2b256feed331546f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-505bb96d69a8622c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-172630e6aa5e2d7f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-f0fe2d7477845181.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-fc98ac70b44eccb0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-5bd7f10271f72413.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-97d0d85ea05fef9b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-9e4d02d567b98240.js +0 -1
- package/apps/pwa/out/_next/static/css/128fb7fb670b2fbf.css +0 -1
- /package/apps/pwa/out/_next/static/{D51l8jUBplJrIpWvENsDK → LZcstaO9_NXypPFmujolv}/_ssgManifest.js +0 -0
|
@@ -5,10 +5,19 @@ import type { DeployVerifyFinding } from '../harness/browser-verify.js';
|
|
|
5
5
|
import type { SelfDevJobResult } from './orchestrate.js';
|
|
6
6
|
import { recordSelfDevRunSupervisorStop, selfDevRunsDir } from './run-store.js';
|
|
7
7
|
import { debug } from '../debug/log.js';
|
|
8
|
+
import { sweepFrozenMerges } from '../self-implement/frozen-merges.js';
|
|
8
9
|
|
|
9
10
|
export interface SupervisorRound {
|
|
10
11
|
round: number;
|
|
11
12
|
landed: number;
|
|
13
|
+
/**
|
|
14
|
+
* Pieces that finished (`ok=true`) with at least one new commit against
|
|
15
|
+
* this run's own `--base` — not a main merge. Omitted on rounds that
|
|
16
|
+
* never measured it, so older history stays comparable.
|
|
17
|
+
*/
|
|
18
|
+
baseCompleted?: number;
|
|
19
|
+
/** The run's own `--base` those completions were counted against. */
|
|
20
|
+
base?: string;
|
|
12
21
|
actionable: number;
|
|
13
22
|
reviewMustFixTrend?: SupervisorDecision['reviewMustFixTrend'];
|
|
14
23
|
}
|
|
@@ -21,6 +30,7 @@ export type SupervisorStopReason =
|
|
|
21
30
|
| 'parent-signals-red'
|
|
22
31
|
| 'needs-human'
|
|
23
32
|
| 'no-actionable-work'
|
|
33
|
+
| 'harvestable-awaiting-human'
|
|
24
34
|
| 'handed-off-to-salvage'
|
|
25
35
|
| 'max-rounds'
|
|
26
36
|
| 'no-progress'
|
|
@@ -38,6 +48,7 @@ export const SUPERVISOR_STOP_REASONS = [
|
|
|
38
48
|
'parent-signals-red',
|
|
39
49
|
'needs-human',
|
|
40
50
|
'no-actionable-work',
|
|
51
|
+
'harvestable-awaiting-human',
|
|
41
52
|
'handed-off-to-salvage',
|
|
42
53
|
'max-rounds',
|
|
43
54
|
'no-progress',
|
|
@@ -104,9 +115,25 @@ export function countLanded(results: readonly SelfDevJobResult[]): number {
|
|
|
104
115
|
return results.filter(hasDelivered).length;
|
|
105
116
|
}
|
|
106
117
|
|
|
118
|
+
/** One base label for the round. Mixed or missing bases stay unnamed. */
|
|
119
|
+
export function roundBase(results: readonly SelfDevJobResult[]): string | undefined {
|
|
120
|
+
const bases = [...new Set(results.flatMap((result) => result.base === undefined ? [] : [result.base]))];
|
|
121
|
+
return bases.length === 1 ? bases[0] : undefined;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Finished pieces (`ok=true`) that added at least one commit against this
|
|
126
|
+
* run's own `--base`. A main merge is not required, and a commit without
|
|
127
|
+
* `ok=true` is not completion.
|
|
128
|
+
*/
|
|
129
|
+
export function countBaseCompleted(results: readonly SelfDevJobResult[]): number {
|
|
130
|
+
return results.filter((result) => result.ok === true && (result.commitsAheadOfBase ?? 0) >= 1).length;
|
|
131
|
+
}
|
|
132
|
+
|
|
107
133
|
export function madeProgress(prev: SupervisorRound | undefined, next: SupervisorRound): boolean {
|
|
108
134
|
if (!prev) return true;
|
|
109
135
|
if (next.landed > 0) return true;
|
|
136
|
+
if ((next.baseCompleted ?? 0) > (prev.baseCompleted ?? 0)) return true;
|
|
110
137
|
if (next.actionable < prev.actionable) return true;
|
|
111
138
|
return next.reviewMustFixTrend === 'improving';
|
|
112
139
|
}
|
|
@@ -190,8 +217,16 @@ export function decideNextRun(input: {
|
|
|
190
217
|
const t = triageRun(activeResults, deployFindings, observation === 'failed');
|
|
191
218
|
const round = history.length;
|
|
192
219
|
const landed = countLanded(results);
|
|
220
|
+
const baseCompleted = countBaseCompleted(results);
|
|
221
|
+
const completionBase = roundBase(results);
|
|
193
222
|
const actionable = countActionable(t);
|
|
194
|
-
const thisRound: SupervisorRound = {
|
|
223
|
+
const thisRound: SupervisorRound = {
|
|
224
|
+
round,
|
|
225
|
+
landed,
|
|
226
|
+
...(baseCompleted > 0 || completionBase !== undefined ? { baseCompleted, ...(completionBase === undefined ? {} : { base: completionBase }) } : {}),
|
|
227
|
+
actionable,
|
|
228
|
+
reviewMustFixTrend,
|
|
229
|
+
};
|
|
195
230
|
const goalPlanRevisionObservation = formatGoalPlanRevisionObservation(results);
|
|
196
231
|
const withGoalPlanRevisionObservation = (why: string): string => goalPlanRevisionObservation
|
|
197
232
|
&& goalPlanRevisionObservation !== GOAL_PLAN_REVISION_OBSERVATION_UNAVAILABLE
|
|
@@ -225,6 +260,16 @@ export function decideNextRun(input: {
|
|
|
225
260
|
};
|
|
226
261
|
}
|
|
227
262
|
|
|
263
|
+
const harvestable = results.filter((result) => result.harvestable === true && !!result.branch);
|
|
264
|
+
if (harvestable.length > 0) {
|
|
265
|
+
return {
|
|
266
|
+
...base,
|
|
267
|
+
action: 'stop',
|
|
268
|
+
stopReason: 'harvestable-awaiting-human',
|
|
269
|
+
why: withGoalPlanRevisionObservation(`구현 완료 · 수확 대기 — ${harvestable.map((result) => result.branch).join(' · ')}`),
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
228
273
|
if (deliverableMergeCompleteness === 'complete') {
|
|
229
274
|
return {
|
|
230
275
|
...base,
|
|
@@ -363,7 +408,7 @@ export function decideNextRun(input: {
|
|
|
363
408
|
...base,
|
|
364
409
|
action: 'stop',
|
|
365
410
|
stopReason: 'no-progress',
|
|
366
|
-
why: withNoProgressGoalPlanRevisionObservation(`${stallRounds}라운드 연속 제자리 —
|
|
411
|
+
why: withNoProgressGoalPlanRevisionObservation(`${stallRounds}라운드 연속 제자리 — base=${completionBase ?? 'main'} 기준 완료 ${baseCompleted} · 착지 ${landed} · 남은 조각도 안 줄었다(${actionable}). 골·불변식을 의심할 자리`),
|
|
367
412
|
};
|
|
368
413
|
}
|
|
369
414
|
}
|
|
@@ -389,9 +434,12 @@ export function appendRound(
|
|
|
389
434
|
reviewMustFixTrend?: SupervisorDecision['reviewMustFixTrend'],
|
|
390
435
|
): SupervisorRound[] {
|
|
391
436
|
const t = triageRun(results.filter((result) => result.salvage !== 'launched'));
|
|
437
|
+
const baseCompleted = countBaseCompleted(results);
|
|
438
|
+
const base = roundBase(results);
|
|
392
439
|
return [...history, {
|
|
393
440
|
round: history.length,
|
|
394
441
|
landed: countLanded(results),
|
|
442
|
+
...(baseCompleted > 0 || base !== undefined ? { baseCompleted, ...(base === undefined ? {} : { base }) } : {}),
|
|
395
443
|
actionable: countActionable(t),
|
|
396
444
|
...(reviewMustFixTrend === undefined ? {} : { reviewMustFixTrend }),
|
|
397
445
|
}];
|
|
@@ -410,6 +458,7 @@ export interface SuperviseRunOptions {
|
|
|
410
458
|
onRound?: (results: readonly SupervisorJobResult[]) => void;
|
|
411
459
|
runStore?: { runId?: string; operatorDir?: string; dir?: string; isolatedDir?: string };
|
|
412
460
|
observe?: (event: string, data: Record<string, unknown>) => void;
|
|
461
|
+
sweepPendingMerges?: typeof sweepFrozenMerges;
|
|
413
462
|
observeDeliverables?: () => Promise<{
|
|
414
463
|
readonly deployFindings: ReadonlyMap<string, { target: string; findings?: readonly DeployVerifyFinding[] | readonly unknown[] }>;
|
|
415
464
|
readonly unmeasured: readonly unknown[];
|
|
@@ -432,6 +481,12 @@ export async function superviseRun(opts: SuperviseRunOptions): Promise<Superviso
|
|
|
432
481
|
let history: SupervisorRound[] = [];
|
|
433
482
|
|
|
434
483
|
for (;;) {
|
|
484
|
+
try {
|
|
485
|
+
const resumed = await (opts.sweepPendingMerges ?? sweepFrozenMerges)();
|
|
486
|
+
if (resumed.pending || resumed.merged) opts.observe?.('frozen-merges', resumed);
|
|
487
|
+
} catch (error) {
|
|
488
|
+
try { debug.log('harness.merge', 'resume-failed', { error: String(error) }); } catch { /* observation must not interrupt supervision */ }
|
|
489
|
+
}
|
|
435
490
|
if (opts.enrich) results = [...opts.enrich(results)];
|
|
436
491
|
|
|
437
492
|
let observed: {
|
|
@@ -13,7 +13,7 @@ import { posix as posixPath } from 'node:path';
|
|
|
13
13
|
import type { LLMToolSpec } from '../llm.js';
|
|
14
14
|
import type { ToolRuntime, ToolRuntimeContext } from '../tool-runtime/types.js';
|
|
15
15
|
import { orchestrateSelfDev, type SelfDevJobResult, type OrchestrateSelfDevOptions, type SelfDevGoal } from './orchestrate.js';
|
|
16
|
-
import type
|
|
16
|
+
import { processBirthId, type SelfDevRunParticipant, type SelfDevRunState } from './run-store.js';
|
|
17
17
|
import { HARNESS_RUN_ID_ENV, resolveRunIdentity as defaultResolveRunIdentity } from '../harness/harness-space.js';
|
|
18
18
|
import { decomposeSelfDevGoal, type SelfDevDecomposition, type SelfDevDecomposeOptions } from './decompose.js';
|
|
19
19
|
import { decomposeFabricRequest, type FabricDecomposeRequestOptions, type FabricDecomposeRequestResult } from './fabric-decompose-adapter.js';
|
|
@@ -302,6 +302,7 @@ interface OrchestrateRunLedgerInput {
|
|
|
302
302
|
readonly prior: Pick<SelfDevRunState, 'dependencies' | 'results'> | null;
|
|
303
303
|
readonly goals: readonly SelfDevGoal[];
|
|
304
304
|
readonly pid: number;
|
|
305
|
+
readonly seat?: SelfDevRunState['seat'];
|
|
305
306
|
readonly runIdSource: SelfDevRunParticipant['runIdSource'];
|
|
306
307
|
readonly now: () => number;
|
|
307
308
|
readonly onPersistenceFailure?: (stage: 'checkpoint' | 'participant', error: unknown) => void;
|
|
@@ -421,6 +422,7 @@ export function bindOrchestrateRunLedger(
|
|
|
421
422
|
input: OrchestrateRunLedgerInput,
|
|
422
423
|
): OrchestrateRunLedgerBinding {
|
|
423
424
|
const dependencies = deps.checkpointDependencies(input.prior, input.goals);
|
|
425
|
+
const pidStart = processBirthId(input.pid);
|
|
424
426
|
const checkpoint = (results: SelfDevRunState['results']): void => {
|
|
425
427
|
try {
|
|
426
428
|
deps.saveRun({
|
|
@@ -431,6 +433,8 @@ export function bindOrchestrateRunLedger(
|
|
|
431
433
|
dependencies,
|
|
432
434
|
goals: [...input.goals],
|
|
433
435
|
pid: input.pid,
|
|
436
|
+
...(pidStart ? { pidStart } : {}),
|
|
437
|
+
...(input.seat ? { seat: input.seat } : {}),
|
|
434
438
|
});
|
|
435
439
|
} catch (error) {
|
|
436
440
|
if (input.onPersistenceFailure) input.onPersistenceFailure('checkpoint', error);
|
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
|
|
4
4
|
import type { RotationCandidate } from '../oauth/codex-account-rotation.js';
|
|
5
5
|
import { inspectCodexRotation } from '../oauth/codex-account-store.js';
|
|
6
|
+
import { codexPolicyAllowsCredits } from '../oauth/codex-quota-policy.js';
|
|
6
7
|
import { getUsageStore } from '../budget/usage-store.js';
|
|
7
8
|
import type { UsageSnapshot } from '../budget/types.js';
|
|
9
|
+
import { debug } from '../debug/log.js';
|
|
8
10
|
import {
|
|
9
11
|
DEFAULT_BUDGET_GATE_MAX_USED_PERCENT,
|
|
10
12
|
type BudgetGateMaxUsedPercent,
|
|
@@ -25,6 +27,9 @@ export interface BudgetCodexCandidate {
|
|
|
25
27
|
readonly name: string;
|
|
26
28
|
readonly reached?: boolean;
|
|
27
29
|
readonly usedPercent?: number;
|
|
30
|
+
/** 선불 크레딧 잔액(계정에 귀속된 신호만). 모르면 비운다. */
|
|
31
|
+
readonly creditBalance?: number;
|
|
32
|
+
readonly hasCredits?: boolean;
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
export interface DecideBudgetInput {
|
|
@@ -36,6 +41,8 @@ export interface DecideBudgetInput {
|
|
|
36
41
|
/** 주간 사용률. `undefined` 는 «모름» — 쓸 수 있다고 보지 않는다. */
|
|
37
42
|
readonly grokUsedPercent: number | undefined;
|
|
38
43
|
readonly maxUsedPercent: BudgetGateMaxUsedPercent;
|
|
44
|
+
/** codex 한도 정책이 credits 인가(`codexPolicyAllowsCredits` · 회전·Pod 배분과 같은 해석). 없으면 false. */
|
|
45
|
+
readonly codexCreditsAllowed?: boolean;
|
|
39
46
|
}
|
|
40
47
|
|
|
41
48
|
export interface BudgetDecision {
|
|
@@ -50,6 +57,7 @@ export interface BudgetInputs {
|
|
|
50
57
|
readonly codexCandidates: readonly BudgetCodexCandidate[];
|
|
51
58
|
readonly grokUsedPercent: number | undefined;
|
|
52
59
|
readonly maxUsedPercent: BudgetGateMaxUsedPercent;
|
|
60
|
+
readonly codexCreditsAllowed?: boolean;
|
|
53
61
|
}
|
|
54
62
|
|
|
55
63
|
const WEEKLY_WINDOW_MINUTES = 7 * 24 * 60;
|
|
@@ -98,6 +106,16 @@ function codexCandidateUsable(candidate: BudgetCodexCandidate, cap: number | und
|
|
|
98
106
|
return candidate.usedPercent < cap;
|
|
99
107
|
}
|
|
100
108
|
|
|
109
|
+
/** BUDGET-GATE(10-05) — 정책이 credits 면 구독 % 가 차도 크레딧 잔액이 «확인된» 계정으로 계속 간다(Pod 배분과 같은 결론).
|
|
110
|
+
* 잔액을 모르면 쓰지 않는다 — 크레딧은 돈이라 fail-closed. 잔액이 가장 큰 계정 하나를 돌려준다. */
|
|
111
|
+
function codexCreditCandidate(input: DecideBudgetInput): BudgetCodexCandidate | undefined {
|
|
112
|
+
if (input.codexCreditsAllowed !== true) return undefined;
|
|
113
|
+
return input.codexCandidates
|
|
114
|
+
.filter((candidate) => candidate.hasCredits !== false
|
|
115
|
+
&& typeof candidate.creditBalance === 'number' && Number.isFinite(candidate.creditBalance) && candidate.creditBalance > 0)
|
|
116
|
+
.sort((a, b) => (b.creditBalance! - a.creditBalance!) || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))[0];
|
|
117
|
+
}
|
|
118
|
+
|
|
101
119
|
function slotUsable(
|
|
102
120
|
provider: string,
|
|
103
121
|
input: DecideBudgetInput,
|
|
@@ -106,6 +124,13 @@ function slotUsable(
|
|
|
106
124
|
if (provider === CODEX_PROVIDER) {
|
|
107
125
|
const cap = capFor(input.maxUsedPercent, CODEX_PROVIDER);
|
|
108
126
|
const measuredUsable = input.codexCandidates.some((candidate) => codexCandidateUsable(candidate, cap));
|
|
127
|
+
const credit = measuredUsable ? undefined : codexCreditCandidate(input);
|
|
128
|
+
if (credit) {
|
|
129
|
+
try {
|
|
130
|
+
debug.log('budget.gate', 'credits-allowed', { used: credit.usedPercent ?? null, balance: credit.creditBalance, policy: 'credits' });
|
|
131
|
+
} catch { /* observation is fail-soft */ }
|
|
132
|
+
return { usable: true, reason: `${formatCodexReason(input.codexCandidates, cap)} · credits allowed (balance ${credit.creditBalance})` };
|
|
133
|
+
}
|
|
109
134
|
const unmeasured = allowUnmeasured && !measuredUsable && (input.codexCandidates.length === 0
|
|
110
135
|
|| input.codexCandidates.some((candidate) => candidate.reached !== true
|
|
111
136
|
&& (typeof candidate.usedPercent !== 'number' || !Number.isFinite(candidate.usedPercent))));
|
|
@@ -188,6 +213,8 @@ export function codexCandidatesFromRotation(
|
|
|
188
213
|
name: candidate.name,
|
|
189
214
|
...(candidate.reached !== undefined ? { reached: candidate.reached } : {}),
|
|
190
215
|
...(candidate.usedPercent !== undefined ? { usedPercent: candidate.usedPercent } : {}),
|
|
216
|
+
...(candidate.creditBalance !== undefined ? { creditBalance: candidate.creditBalance } : {}),
|
|
217
|
+
...(candidate.hasCredits !== undefined ? { hasCredits: candidate.hasCredits } : {}),
|
|
191
218
|
}));
|
|
192
219
|
}
|
|
193
220
|
|
|
@@ -212,6 +239,8 @@ export function readBudgetInputs(deps: ReadBudgetInputsDeps = {}): BudgetInputs
|
|
|
212
239
|
codexCandidates: codexCandidatesFromRotation(rotation.candidates),
|
|
213
240
|
grokUsedPercent: grokWeeklyUsedPercent(snapshot),
|
|
214
241
|
maxUsedPercent,
|
|
242
|
+
// 회전·Pod 배분과 같은 해석(`resolveCodexQuotaPolicy` → `codexPolicyAllowsCredits`)을 읽는다 — 다시 짓지 않는다.
|
|
243
|
+
...(rotation.policy && codexPolicyAllowsCredits(rotation.policy.policy) ? { codexCreditsAllowed: true } : {}),
|
|
215
244
|
};
|
|
216
245
|
}
|
|
217
246
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { debug } from '../debug/log.js';
|
|
3
|
+
|
|
4
|
+
type ExposeMode = 'warn' | 'strict';
|
|
5
|
+
type ProcessResult = { status: number | null; stdout?: string; stderr?: string; error?: Error };
|
|
6
|
+
type ExposeResult = { passed: boolean; log: string };
|
|
7
|
+
|
|
8
|
+
/** The rubric only accepts root-level public Markdown documents. */
|
|
9
|
+
export function publicExposureFiles(changed: readonly string[]): string[] {
|
|
10
|
+
return [...new Set(changed.filter((file) => /^release\/public\/docs\/[^/]+\.md$/.test(file)))];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function runExposeGate(
|
|
14
|
+
cwd: string,
|
|
15
|
+
changed: readonly string[],
|
|
16
|
+
mode: ExposeMode,
|
|
17
|
+
run: (command: string, args: string[], cwd: string) => ProcessResult = (command, args, dir) => {
|
|
18
|
+
const result = spawnSync(command, args, { cwd: dir, encoding: 'utf8', timeout: 300_000 });
|
|
19
|
+
return { status: result.status, stdout: result.stdout ?? '', stderr: result.stderr ?? '', error: result.error };
|
|
20
|
+
},
|
|
21
|
+
): ExposeResult {
|
|
22
|
+
const files = publicExposureFiles(changed);
|
|
23
|
+
if (files.length === 0) return { passed: true, log: '' };
|
|
24
|
+
let unreviewed: number | null = null;
|
|
25
|
+
let failed: number | null = null;
|
|
26
|
+
let docs: number | null = null;
|
|
27
|
+
let error = '';
|
|
28
|
+
try {
|
|
29
|
+
const result = run('bun', ['scripts/expose-rubric-check.ts', '--json', '--files', ...files], cwd);
|
|
30
|
+
if (result.status !== 0 || result.error) throw new Error(result.stderr?.trim() || result.error?.message || `exit ${result.status}`);
|
|
31
|
+
const parsed = JSON.parse(result.stdout ?? '') as Record<string, unknown>;
|
|
32
|
+
const lists = ['missing', 'orphan', 'failing', 'internal', 'unreviewed', 'unjudged'] as const;
|
|
33
|
+
if (!Number.isSafeInteger(parsed.docs) || (parsed.docs as number) < 0 || !lists.every((key) => Array.isArray(parsed[key]))) {
|
|
34
|
+
throw new Error('invalid exposure rubric JSON');
|
|
35
|
+
}
|
|
36
|
+
docs = parsed.docs as number;
|
|
37
|
+
unreviewed = new Set([...(parsed.missing as string[]), ...(parsed.unjudged as string[])]).size;
|
|
38
|
+
failed = new Set([...(parsed.orphan as string[]), ...(parsed.failing as Array<{ path: string }>).map((item) => item.path), ...(parsed.internal as string[])]).size;
|
|
39
|
+
} catch (cause) {
|
|
40
|
+
error = cause instanceof Error ? cause.message : String(cause);
|
|
41
|
+
}
|
|
42
|
+
try { debug.log('self-implement.gate', 'expose', { files, unreviewed, failed, mode }); } catch { /* observation must not change judgment */ }
|
|
43
|
+
const log = docs === null
|
|
44
|
+
? `[expose] 못 쟀다 — ${error}`
|
|
45
|
+
: `[expose] ${files.length}개 · 미판정 ${unreviewed} · fail ${failed}`;
|
|
46
|
+
return { passed: mode === 'warn' || (docs !== null && unreviewed === 0 && failed === 0), log };
|
|
47
|
+
}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dlopen, FFIType } from 'bun:ffi';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { dirname, join, resolve } from 'node:path';
|
|
5
|
+
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
6
|
+
import { debug } from '../debug/log.js';
|
|
7
|
+
import { beginLandingMerge, readLandingFreeze, type LandingFreeze } from '../release-loop/landing-freeze.js';
|
|
8
|
+
|
|
9
|
+
/** `manual`: the owning repository could not be determined — a sweep never resumes it; a person does. */
|
|
10
|
+
export interface FrozenMerge { prNumber: number; headCommit: string; repoRoot: string; manual?: true; /** Host goal document, so a resumed merge can still apply its «이 칸 완료» declaration (CL-AUTO). */ goalFile?: string }
|
|
11
|
+
/** What a resume sweep hands its merge function: the held entry, marked as already claimed by this sweep. */
|
|
12
|
+
export type ResumedMerge = FrozenMerge & { resumed: true };
|
|
13
|
+
|
|
14
|
+
function queuePath(root: string): string { return join(root, 'landing-freeze-pending.json'); }
|
|
15
|
+
|
|
16
|
+
/** flock(2) through bun:ffi — the kernel drops the lock when its holder exits, so there is no stale lock to reclaim
|
|
17
|
+
* and no reclaim race. The lock file itself is never deleted (unlinking a flock file would split later lockers). */
|
|
18
|
+
const LOCK_EX = 2, LOCK_NB = 4;
|
|
19
|
+
let flockCall: ((fd: number, op: number) => number) | undefined;
|
|
20
|
+
function flock(fd: number, op: number): number {
|
|
21
|
+
if (!flockCall) {
|
|
22
|
+
const names = process.platform === 'darwin' ? ['libc.dylib'] : ['libc.so.6', `libc.musl-${process.arch === 'arm64' ? 'aarch64' : 'x86_64'}.so.1`];
|
|
23
|
+
let lastError: unknown;
|
|
24
|
+
for (const name of names) {
|
|
25
|
+
try {
|
|
26
|
+
const lib = dlopen(name, { flock: { args: [FFIType.i32, FFIType.i32], returns: FFIType.i32 } });
|
|
27
|
+
flockCall = (descriptor, operation) => lib.symbols.flock(descriptor, operation) as number;
|
|
28
|
+
break;
|
|
29
|
+
} catch (error) { lastError = error; }
|
|
30
|
+
}
|
|
31
|
+
if (!flockCall) throw new Error(`flock unavailable (${names.join(', ')}): ${String(lastError)}`);
|
|
32
|
+
}
|
|
33
|
+
return flockCall(fd, op);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function tryLandingLock(path: string): (() => void) | null {
|
|
37
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
38
|
+
const fd = openSync(path, 'a', 0o600);
|
|
39
|
+
if (flock(fd, LOCK_EX | LOCK_NB) !== 0) { closeSync(fd); return null; }
|
|
40
|
+
let released = false;
|
|
41
|
+
return () => { if (!released) { released = true; closeSync(fd); } };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function tryLock(path: string): (() => void) | null { return tryLandingLock(path); }
|
|
45
|
+
|
|
46
|
+
function lockedQueue<T>(root: string, action: () => T): T {
|
|
47
|
+
mkdirSync(root, { recursive: true });
|
|
48
|
+
const path = join(root, 'landing-freeze-pending.lock');
|
|
49
|
+
const deadline = Date.now() + 5_000;
|
|
50
|
+
let release = tryLock(path);
|
|
51
|
+
while (!release) {
|
|
52
|
+
if (Date.now() >= deadline) throw new Error(`frozen merge queue lock busy: ${path}`);
|
|
53
|
+
Bun.sleepSync(25);
|
|
54
|
+
release = tryLock(path);
|
|
55
|
+
}
|
|
56
|
+
try { return action(); }
|
|
57
|
+
finally { release(); }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function readQueue(root: string): FrozenMerge[] {
|
|
61
|
+
const path = queuePath(root);
|
|
62
|
+
if (!existsSync(path)) return [];
|
|
63
|
+
const data: unknown = JSON.parse(readFileSync(path, 'utf8'));
|
|
64
|
+
if (!Array.isArray(data) || !data.every((item) => item && typeof item === 'object' &&
|
|
65
|
+
Number.isSafeInteger(item.prNumber) && item.prNumber > 0 &&
|
|
66
|
+
typeof item.headCommit === 'string' && /^[a-f0-9]{40}$/i.test(item.headCommit) &&
|
|
67
|
+
typeof item.repoRoot === 'string' && item.repoRoot.length > 0 &&
|
|
68
|
+
(item.manual === undefined || item.manual === true) &&
|
|
69
|
+
(item.goalFile === undefined || (typeof item.goalFile === 'string' && item.goalFile.length > 0)))) throw new Error(`invalid frozen merge queue: ${path}`);
|
|
70
|
+
return data as FrozenMerge[];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function writeQueue(root: string, entries: FrozenMerge[]): void {
|
|
74
|
+
mkdirSync(root, { recursive: true });
|
|
75
|
+
const path = queuePath(root);
|
|
76
|
+
const temp = `${path}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
77
|
+
try {
|
|
78
|
+
writeFileSync(temp, `${JSON.stringify(entries, null, 2)}\n`, { mode: 0o600 });
|
|
79
|
+
renameSync(temp, path);
|
|
80
|
+
} finally { rmSync(temp, { force: true }); }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Ordering of two heads of one PR in `repoRoot`: 'older' when `a` is a strict ancestor of `b`, 'newer' when `b` is one
|
|
84
|
+
* of `a`, otherwise unknown (unrelated, or git cannot see both objects). */
|
|
85
|
+
function headOrder(repoRoot: string, a: string, b: string): 'older' | 'newer' | 'unknown' {
|
|
86
|
+
const ancestor = (x: string, y: string) => {
|
|
87
|
+
try { return Bun.spawnSync(['git', '-C', repoRoot, 'merge-base', '--is-ancestor', x, y], { env: process.env }).exitCode === 0; }
|
|
88
|
+
catch { return false; }
|
|
89
|
+
};
|
|
90
|
+
if (a === b) return 'unknown';
|
|
91
|
+
if (ancestor(a, b)) return 'older';
|
|
92
|
+
if (ancestor(b, a)) return 'newer';
|
|
93
|
+
return 'unknown';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** GitHub's current head of the PR, or null when it cannot be read. */
|
|
97
|
+
function githubHead(entry: FrozenMerge): string | null {
|
|
98
|
+
try {
|
|
99
|
+
const gh = Bun.which('gh', { PATH: process.env.PATH ?? '' }) ?? 'gh';
|
|
100
|
+
const view = Bun.spawnSync([gh, 'pr', 'view', String(entry.prNumber), '--json', 'headRefOid'], { cwd: entry.repoRoot, env: process.env, timeout: 30_000 });
|
|
101
|
+
if (view.exitCode !== 0) return null;
|
|
102
|
+
const head = (JSON.parse(view.stdout.toString()) as { headRefOid?: string }).headRefOid;
|
|
103
|
+
return typeof head === 'string' && /^[0-9a-f]{40}$/i.test(head) ? head : null;
|
|
104
|
+
} catch { return null; }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function queueFrozenMerge(entry: FrozenMerge, root = effectiveInstanceRoot(), currentHead: (entry: FrozenMerge) => string | null = githubHead): void {
|
|
108
|
+
if (!Number.isSafeInteger(entry.prNumber) || entry.prNumber <= 0 || !/^[0-9a-f]{40}$/i.test(entry.headCommit) || !entry.repoRoot) throw new Error('invalid frozen merge entry');
|
|
109
|
+
const repoRoot = repoIdentity(entry.repoRoot);
|
|
110
|
+
// Slow lookups (git, GitHub) happen outside the queue lock; inside it only the held head they were judged against
|
|
111
|
+
// is re-checked — if it changed meanwhile, the judgement is made again.
|
|
112
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
113
|
+
const seen = readQueue(root).find((item) => item.repoRoot === repoRoot && item.prNumber === entry.prNumber)?.headCommit;
|
|
114
|
+
let keepExisting = false;
|
|
115
|
+
let by = 'none';
|
|
116
|
+
if (seen && seen !== entry.headCommit) {
|
|
117
|
+
const order = headOrder(repoRoot, entry.headCommit, seen);
|
|
118
|
+
if (order !== 'unknown') { keepExisting = order === 'older'; by = 'ancestry'; }
|
|
119
|
+
else { keepExisting = currentHead({ ...entry, repoRoot }) !== entry.headCommit; by = 'github-or-unknown'; }
|
|
120
|
+
}
|
|
121
|
+
const done = lockedQueue(root, () => {
|
|
122
|
+
const current = readQueue(root);
|
|
123
|
+
const existing = current.find((item) => item.repoRoot === repoRoot && item.prNumber === entry.prNumber);
|
|
124
|
+
if (existing?.headCommit !== seen) return false; // changed while we looked: judge again
|
|
125
|
+
if (existing && keepExisting) {
|
|
126
|
+
debug.log('harness.merge', 'frozen-head-kept', { pr: entry.prNumber, kept: existing.headCommit, ignored: entry.headCommit, by });
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
writeQueue(root, [...current.filter((item) => item !== existing), { ...entry, repoRoot }]);
|
|
130
|
+
return true;
|
|
131
|
+
});
|
|
132
|
+
if (done) return;
|
|
133
|
+
}
|
|
134
|
+
throw new Error(`frozen merge queue kept changing for PR ${entry.prNumber}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function pendingFrozenMerges(root = effectiveInstanceRoot()): number { return readQueue(root).length; }
|
|
138
|
+
|
|
139
|
+
/** The repository a run worktree belongs to, as a path that outlives the worktree: the main worktree when there is
|
|
140
|
+
* one (also with a separate git dir), else the bare repository directory; the worktree itself only if git cannot say. */
|
|
141
|
+
export function owningRepoRoot(worktree: string, spawn: typeof Bun.spawnSync = Bun.spawnSync): string | null {
|
|
142
|
+
let list: ReturnType<typeof Bun.spawnSync>;
|
|
143
|
+
try { list = spawn(['git', '-C', worktree, 'worktree', 'list', '--porcelain'], { env: process.env }); }
|
|
144
|
+
catch { return null; } // git could not even run: unknown owner — the held PR stays for a person
|
|
145
|
+
if (list.exitCode === 0) {
|
|
146
|
+
const first = (list.stdout?.toString() ?? "").split('\n\n')[0] ?? '';
|
|
147
|
+
const path = /^worktree (.+)$/m.exec(first)?.[1];
|
|
148
|
+
if (path) return path; // main worktree, or the bare repository directory (gh reads the remote from the git dir)
|
|
149
|
+
}
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const sameEntry = (a: FrozenMerge, b: FrozenMerge) => a.prNumber === b.prNumber && a.repoRoot === b.repoRoot && a.headCommit === b.headCommit;
|
|
154
|
+
|
|
155
|
+
/** One runner per held PR: whoever creates the claim file (resume sweep or the original lander) merges it. */
|
|
156
|
+
/** One identity per repository path: symlinks and `..` resolve to the real path (absolute path when it does not exist). */
|
|
157
|
+
export function repoIdentity(path: string): string {
|
|
158
|
+
try { return realpathSync(path); } catch { return resolve(path); }
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** A claim names the repository as well as the PR number — the queue tells PRs apart the same way. */
|
|
162
|
+
export function claimPath(key: { prNumber: number; repoRoot: string }, root: string): string {
|
|
163
|
+
const repo = createHash('sha256').update(repoIdentity(key.repoRoot)).digest('hex').slice(0, 12);
|
|
164
|
+
return join(root, `landing-freeze-claim-${key.prNumber}-${repo}.lock`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function claimFrozenMerge(entry: FrozenMerge, root: string): { release: () => void } | null {
|
|
168
|
+
mkdirSync(root, { recursive: true });
|
|
169
|
+
const release = tryLock(claimPath(entry, root));
|
|
170
|
+
return release ? { release } : null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export type LandingAdmission =
|
|
174
|
+
| { kind: 'merge'; /** `true` once the merge is confirmed: drops this lander's own held entry. */ end: (merged?: boolean) => void }
|
|
175
|
+
| { kind: 'held'; freeze: LandingFreeze }
|
|
176
|
+
| { kind: 'taken' };
|
|
177
|
+
|
|
178
|
+
/** Gate one merge/publication on the freeze. While frozen the entry is queued; if the freeze is lifted while queueing,
|
|
179
|
+
* the entry is taken back and the caller merges now — unless a resume sweep already claimed it ('taken'). */
|
|
180
|
+
export function admitLandingMerge(
|
|
181
|
+
heldEntry: FrozenMerge | null | (() => FrozenMerge | null),
|
|
182
|
+
root = effectiveInstanceRoot(),
|
|
183
|
+
hooks: { afterQueue?: () => void } = {},
|
|
184
|
+
claimKey?: { prNumber: number; repoRoot: string; headCommit?: string },
|
|
185
|
+
): LandingAdmission {
|
|
186
|
+
let entry: FrozenMerge | null | undefined;
|
|
187
|
+
// A held PR is merged by exactly one runner: the claim (repository + PR) is held from admission until the lander's
|
|
188
|
+
// merge has ended. Its queue entry stays until that merge is confirmed — `end(true)` removes this lander's own head;
|
|
189
|
+
// a failed or interrupted merge leaves it for the next resume sweep.
|
|
190
|
+
let claim: { release: () => void } | null = null;
|
|
191
|
+
let ownHead: { prNumber: number; repoRoot: string; headCommit?: string } | null = null;
|
|
192
|
+
const finish = (landing: { end: () => void }, held: { release: () => void } | null, own: typeof ownHead) => (merged?: boolean) => {
|
|
193
|
+
try {
|
|
194
|
+
// Only a confirmed merge of a known head removes that head; without a head nothing is dequeued (a sweep will
|
|
195
|
+
// find the PR merged on GitHub and drop it).
|
|
196
|
+
if (merged && own?.headCommit) {
|
|
197
|
+
const head = own.headCommit;
|
|
198
|
+
lockedQueue(root, () => writeQueue(root, readQueue(root).filter((item) =>
|
|
199
|
+
item.prNumber !== own.prNumber || item.repoRoot !== own.repoRoot || item.headCommit !== head)));
|
|
200
|
+
}
|
|
201
|
+
} finally { landing.end(); held?.release(); }
|
|
202
|
+
};
|
|
203
|
+
for (;;) {
|
|
204
|
+
const landing = beginLandingMerge(root);
|
|
205
|
+
if (!landing.frozen) {
|
|
206
|
+
if (!claim && claimKey) {
|
|
207
|
+
const key = { prNumber: claimKey.prNumber, repoRoot: repoIdentity(claimKey.repoRoot), headCommit: claimKey.headCommit };
|
|
208
|
+
const queued = () => readQueue(root).some((item) => item.prNumber === key.prNumber && item.repoRoot === key.repoRoot);
|
|
209
|
+
if (existsSync(claimPath(key, root)) || queued()) {
|
|
210
|
+
const probe = tryLock(claimPath(key, root));
|
|
211
|
+
if (!probe) { landing.end(); return { kind: 'taken' }; }
|
|
212
|
+
claim = { release: probe };
|
|
213
|
+
ownHead = key;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return { kind: 'merge', end: finish(landing, claim, ownHead) };
|
|
217
|
+
}
|
|
218
|
+
claim?.release();
|
|
219
|
+
claim = null;
|
|
220
|
+
ownHead = null;
|
|
221
|
+
if (entry === undefined) entry = typeof heldEntry === 'function' ? heldEntry() : heldEntry; // only built when frozen
|
|
222
|
+
if (!entry) return { kind: 'held', freeze: landing.frozen };
|
|
223
|
+
queueFrozenMerge(entry, root);
|
|
224
|
+
hooks.afterQueue?.();
|
|
225
|
+
if (readLandingFreeze(root)) return { kind: 'held', freeze: landing.frozen };
|
|
226
|
+
const mine = { ...entry, repoRoot: repoIdentity(entry.repoRoot) };
|
|
227
|
+
claim = claimFrozenMerge(mine, root);
|
|
228
|
+
if (!claim) return { kind: 'taken' };
|
|
229
|
+
// A resume sweep already merged and dequeued it between our queueing and our claim: nothing left to merge.
|
|
230
|
+
let stillHeld = false;
|
|
231
|
+
try { stillHeld = readQueue(root).some((item) => sameEntry(item, mine)); }
|
|
232
|
+
catch (error) { claim.release(); throw error; }
|
|
233
|
+
if (!stillHeld) { claim.release(); return { kind: 'taken' }; }
|
|
234
|
+
ownHead = mine;
|
|
235
|
+
debug.log('harness.merge', 'freeze-lifted-while-queueing', { pr: entry.prNumber });
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** Default «is this PR already merged at this head?» check — GitHub is the final word. */
|
|
240
|
+
function githubMergedAt(entry: FrozenMerge): boolean {
|
|
241
|
+
try {
|
|
242
|
+
// Resolve gh against the current PATH (Bun's spawn would otherwise use the PATH from process start).
|
|
243
|
+
const gh = Bun.which('gh', { PATH: process.env.PATH ?? '' }) ?? 'gh';
|
|
244
|
+
const view = Bun.spawnSync([gh, 'pr', 'view', String(entry.prNumber), '--json', 'state,headRefOid'], { cwd: entry.repoRoot, env: process.env, timeout: 30_000 });
|
|
245
|
+
if (view.exitCode !== 0) return false;
|
|
246
|
+
const pr = JSON.parse(view.stdout.toString()) as { state?: string; headRefOid?: string };
|
|
247
|
+
return pr.state === 'MERGED' && pr.headRefOid === entry.headCommit;
|
|
248
|
+
} catch { return false; } // unknown is «not known merged»: the head-pinned merge still refuses a second merge
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** Invoke at a supervision tick. A failed re-gate remains queued; only a confirmed merge is removed.
|
|
252
|
+
* «Merged once» does not rest on the claim lock alone: every merge is pinned to the held head
|
|
253
|
+
* (`gh pr merge --match-head-commit`, which refuses an already-merged PR), and before and after each attempt the
|
|
254
|
+
* sweep asks GitHub whether the PR is already merged at that head — a duplicate runner can at most repeat a re-gate. */
|
|
255
|
+
export async function sweepFrozenMerges(
|
|
256
|
+
merge: (entry: ResumedMerge) => Promise<{ passed: boolean; status?: string }> = async (entry) => {
|
|
257
|
+
const { runHostRegate } = await import('./host-regate.js');
|
|
258
|
+
return runHostRegate(entry);
|
|
259
|
+
},
|
|
260
|
+
root = effectiveInstanceRoot(),
|
|
261
|
+
alreadyMerged: (entry: FrozenMerge) => boolean | Promise<boolean> = githubMergedAt,
|
|
262
|
+
): Promise<{ pending: number; merged: number }> {
|
|
263
|
+
const frozen = readLandingFreeze(root);
|
|
264
|
+
if (frozen) return { pending: readQueue(root).length, merged: 0 };
|
|
265
|
+
const pending = readQueue(root);
|
|
266
|
+
let merged = 0;
|
|
267
|
+
for (const entry of pending) {
|
|
268
|
+
if (readLandingFreeze(root)) break;
|
|
269
|
+
const claim = claimFrozenMerge(entry, root);
|
|
270
|
+
if (!claim) continue; // another sweep or the original lander is on it
|
|
271
|
+
try {
|
|
272
|
+
if (!readQueue(root).some((item) => sameEntry(item, entry))) continue; // taken back by its lander
|
|
273
|
+
if (entry.manual) { debug.log('harness.merge', 'resume-manual', { pr: entry.prNumber, repoRoot: entry.repoRoot }); continue; }
|
|
274
|
+
if (!(await alreadyMerged(entry))) {
|
|
275
|
+
const outcome = await merge({ ...entry, resumed: true });
|
|
276
|
+
if (outcome.status === 'frozen') continue;
|
|
277
|
+
// Whatever the merge call reported, only GitHub showing the PR merged at the held head dequeues it.
|
|
278
|
+
if (!(await alreadyMerged(entry))) {
|
|
279
|
+
if (outcome.passed) debug.log('harness.merge', 'resume-unconfirmed', { pr: entry.prNumber, head: entry.headCommit });
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
lockedQueue(root, () => {
|
|
284
|
+
const current = readQueue(root);
|
|
285
|
+
if (current.some((item) => sameEntry(item, entry))) {
|
|
286
|
+
writeQueue(root, current.filter((item) => !sameEntry(item, entry)));
|
|
287
|
+
merged++;
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
} catch (error) {
|
|
291
|
+
debug.log('harness.merge', 'resume-failed', { pr: entry.prNumber, error: String(error) });
|
|
292
|
+
} finally { claim.release(); }
|
|
293
|
+
}
|
|
294
|
+
return { pending: readQueue(root).length, merged };
|
|
295
|
+
}
|
|
@@ -12,6 +12,8 @@ import { runMockModuleRestoreGate } from '../../scripts/ci-mock-module-restore-g
|
|
|
12
12
|
import { parseReviewDepthFromFilesJson } from '../agent-mission/review-depth.js';
|
|
13
13
|
import { buildGateBaselineReport, calculateGateTestCountChange, formatGateBaselineNote, formatGateTestCountNote, isGateTestFile, runGateBaseline, type BaselineProcessResult, type BaselineProcessStatus, type GateTestFailure } from './gate-baseline.js';
|
|
14
14
|
import { resolveGateScope } from './gate-scope.js';
|
|
15
|
+
import { publicExposureFiles, runExposeGate } from './expose-gate.js';
|
|
16
|
+
import { getUserConfig } from '../user-config.js';
|
|
15
17
|
import { debug } from '../debug/log.js';
|
|
16
18
|
import { buildDocumentGuardianIndex, formatDocumentGuardians } from './document-guardian-index.js';
|
|
17
19
|
import { buildImporterTestIndex, isTestPath, type ImporterTestIndex } from './importer-test-index.js';
|
|
@@ -585,6 +587,9 @@ export function runSelfGateCli(cwd: string, options: SelfGateCliOptions = {}, de
|
|
|
585
587
|
? buildRouteConsumerTestIndex(cwd, availableIndex.testPaths, selection.files, selection.baseRef) : null;
|
|
586
588
|
const scope = resolveGateScope(selection.files, exists, availableIndex,
|
|
587
589
|
{ routeConsumerTestIndex: routeIndex ?? undefined });
|
|
590
|
+
const expose = publicExposureFiles(selection.files).length > 0
|
|
591
|
+
? runExposeGate(cwd, selection.files, getUserConfig().harness?.exposeGate === 'strict' ? 'strict' : 'warn', deps.runCommand)
|
|
592
|
+
: { passed: true, log: '' };
|
|
588
593
|
const testFiles = [...(scope.testArgs ?? [])];
|
|
589
594
|
const unrunImporterTotal = lookupFailed ? null : (scope.importerTestsNotRun?.total ?? 0);
|
|
590
595
|
const importerLookupFailed = Boolean(lookupFailed);
|
|
@@ -615,6 +620,7 @@ export function runSelfGateCli(cwd: string, options: SelfGateCliOptions = {}, de
|
|
|
615
620
|
...(scope.callerTestsOverflow.length ? [`⚠️ caller test cap exceeded — not run: ${scope.callerTestsOverflow.map(({ file, reasons }) => `${file} (${reasons.join('+')})`).join(', ')}`] : []),
|
|
616
621
|
changedTestCountNote(cwd, selection.files, selection.baseRef, deps.runCommand, deps.readFile),
|
|
617
622
|
];
|
|
623
|
+
if (expose.log) lines.push(expose.log);
|
|
618
624
|
if (options.pr) {
|
|
619
625
|
const worktreeObservation = observePrWorktreeDirtiness(cwd, selection.prHeadRefName, {
|
|
620
626
|
runCommand: deps.runCommand,
|
|
@@ -638,7 +644,7 @@ export function runSelfGateCli(cwd: string, options: SelfGateCliOptions = {}, de
|
|
|
638
644
|
if (scope.skipTestStep) {
|
|
639
645
|
const policyPassed = runPolicyGates(selection.files, cwd, lines, deps);
|
|
640
646
|
logGateCliBaseline(cwd, { ...ZERO_GATE_BASELINE, unrunImporterTotal, lookupFailed: importerLookupFailed });
|
|
641
|
-
return { exitCode: androidPassed && iosPassed && pwaPassed && policyPassed ? 0 : 1, lines, changedFiles: selection.files, testFiles: [], unverified: scope.unverified, documentPaths: scope.documentPaths, documentsWithoutDerivedTests: scope.documentsWithoutDerivedTests };
|
|
647
|
+
return { exitCode: androidPassed && iosPassed && pwaPassed && policyPassed && expose.passed ? 0 : 1, lines, changedFiles: selection.files, testFiles: [], unverified: scope.unverified, documentPaths: scope.documentPaths, documentsWithoutDerivedTests: scope.documentsWithoutDerivedTests };
|
|
642
648
|
}
|
|
643
649
|
|
|
644
650
|
if (options.shards !== undefined) {
|
|
@@ -662,7 +668,7 @@ export function runSelfGateCli(cwd: string, options: SelfGateCliOptions = {}, de
|
|
|
662
668
|
}
|
|
663
669
|
const policyPassed = runPolicyGates(selection.files, cwd, lines, deps);
|
|
664
670
|
return {
|
|
665
|
-
exitCode: aggregate.status !== 'passed' || !policyPassed || !androidPassed || !iosPassed || !pwaPassed ? 1 : 0,
|
|
671
|
+
exitCode: aggregate.status !== 'passed' || !policyPassed || !androidPassed || !iosPassed || !pwaPassed || !expose.passed ? 1 : 0,
|
|
666
672
|
lines, changedFiles: selection.files, testFiles, unverified: scope.unverified,
|
|
667
673
|
documentPaths: scope.documentPaths, documentsWithoutDerivedTests: scope.documentsWithoutDerivedTests,
|
|
668
674
|
};
|
|
@@ -674,7 +680,7 @@ export function runSelfGateCli(cwd: string, options: SelfGateCliOptions = {}, de
|
|
|
674
680
|
lines.push(formatPartialObservationNote(unrunImporterTotal ?? 0, `tests: pass (${testFiles.length} files)`));
|
|
675
681
|
const policyPassed = runPolicyGates(selection.files, cwd, lines, deps);
|
|
676
682
|
logGateCliBaseline(cwd, { ...ZERO_GATE_BASELINE, unrunImporterTotal, lookupFailed: importerLookupFailed });
|
|
677
|
-
return { exitCode: policyPassed && androidPassed && iosPassed && pwaPassed ? 0 : 1, lines, changedFiles: selection.files, testFiles, unverified: scope.unverified, documentPaths: scope.documentPaths, documentsWithoutDerivedTests: scope.documentsWithoutDerivedTests };
|
|
683
|
+
return { exitCode: policyPassed && androidPassed && iosPassed && pwaPassed && expose.passed ? 0 : 1, lines, changedFiles: selection.files, testFiles, unverified: scope.unverified, documentPaths: scope.documentPaths, documentsWithoutDerivedTests: scope.documentsWithoutDerivedTests };
|
|
678
684
|
}
|
|
679
685
|
|
|
680
686
|
if (worktreeLog.includes('deterministic environment setup failed')) lines.push(worktreeLog);
|
|
@@ -695,6 +701,6 @@ export function runSelfGateCli(cwd: string, options: SelfGateCliOptions = {}, de
|
|
|
695
701
|
});
|
|
696
702
|
lines.push(formatPartialObservationNote(unrunImporterTotal ?? 0, formatGateBaselineNote(report, unrunImporterTotal ?? 0)));
|
|
697
703
|
const policyPassed = runPolicyGates(selection.files, cwd, lines, deps);
|
|
698
|
-
const exitCode = !androidPassed || !iosPassed || !pwaPassed || !policyPassed || report.introduced > 0 || report.unknown > 0 || report.preconditionUnmet > 0 ? 1 : 0;
|
|
704
|
+
const exitCode = !androidPassed || !iosPassed || !pwaPassed || !policyPassed || !expose.passed || report.introduced > 0 || report.unknown > 0 || report.preconditionUnmet > 0 ? 1 : 0;
|
|
699
705
|
return { exitCode, lines, changedFiles: selection.files, testFiles, unverified: scope.unverified, documentPaths: scope.documentPaths, documentsWithoutDerivedTests: scope.documentsWithoutDerivedTests };
|
|
700
706
|
}
|