elanous 0.2.19 → 0.2.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/{J5H7BvqgSI8DUjtRRcA_S → Bam1YziK2iH9bN4K0utGk}/_buildManifest.js +1 -1
- package/apps/pwa/out/_next/static/chunks/{4323.c5dfb6f503304775.js → 1268.0840ff91e083fcbd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1307-77fad80cf54477c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1612-f60c0c93ec773f81.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1844-9ec4b1d12f6e80eb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1948.15f356b15a625597.js +5 -0
- package/apps/pwa/out/_next/static/chunks/195-fb1d7690bf30bab4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1986-33e13d648a9df765.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2977-e38442e86289f1d3.js → 211-9c9090c59e2c5907.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2116-6429c188eb846ab8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +1 -0
- package/apps/pwa/out/_next/static/chunks/224-e7be8f762fe74986.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2489-ff55a35b202c0f94.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2537-6c289376caa88ca1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2549-89c518a4c8ed4d00.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2699-325cc923ba7b8631.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/3857-995ea9f5ef33d71e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4144-fad095b48a1862ee.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4565-3d7e3fdeb43686a8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4795-f372490b178583fc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5755-ea7ed88eb956f47b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5977-94ed6f78e9814b2b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{4847.56f5bc454fbcc827.js → 6110.1297d4093fdfbb0d.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/6158.af2e93b7adff709b.js +2 -0
- package/apps/pwa/out/_next/static/chunks/6877-74399f69c7da1433.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1470.98a6ab61ccb4131c.js → 726.83532d69452ee24b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6062-73e68343a9814f6a.js → 7327-2d69fb321ba73389.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{8870-9614a213b990b10b.js → 7748-6e51bfe927019f5c.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/7797-df5aa0381fdc078f.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/8401-35e94e9091f5eb8d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8695-846034ce44852c70.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9103-60fbef33e66f0a46.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9247-c1753a059e7bac08.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7647.2fcd30d894d5ee30.js → 935.4ddaa99fc1d1126c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{ae787e4c-e5b1ef00b2dbceca.js → a9f2b54d-33c0689b70ed8cf3.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-c855c24de7db74fe.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-85b316bb4235980b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-9b2b26a33cb2cd42.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-c9955fde67b1bcb5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-7a852956c74015a3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-c5a5c621be0e04ae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-4ba4abe096b6c2a8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-575d4a8b4f7becb2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-f5d56ff5f45ad164.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-2260e9eef0357421.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-7c058069c5e7fa63.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-dc5da0b8a2edb8a4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-466bf0ee16ad1432.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-a23bc71713bcc08f.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-5b29d9b06ceadade.js → page-ed4a6f799f005d09.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-9cd565005b93aca6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-55f0f40c6db95f15.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-474cb5888ea0e8f3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-35bfec2e631d3eae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-9ca21f9ad3208a61.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-f9ccb1b1adbea14d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-ec102c9b8b691e37.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-a9c2cfa8dda922a5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-6da35144878feb6d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-f6055495e9ed7ea8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-25204694fa9984a5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-a60e035f151d84d3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-189c587f36874538.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-c56634bde705a65d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-db03ae3589a81c9e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-4a6463b97f0ad94f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-75bffecc9aa1004e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-a8d2fe0c669e0de4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-31d5b875760bb62d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-c26fa10aac6c21c4.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-68793c6e10331d66.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-46d5201d2207a511.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-d81c07a7b4f618ae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-be1c4e8697f22360.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-cf0a4b4abead71ea.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-a4717e2c4bf7a28f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-0fd895fab9b62c1b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-8b6c1454a8e8f306.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-c6e4364d9de8f717.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-0acc8e198f7b0dfb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-34882c84f84021c5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-0506a1e0e4bdb2f5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8046cadf-211ca3c0e00f2b72.js → f6231284-45726550aba08fca.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{2ea96958.e8c7e112de50b067.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-c260571513bf43c2.js +1 -0
- package/apps/pwa/out/_next/static/css/c23d959d3ee15e57.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +23 -23
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +23 -23
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +23 -23
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +22 -22
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +22 -22
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +23 -23
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +22 -22
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +23 -23
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +23 -23
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +22 -22
- 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 -1
- package/apps/pwa/out/loops/index.txt +22 -22
- 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/demo/editor-recipes.yaml +22 -0
- package/graphs/retro/daily.yaml +25 -0
- package/graphs/retro/recipes.yaml +24 -0
- package/package.json +1 -1
- package/scripts/ask-marker-check.ts +3 -1
- package/scripts/land-global-checks.ts +89 -0
- package/scripts/lib/pch-regress-map.ts +2 -1
- package/scripts/release-loop/gate-node.ts +296 -25
- package/scripts/release-loop/gate-timing.ts +116 -7
- package/scripts/release-loop/preflight.ts +6 -4
- package/scripts/release-loop/publish-window.ts +141 -0
- package/scripts/release-loop/release-docs.ts +1 -4
- package/scripts/release-loop/release-universe.ts +38 -0
- package/scripts/release-loop/resume-release.ts +4 -1
- package/scripts/release-loop/unattended-release.ts +10 -2
- package/scripts/release-story/draft.ts +5 -1
- package/scripts/retro/shadow-compare.ts +71 -0
- package/scripts/rhythm/daily.ts +6 -1
- package/src/agent-mission/driver.ts +34 -6
- package/src/autopilot/build/llm-conflict-merge.ts +132 -7
- package/src/autopilot/build/merge-intent.ts +135 -7
- package/src/away/release-watch.ts +20 -3
- package/src/cli/config-drift.ts +513 -0
- package/src/cli/log-event-names.ts +4 -0
- package/src/cli/pr-cli.ts +20 -0
- package/src/cli/release-cli.ts +20 -5
- package/src/cli/retro-cli.ts +78 -0
- package/src/cli/tasks-cli.ts +94 -8
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +5 -4
- package/src/dashboard/slash-runtime/help-from-registry.ts +57 -31
- package/src/domains/outbound-log-probe.ts +6 -1
- package/src/graph-runner/graph-run-api.ts +205 -0
- package/src/graph-runner/runner.ts +11 -1
- package/src/harness/harness-cli-command.ts +419 -52
- package/src/harness/harness-queue.ts +315 -25
- package/src/harness/harness-salvage-cli.ts +62 -0
- package/src/hq/fence-outcomes.ts +63 -0
- package/src/hq/fence-wrapper.ts +33 -0
- package/src/hq/hq.ts +11 -1
- package/src/index.ts +69 -6
- package/src/maturity/feature-maturity.ts +2 -2
- package/src/nexus/api/graph-kinds.ts +10 -4
- package/src/nexus/api/graphs-api.ts +178 -6
- package/src/nexus/api/http-server.ts +38 -3
- package/src/nexus/api/loop-edges.ts +4 -2
- package/src/nexus/api/ops-api.ts +22 -7
- package/src/nexus/api/ops-board-route.ts +48 -0
- package/src/onboarding/entry-hints.ts +5 -0
- package/src/onboarding.ts +24 -6
- package/src/release-loop/feature-store.ts +14 -11
- package/src/release-loop/gate-shards.ts +158 -0
- package/src/release-loop/release-schedule.ts +6 -2
- package/src/retro/daily.ts +372 -0
- package/src/seat-loop/op-decide.ts +173 -0
- package/src/seat-loop/seat-loop.ts +60 -3
- package/src/seat-loop/seat-report.ts +58 -3
- package/src/self-dev/ask-launch-flow.ts +5 -4
- package/src/self-dev/ask-launch-io.ts +49 -1
- package/src/self-dev/ask-path-candidates.ts +40 -2
- package/src/self-dev/draft-sweep.ts +371 -16
- package/src/self-dev/draft-triage-rules.ts +19 -4
- package/src/self-dev/pr-preview.ts +84 -16
- package/src/self-implement/blocked-draft-policy.ts +101 -0
- package/src/self-implement/execution-clarification.ts +11 -0
- package/src/self-implement/gate-env-retry.ts +83 -0
- package/src/self-implement/orchestrator.ts +218 -5
- package/src/self-implement/run-stop.ts +123 -0
- package/src/self-implement/run-ttl.ts +258 -0
- package/src/self-implement/salvage-retention.ts +123 -0
- package/src/self-implement/seams.ts +4 -0
- package/src/self-implement/self-implement-cli.ts +2 -0
- package/src/self-implement/sibling-resync.ts +223 -0
- package/src/self-implement/stop-autoheal.ts +204 -0
- package/src/task-agent/board.ts +1 -1
- package/src/task-agent/card-evidence.ts +99 -10
- package/src/task-agent/judge.ts +7 -0
- package/src/task-agent/live-moves.ts +120 -20
- package/src/task-agent/mission.ts +1 -1
- package/src/task-agent/shadow.ts +10 -1
- package/src/task-agent/task-hand.ts +7 -2
- package/src/task-orchestrator/surfaces/pod-command-job.ts +19 -5
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +4 -0
- package/src/task-orchestrator/surfaces/pod-install-slots.ts +89 -0
- package/src/task-orchestrator/surfaces/pod-lease.ts +93 -10
- package/src/task-orchestrator/surfaces/pod-pool.ts +147 -34
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +19 -3
- package/src/telegram-commands.ts +41 -0
- package/src/testing/real-cli-spawn.ts +106 -0
- package/src/user-config.ts +56 -3
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1157-cba0e8553d35a980.js +0 -1
- package/apps/pwa/out/_next/static/chunks/171-23a877ed11720de9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2336-e830c777eb6bce04.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2375-e061bb24cd2b5cde.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2415.ed6428ff334c7ce2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3094.67339318c7f72a3a.js +0 -2
- package/apps/pwa/out/_next/static/chunks/338-ceb956612dcfaa86.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3395-835e4ceaa6335ba4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3403.4bc97b4066945a94.js +0 -5
- package/apps/pwa/out/_next/static/chunks/3441-be82e3595aef3a20.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3689-89905039bf3e7257.js +0 -1
- package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3822-6f268bc26c6a84ac.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4457-d7df81c90f9d0268.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4551-77dadae76f6cbace.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4978.6fd93b974441e87f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5012-f746f6549b869f91.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5791-bb82743fa6299562.js +0 -1
- package/apps/pwa/out/_next/static/chunks/62-5bfed1e23790b745.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6320-9bc8561fde416e07.js +0 -1
- package/apps/pwa/out/_next/static/chunks/649-ca96d1c5d09e02b8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6726-85a7db59b288c53f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6735-33267c4630d0d963.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7633-141682015873beb6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8067-143791231c99bd02.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8430-a10cfba8ef282d4e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9454-12c2f30dea62291c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/976-4a9b4ebdca7c739a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9989-15cdf568bafa9fc8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-deef5ee191830f14.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-b193da485f9a14b2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-b6e1a79df0e214f4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-e84df4d907f703c7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-a5fb9a5db89ae4ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-1499f481d7fc52d6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-c7cf753aeb648790.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-4118469e514e10fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-e968f7630654332a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-505915052f947022.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-c2f962fb4a3b06c6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-7b5ed0bd8a589ec4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-cf8193250dd42dd4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-cf3f5dfa35dea8e9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-a15e7fcc283e9ca7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-d2915e7b70ff3d0e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-b70cd005d9169e25.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-2595f0eba29f57e1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-c776322454156e36.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-77bbd17f4435aa10.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-049689e611ecc936.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-85954a4fb09c44eb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-e773e552e1068ee6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-5633c4bfa7684b4a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-d829b92bd01b4994.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-061c317fc771becf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-3a45ece8865ed4b6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-0be990618c0d56bd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-bfea4865dc244b73.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-0d6ba8e337741484.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-76a88b55d816482e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-bba1542df59f61ce.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-4185c95689bb372e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-b699e1a8d5d34b8b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-c225e338c31bf274.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-449353f70507ca1f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-90b889714102476d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-f1ee7fbeb7f96ddc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-d59a95e1434dfaa0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-71491a23414d1a47.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-de7112bdb65fa4fe.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-feb4d0d88079660d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-f17d887b40dd6b1d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-8da77be2192bcd5d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-72c3b9a5e9582b29.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3afe4c8d0c48c39.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-b9308f2d8a931531.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-6f35f9d892fa630b.js +0 -1
- package/apps/pwa/out/_next/static/css/85f53c6f35d11382.css +0 -1
- /package/apps/pwa/out/_next/static/{J5H7BvqgSI8DUjtRRcA_S → Bam1YziK2iH9bN4K0utGk}/_ssgManifest.js +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { execFileSync, spawn } from 'node:child_process';
|
|
1
|
+
import { execFile, execFileSync, spawn } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
2
3
|
import { dlopen, FFIType } from 'bun:ffi';
|
|
3
4
|
import { createHash } from 'node:crypto';
|
|
4
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
5
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs';
|
|
5
6
|
import { constants as osConstants, hostname } from 'node:os';
|
|
6
7
|
import { basename, dirname, join, resolve, sep } from 'node:path';
|
|
7
8
|
import { findGitDir } from '../git-fs/locate.js';
|
|
@@ -16,14 +17,15 @@ import { Command, Option } from 'commander';
|
|
|
16
17
|
import { runGitCommand } from '../git-fs/runner.js';
|
|
17
18
|
import { GOAL_TYPES, lintGoalFile, parseGoalId, parseGoalType, resolveGoalAuthorGrade, tracedPathReferences, type GoalAuthorGrade, type GoalAuthorGradeSelection, type GoalType } from '../self-implement/goal-author.js';
|
|
18
19
|
import { templateForGoalType } from '../self-implement/graph-templates.js';
|
|
19
|
-
import { listRunLedgers, loadFederatedRunLedger, loadRunLedger, runLedgerDir, type RunLedgerMatch } from '../self-implement/run-ledger.js';
|
|
20
|
+
import { listRunLedgers, loadFederatedRunLedger, loadRunLedger, queryRunScreenKey, runLedgerDir, type RunLedgerMatch } from '../self-implement/run-ledger.js';
|
|
21
|
+
import { classifyPodExitStop, recordRunStop, runStopRecorded, RUN_STOP_EVENT } from '../self-implement/run-stop.js';
|
|
20
22
|
import { resolveChildLlmEffort, resolveImplementationChildModel } from '../self-dev/dev-cli.js';
|
|
21
23
|
import { resolveHarnessTarget } from '../self-implement/harness-target-options.js';
|
|
22
24
|
import { queryRunningRuns } from '../self-implement/running-runs.js';
|
|
23
25
|
import { DevPipelineError } from '../self-dev/dev-pipeline.js';
|
|
24
|
-
import { runDraftSweep, sweepFailureReason, type DraftSweepAdapters, type DraftSweepResult, type SweepDraft, type SweepMergedPr, type SweepReviewGate } from '../self-dev/draft-sweep.js';
|
|
26
|
+
import { DRAFT_SWEEP_CONCURRENCY, mapBounded, runDraftSweep, sweepFailureReason, type DraftSweepAdapters, type DraftSweepResult, type SweepDraft, type SweepMergedPr, type SweepReviewGate } from '../self-dev/draft-sweep.js';
|
|
25
27
|
import { debug } from '../debug/log.js';
|
|
26
|
-
import { installHarnessSalvageCommand } from './harness-salvage-cli.js';
|
|
28
|
+
import { installHarnessSalvageCommand, installHarnessSalvageRetentionCommand } from './harness-salvage-cli.js';
|
|
27
29
|
import { decideNestedElanousLaunch, readNestedElanousDepth } from './nested-elanous-policy.js';
|
|
28
30
|
import { dispatchTask, type DispatchTaskInput, type DispatchTaskDeps } from '../execution-loop/dispatch-task.js';
|
|
29
31
|
import { launchRequestId, preLaunchGate, type PreLaunchGateDeps } from '../execution-loop/launch-gate.js';
|
|
@@ -32,6 +34,9 @@ import { CODEX_PROVIDER, GROK_PROVIDER, decideBudget, decideLaunchBudget, readBu
|
|
|
32
34
|
import { DEFAULT_FALLBACK_CHAIN } from '../oauth/fallback-chain.js';
|
|
33
35
|
import { installDeliverableVerifyCliCommand, type InstallDeliverableVerifyCliDeps } from './deliverable-verify-cli.js';
|
|
34
36
|
import { getUserConfig } from '../user-config.js';
|
|
37
|
+
import type { SiblingResyncAdapters, SiblingResyncResult, SiblingRunTarget } from '../self-implement/sibling-resync.js';
|
|
38
|
+
import type { RunTtlAdapters, RunTtlResult } from '../self-implement/run-ttl.js';
|
|
39
|
+
import type { ControlMemoPayload } from './control-inbox.js';
|
|
35
40
|
import { resolveRepositoryName } from './repository-name.js';
|
|
36
41
|
import { installHarnessCliSinkHook } from './harness-cli-sink.js';
|
|
37
42
|
import { addHarnessQueue, HarnessQueueDuplicateError, harnessQueueReceiptPath, listHarnessQueue, queueSeatForCwd, reconcileHarnessQueue, removeHarnessQueue, setHarnessQueuePriority, tickHarnessQueue, type HarnessQueueDeps, type QueueItem, type QueueSeat } from './harness-queue.js';
|
|
@@ -670,11 +675,40 @@ async function onPod(opts: unknown, entrance: 'cli-harness-ask' | 'cli-harness-s
|
|
|
670
675
|
const classified = classifyHarnessPodExit({ status }, output, { ...(runId ? { runId } : {}) });
|
|
671
676
|
recordClassifiedHarnessPodExit(runId ?? harnessPodRunId(output), classified.reason, status,
|
|
672
677
|
{ entrance, seat: o.seat, output });
|
|
678
|
+
recordHarnessPodStop(runId ?? harnessPodRunId(output), status, classified);
|
|
673
679
|
for (const line of classified.lines) console.error(line);
|
|
674
680
|
process.exitCode = status;
|
|
675
681
|
}
|
|
676
682
|
}
|
|
677
683
|
|
|
684
|
+
/**
|
|
685
|
+
* STOP-RECORD — a nonzero Pod exit appends one `stop` line to the host run ledger (additive: the
|
|
686
|
+
* `exit-classified` log and the existing ledger stay as they are). A failed Pod Job loses its in-Pod
|
|
687
|
+
* ledger, so without this line the run reads `outcome: unknown`. Skips when a stop is already recorded.
|
|
688
|
+
*/
|
|
689
|
+
export function recordHarnessPodStop(
|
|
690
|
+
runId: string | undefined,
|
|
691
|
+
status: number,
|
|
692
|
+
classified: { reason: string; lines: string[] },
|
|
693
|
+
deps: { loadLedger?: typeof loadRunLedger; record?: typeof recordRunStop } = {},
|
|
694
|
+
): boolean {
|
|
695
|
+
if (!runId) return false;
|
|
696
|
+
try {
|
|
697
|
+
if (runStopRecorded(runId)) return false;
|
|
698
|
+
if ((deps.loadLedger ?? loadRunLedger)(runId)?.some((entry) => entry.event === RUN_STOP_EVENT)) return false;
|
|
699
|
+
// The classified line is our own one-line verdict («Pod 안 자식이 실패했다 — OOMKilled»), not the raw output tail.
|
|
700
|
+
const verdict = classified.lines[0]?.split(' — ').slice(1).join(' — ').trim();
|
|
701
|
+
(deps.record ?? recordRunStop)({
|
|
702
|
+
runId, site: 'pod-exit', class: classifyPodExitStop(classified.reason),
|
|
703
|
+
cause: `Pod exit ${status}: ${classified.reason}${verdict ? ` — ${verdict}` : ''}`,
|
|
704
|
+
evidenceRef: `logs:harness.pod/exit-classified:${runId}`,
|
|
705
|
+
nextMove: classified.reason === 'no-launch' ? 'Check Pod launch gate and pool, then relaunch'
|
|
706
|
+
: 'Inspect Pod exit classification and harvest the branch before relaunch',
|
|
707
|
+
});
|
|
708
|
+
return true;
|
|
709
|
+
} catch { return false; /* ledger observation cannot change Pod output or exit */ }
|
|
710
|
+
}
|
|
711
|
+
|
|
678
712
|
const LAUNCH_BUDGET_PROVIDER: Readonly<Record<string, string>> = { 'codex-rotate': CODEX_PROVIDER, grok: GROK_PROVIDER };
|
|
679
713
|
|
|
680
714
|
/**
|
|
@@ -1801,7 +1835,7 @@ type GithubPull = {
|
|
|
1801
1835
|
title: string;
|
|
1802
1836
|
draft: boolean;
|
|
1803
1837
|
state: 'open' | 'closed';
|
|
1804
|
-
head: { ref: string };
|
|
1838
|
+
head: { ref: string; sha?: string };
|
|
1805
1839
|
base?: { ref: string };
|
|
1806
1840
|
labels: Array<{ name: string }>;
|
|
1807
1841
|
created_at: string;
|
|
@@ -1844,6 +1878,147 @@ export async function supersedeMergedGoalDrafts(
|
|
|
1844
1878
|
await supersedeDraftsOnMerge({ repository, merged, adapters: githubDraftSweepAdapters(execute, git, cwd) });
|
|
1845
1879
|
}
|
|
1846
1880
|
|
|
1881
|
+
/**
|
|
1882
|
+
* SIBLING-RESYNC-ON-MERGE — merge-time adapter: open harness PRs whose files intersect the merged PR get a
|
|
1883
|
+
* resync memo in their live run's control inbox, or the quiet `elanous:needs-rebase` marker when no run is alive.
|
|
1884
|
+
*/
|
|
1885
|
+
export async function resyncMergedSiblings(
|
|
1886
|
+
number: number, cwd: string,
|
|
1887
|
+
execute: GhExecute = executeGh, git: DraftSweepGitExecute = runGitCommand,
|
|
1888
|
+
overrides: Partial<Pick<SiblingResyncAdapters, 'resolveRun' | 'sendResync'>> & {
|
|
1889
|
+
maxFetch?: number; fetchConcurrency?: number; log?: (category: string, event: string, data: Record<string, unknown>) => void;
|
|
1890
|
+
} = {},
|
|
1891
|
+
): Promise<SiblingResyncResult> {
|
|
1892
|
+
const remote = git(cwd, ['config', '--get', 'remote.origin.url']);
|
|
1893
|
+
const repository = remote.status === 0 ? githubRemoteRepository(remote.stdout) : undefined;
|
|
1894
|
+
if (!repository) throw new Error('Merge repository unavailable');
|
|
1895
|
+
const sweep = githubDraftSweepAdapters(execute, git, cwd);
|
|
1896
|
+
const [{ requestSiblingResync }, inbox, pod] = await Promise.all([
|
|
1897
|
+
import('../self-implement/sibling-resync.js'),
|
|
1898
|
+
import('./control-inbox.js'),
|
|
1899
|
+
import('./self-send-target.js'),
|
|
1900
|
+
]);
|
|
1901
|
+
const maxFetch = overrides.maxFetch ?? (() => {
|
|
1902
|
+
try { return getUserConfig().harness?.siblingResync?.maxFetch; } catch { return undefined; }
|
|
1903
|
+
})();
|
|
1904
|
+
let ledgerMatches: readonly RunLedgerMatch[] | undefined;
|
|
1905
|
+
return requestSiblingResync({
|
|
1906
|
+
merged: { number },
|
|
1907
|
+
...(maxFetch !== undefined ? { maxFetch } : {}),
|
|
1908
|
+
...(overrides.fetchConcurrency !== undefined ? { fetchConcurrency: overrides.fetchConcurrency } : {}),
|
|
1909
|
+
...(overrides.log ? { log: overrides.log } : {}),
|
|
1910
|
+
adapters: {
|
|
1911
|
+
mergedFiles: async () => sweep.getPrFiles!(repository, number),
|
|
1912
|
+
listOpenPrs: async () => {
|
|
1913
|
+
const all: GithubPull[] = [];
|
|
1914
|
+
for (let page = 1; page <= 100; page++) {
|
|
1915
|
+
const batch = githubPullsPage(repository, 'open', page, execute);
|
|
1916
|
+
if (!Array.isArray(batch) || batch.length > 100) throw new Error('Incomplete open PR inventory');
|
|
1917
|
+
all.push(...batch);
|
|
1918
|
+
if (batch.length < 100) break;
|
|
1919
|
+
if (page === 100) throw new Error('Open PR inventory exceeded pagination limit');
|
|
1920
|
+
}
|
|
1921
|
+
// No per-PR call here: the core prefilters, prioritizes and caps file fetches (#24893 review should-fix ①).
|
|
1922
|
+
return all.filter((pr) => pr.base?.ref === 'main' && pr.head?.ref?.startsWith('self-impl/') && pr.number !== number)
|
|
1923
|
+
.map((pr) => ({ number: pr.number, branch: pr.head.ref, labels: pr.labels.map((label) => label.name) }));
|
|
1924
|
+
},
|
|
1925
|
+
prFiles: async (prNumber) => sweep.getPrFiles!(repository, prNumber),
|
|
1926
|
+
resolveRun: overrides.resolveRun ?? (async (pr) => {
|
|
1927
|
+
ledgerMatches ??= listRunLedgers().matches;
|
|
1928
|
+
return resolveOwningRunForPr(ledgerMatches, repository, pr.number, 'sibling-resync');
|
|
1929
|
+
}),
|
|
1930
|
+
sendResync: overrides.sendResync ?? (({ spaceId }, memo) => deliverResyncMemo(spaceId, memo, inbox, pod)),
|
|
1931
|
+
addMarker: async (prNumber, label) => sweep.setLabels(repository, prNumber, { add: label, remove: [] }),
|
|
1932
|
+
},
|
|
1933
|
+
});
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
/** The PR's owning run, alive only when running and its control screen resolves (shared by SIBLING-RESYNC and RUN-TTL). */
|
|
1937
|
+
function resolveOwningRunForPr(ledgerMatches: readonly RunLedgerMatch[], repository: string, number: number,
|
|
1938
|
+
caller: string): SiblingRunTarget {
|
|
1939
|
+
const runId = currentDraftSweepRunId(ledgerMatches, repository, number);
|
|
1940
|
+
if (!runId) return { alive: false, reason: 'owner-run-unknown' };
|
|
1941
|
+
const status = queryRunningRuns({ runIds: [runId], caller }).entries.find((entry) => entry.runId === runId)?.status;
|
|
1942
|
+
if (status !== 'running' && status !== 'probable-running') return { alive: false, runId, reason: `run-${status ?? 'not-running'}` };
|
|
1943
|
+
const screen = queryRunScreenKey(runId).screenKey;
|
|
1944
|
+
if (!screen) return { alive: false, runId, reason: 'screen-key-unresolved' };
|
|
1945
|
+
return { alive: true, runId, spaceId: screen };
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
/** One supervisor memo into a live run's control inbox — Pod runs through their fragment, host runs through the file inbox. */
|
|
1949
|
+
function deliverResyncMemo(spaceId: string, memo: ControlMemoPayload,
|
|
1950
|
+
inbox: typeof import('./control-inbox.js'), pod: typeof import('./self-send-target.js')): void {
|
|
1951
|
+
if (pod.readPodFragment(spaceId)) {
|
|
1952
|
+
if (!pod.dispatchPodSelfSend(spaceId, { memo })) throw new Error('pod fragment unreachable');
|
|
1953
|
+
return;
|
|
1954
|
+
}
|
|
1955
|
+
inbox.enqueueControlMemo(spaceId, memo, { explicitInboxDir: inbox.controlInboxPath(spaceId) });
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
const execFileAsync = promisify(execFile);
|
|
1959
|
+
const executeGhAsync = async (args: string[]): Promise<string> => (await execFileAsync('gh', args, {
|
|
1960
|
+
encoding: 'utf8', timeout: 30_000, maxBuffer: 16 * 1024 * 1024, env: process.env,
|
|
1961
|
+
})).stdout;
|
|
1962
|
+
|
|
1963
|
+
/** RUN-TTL adapters: open self-impl PRs, head commit time (async, bounded by the core), owning run, memo, label. */
|
|
1964
|
+
export async function githubRunTtlAdapters(repository: string, execute: GhExecute = executeGh,
|
|
1965
|
+
executeAsync: (args: string[]) => Promise<string> = executeGhAsync, root: string = effectiveInstanceRoot(),
|
|
1966
|
+
shared: Partial<Pick<GithubDraftSweepAdapters, 'openPullRequests' | 'runLedgerMatches'>> = {}): Promise<RunTtlAdapters> {
|
|
1967
|
+
const [inbox, pod] = await Promise.all([import('./control-inbox.js'), import('./self-send-target.js')]);
|
|
1968
|
+
const ledger = join(root, 'run-ttl', 'memos.jsonl');
|
|
1969
|
+
let sent: Set<string> | undefined;
|
|
1970
|
+
const loadSent = (): Set<string> => {
|
|
1971
|
+
if (sent) return sent;
|
|
1972
|
+
sent = new Set<string>();
|
|
1973
|
+
try {
|
|
1974
|
+
for (const line of readFileSync(ledger, 'utf8').split('\n')) {
|
|
1975
|
+
if (!line.trim()) continue;
|
|
1976
|
+
try { const key = (JSON.parse(line) as { key?: unknown }).key; if (typeof key === 'string') sent.add(key); } catch { /* skip */ }
|
|
1977
|
+
}
|
|
1978
|
+
} catch (error) {
|
|
1979
|
+
// Only a missing journal is «nothing sent yet»; an unreadable one cannot prove «once».
|
|
1980
|
+
if ((error as NodeJS.ErrnoException)?.code !== 'ENOENT') { sent = undefined; throw error; }
|
|
1981
|
+
}
|
|
1982
|
+
return sent;
|
|
1983
|
+
};
|
|
1984
|
+
let ledgerMatches: readonly RunLedgerMatch[] | undefined;
|
|
1985
|
+
return {
|
|
1986
|
+
listOpenPrs: async () => {
|
|
1987
|
+
const all: GithubPull[] = [];
|
|
1988
|
+
// Inside the draft sweep the open listing is already in memory — one listing per sweep (DRAFT-SWEEP-SLOW).
|
|
1989
|
+
if (shared.openPullRequests) all.push(...await shared.openPullRequests(repository));
|
|
1990
|
+
else for (let page = 1; page <= 100; page++) {
|
|
1991
|
+
const batch = githubPullsPage(repository, 'open', page, execute);
|
|
1992
|
+
if (!Array.isArray(batch) || batch.length > 100) throw new Error('Incomplete open PR inventory');
|
|
1993
|
+
all.push(...batch);
|
|
1994
|
+
if (batch.length < 100) break;
|
|
1995
|
+
if (page === 100) throw new Error('Open PR inventory exceeded pagination limit');
|
|
1996
|
+
}
|
|
1997
|
+
return all.filter((pr) => pr.base?.ref === 'main' && pr.head?.ref?.startsWith('self-impl/') && typeof pr.head.sha === 'string')
|
|
1998
|
+
.map((pr) => ({ number: pr.number, branch: pr.head.ref, labels: pr.labels.map((label) => label.name),
|
|
1999
|
+
createdAt: pr.created_at, headSha: pr.head.sha! }));
|
|
2000
|
+
},
|
|
2001
|
+
lastCommitAt: async (pr) => {
|
|
2002
|
+
const rows = JSON.parse(await executeAsync(['api', `repos/${repository}/commits?sha=${pr.headSha}&per_page=1`])) as
|
|
2003
|
+
Array<{ sha?: string; commit?: { committer?: { date?: string } } }>;
|
|
2004
|
+
const head = Array.isArray(rows) ? rows[0] : undefined;
|
|
2005
|
+
return head?.sha === pr.headSha ? head.commit?.committer?.date : undefined;
|
|
2006
|
+
},
|
|
2007
|
+
resolveRun: async (pr) => {
|
|
2008
|
+
ledgerMatches ??= shared.runLedgerMatches?.() ?? listRunLedgers().matches;
|
|
2009
|
+
return resolveOwningRunForPr(ledgerMatches, repository, pr.number, 'run-ttl');
|
|
2010
|
+
},
|
|
2011
|
+
sendMemo: ({ spaceId }, memo) => deliverResyncMemo(spaceId, memo, inbox, pod),
|
|
2012
|
+
addLabel: async (prNumber, label) => { execute(['pr', 'edit', String(prNumber), '--repo', repository, '--add-label', label]); },
|
|
2013
|
+
memoSent: (key) => loadSent().has(key),
|
|
2014
|
+
recordMemoSent: (key) => {
|
|
2015
|
+
mkdirSync(dirname(ledger), { recursive: true });
|
|
2016
|
+
appendFileSync(ledger, `${JSON.stringify({ key, at: new Date().toISOString() })}\n`);
|
|
2017
|
+
loadSent().add(key);
|
|
2018
|
+
},
|
|
2019
|
+
};
|
|
2020
|
+
}
|
|
2021
|
+
|
|
1847
2022
|
function ghJson<T>(args: string[], execute: GhExecute): T {
|
|
1848
2023
|
return JSON.parse(execute(args)) as T;
|
|
1849
2024
|
}
|
|
@@ -1908,7 +2083,94 @@ function githubRemoteRepository(url: string): string | undefined {
|
|
|
1908
2083
|
return match?.[1];
|
|
1909
2084
|
}
|
|
1910
2085
|
|
|
1911
|
-
|
|
2086
|
+
/** One PR's sweep evidence read in a GraphQL batch. A `null` field was not complete in the batch ⇒ its REST path is used. */
|
|
2087
|
+
interface GraphqlPrDetail {
|
|
2088
|
+
readonly files: string[] | null;
|
|
2089
|
+
readonly comments: string[] | null;
|
|
2090
|
+
readonly reviews: Array<{ state?: string; body?: string }> | null;
|
|
2091
|
+
readonly headSha: string | null;
|
|
2092
|
+
readonly mergeCommit: { oid: string; message: string } | null;
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
/** PRs per GraphQL request: three nested connections × 100 nodes stays far under the node limit and costs ~1 point. */
|
|
2096
|
+
const DRAFT_SWEEP_GRAPHQL_BATCH = 40;
|
|
2097
|
+
|
|
2098
|
+
type GraphqlConnection<T> = { totalCount?: number; nodes?: T[] | null; pageInfo?: { hasNextPage?: boolean } } | null | undefined;
|
|
2099
|
+
const completeNodes = <T>(connection: GraphqlConnection<T>): T[] | null =>
|
|
2100
|
+
connection && Array.isArray(connection.nodes) && connection.pageInfo?.hasNextPage === false
|
|
2101
|
+
&& (connection.totalCount === undefined || connection.totalCount === connection.nodes.length) ? connection.nodes : null;
|
|
2102
|
+
|
|
2103
|
+
/**
|
|
2104
|
+
* Batched PR evidence (files · issue comments · reviews · head · merge commit) — one GraphQL request per
|
|
2105
|
+
* DRAFT_SWEEP_GRAPHQL_BATCH PRs instead of 3–7 REST calls per PR (DRAFT-SWEEP-SLOW, 10-08: ~1,300 PRs ⇒ >600 s).
|
|
2106
|
+
* Fail-soft: an unreadable batch or PR yields no entry, and the caller falls back to the per-PR REST path.
|
|
2107
|
+
*/
|
|
2108
|
+
async function graphqlPrDetails(repository: string, numbers: readonly number[],
|
|
2109
|
+
executeAsync: (args: string[]) => Promise<string>): Promise<Map<number, GraphqlPrDetail>> {
|
|
2110
|
+
const details = new Map<number, GraphqlPrDetail>();
|
|
2111
|
+
const [owner, name] = repository.split('/');
|
|
2112
|
+
if (!owner || !name || numbers.length === 0) return details;
|
|
2113
|
+
const batches: number[][] = [];
|
|
2114
|
+
for (let index = 0; index < numbers.length; index += DRAFT_SWEEP_GRAPHQL_BATCH) batches.push(numbers.slice(index, index + DRAFT_SWEEP_GRAPHQL_BATCH));
|
|
2115
|
+
let failedBatches = 0;
|
|
2116
|
+
// Fields the batch could not prove complete (each one is read again over REST by the caller).
|
|
2117
|
+
const incompleteFields = { files: 0, comments: 0, reviews: 0 };
|
|
2118
|
+
await mapBounded(batches, DRAFT_SWEEP_CONCURRENCY, async (batch) => {
|
|
2119
|
+
const fields = batch.map((number) => `p${number}: pullRequest(number: ${number}) { number headRefOid `
|
|
2120
|
+
+ 'files(first: 100) { totalCount nodes { path } pageInfo { hasNextPage } } '
|
|
2121
|
+
+ 'comments(first: 100) { totalCount nodes { body } pageInfo { hasNextPage } } '
|
|
2122
|
+
+ 'reviews(first: 100) { totalCount nodes { state body } pageInfo { hasNextPage } } '
|
|
2123
|
+
+ 'mergeCommit { oid message } }').join(' ');
|
|
2124
|
+
const query = `query { repository(owner: ${JSON.stringify(owner)}, name: ${JSON.stringify(name)}) { ${fields} } }`;
|
|
2125
|
+
let repositoryNode: Record<string, unknown> | undefined;
|
|
2126
|
+
try {
|
|
2127
|
+
const parsed = JSON.parse(await executeAsync(['api', 'graphql', '-f', `query=${query}`])) as { data?: { repository?: Record<string, unknown> } };
|
|
2128
|
+
repositoryNode = parsed?.data?.repository ?? undefined;
|
|
2129
|
+
} catch { repositoryNode = undefined; }
|
|
2130
|
+
if (!repositoryNode || typeof repositoryNode !== 'object' || Array.isArray(repositoryNode)) { failedBatches += 1; return; }
|
|
2131
|
+
for (const number of batch) {
|
|
2132
|
+
const pr = repositoryNode[`p${number}`] as {
|
|
2133
|
+
number?: number; headRefOid?: string;
|
|
2134
|
+
files?: GraphqlConnection<{ path?: string }>; comments?: GraphqlConnection<{ body?: string }>;
|
|
2135
|
+
reviews?: GraphqlConnection<{ state?: string; body?: string }>; mergeCommit?: { oid?: string; message?: string } | null;
|
|
2136
|
+
} | null | undefined;
|
|
2137
|
+
if (!pr || pr.number !== number) continue;
|
|
2138
|
+
const files = completeNodes(pr.files);
|
|
2139
|
+
const comments = completeNodes(pr.comments);
|
|
2140
|
+
const reviews = completeNodes(pr.reviews);
|
|
2141
|
+
const detail: GraphqlPrDetail = {
|
|
2142
|
+
files: files && files.every((file) => typeof file?.path === 'string') ? files.map((file) => file.path!) : null,
|
|
2143
|
+
comments: comments && comments.every((comment) => typeof comment?.body === 'string') ? comments.map((comment) => comment.body!) : null,
|
|
2144
|
+
reviews: reviews && reviews.every((review) => review && typeof review === 'object')
|
|
2145
|
+
? reviews.map((review) => ({ state: review.state, body: review.body })) : null,
|
|
2146
|
+
headSha: typeof pr.headRefOid === 'string' && pr.headRefOid ? pr.headRefOid : null,
|
|
2147
|
+
mergeCommit: pr.mergeCommit && typeof pr.mergeCommit.oid === 'string' && typeof pr.mergeCommit.message === 'string'
|
|
2148
|
+
? { oid: pr.mergeCommit.oid, message: pr.mergeCommit.message } : null,
|
|
2149
|
+
};
|
|
2150
|
+
if (detail.files === null) incompleteFields.files += 1;
|
|
2151
|
+
if (detail.comments === null) incompleteFields.comments += 1;
|
|
2152
|
+
if (detail.reviews === null) incompleteFields.reviews += 1;
|
|
2153
|
+
details.set(number, detail);
|
|
2154
|
+
}
|
|
2155
|
+
});
|
|
2156
|
+
if (failedBatches > 0 || details.size < numbers.length || Object.values(incompleteFields).some((count) => count > 0)) {
|
|
2157
|
+
try {
|
|
2158
|
+
debug.log('self-dev.draft-sweep', 'graphql-fallback', { requested: numbers.length, resolved: details.size, failedBatches, incompleteFields });
|
|
2159
|
+
} catch { /* fail-soft */ }
|
|
2160
|
+
}
|
|
2161
|
+
return details;
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2164
|
+
/** GitHub draft-sweep adapters plus the shared inventory the RUN-TTL step reuses (one open-PR listing per sweep). */
|
|
2165
|
+
export type GithubDraftSweepAdapters = DraftSweepAdapters & {
|
|
2166
|
+
/** The sweep's cached open PR listing — RUN-TTL reads it instead of paging the same list again. */
|
|
2167
|
+
openPullRequests(repository: string): Promise<GithubPull[]>;
|
|
2168
|
+
/** The sweep's cached run-ledger scan. */
|
|
2169
|
+
runLedgerMatches(): readonly RunLedgerMatch[];
|
|
2170
|
+
};
|
|
2171
|
+
|
|
2172
|
+
export function githubDraftSweepAdapters(execute: GhExecute = executeGh, git: DraftSweepGitExecute = runGitCommand, cwd = process.cwd(),
|
|
2173
|
+
executeAsync: (args: string[]) => Promise<string> = execute === executeGh ? executeGhAsync : async (args) => execute(args)): GithubDraftSweepAdapters {
|
|
1912
2174
|
const inventory = async (repository: string, state: 'open' | 'closed'): Promise<GithubPull[]> => {
|
|
1913
2175
|
// ⛔ A large repository has tens of thousands of closed PRs (2026-09-28: 21k → «exceeded pagination limit», no sweep at all).
|
|
1914
2176
|
// Merged twins only matter after the oldest open draft was opened, so the closed listing stops at that time.
|
|
@@ -1937,13 +2199,14 @@ export function githubDraftSweepAdapters(execute: GhExecute = executeGh, git: Dr
|
|
|
1937
2199
|
if (!cache.has(repository)) cache.set(repository, inventory(repository, state));
|
|
1938
2200
|
return cache.get(repository)!;
|
|
1939
2201
|
};
|
|
2202
|
+
const ghJsonAsync = async <T>(args: string[]): Promise<T> => JSON.parse(await executeAsync(args)) as T;
|
|
1940
2203
|
// GitHub stops listing a pull request's files at 3,000, and the cut looks like a normal last page.
|
|
1941
2204
|
// A list that reaches the cap cannot prove it is complete, so it is reported as unknown (undefined):
|
|
1942
2205
|
// a draft without a provable file list never qualifies for the all-files-landed close.
|
|
1943
|
-
const filesFor = (repository: string, number: number): string[] | undefined => {
|
|
2206
|
+
const filesFor = async (repository: string, number: number): Promise<string[] | undefined> => {
|
|
1944
2207
|
const files: string[] = [];
|
|
1945
2208
|
for (let page = 1; page <= 100; page++) {
|
|
1946
|
-
const batch =
|
|
2209
|
+
const batch = await ghJsonAsync<Array<{ filename: string }>>(['api', `repos/${repository}/pulls/${number}/files?per_page=100&page=${page}`]);
|
|
1947
2210
|
if (!Array.isArray(batch) || batch.length > 100 || batch.some((file) => typeof file.filename !== 'string')) throw new Error('Incomplete PR file inventory');
|
|
1948
2211
|
files.push(...batch.map((file) => file.filename));
|
|
1949
2212
|
if (files.length >= GITHUB_PR_FILES_LIST_CAP) {
|
|
@@ -1954,18 +2217,31 @@ export function githubDraftSweepAdapters(execute: GhExecute = executeGh, git: Dr
|
|
|
1954
2217
|
}
|
|
1955
2218
|
throw new Error('PR file inventory exceeded pagination limit');
|
|
1956
2219
|
};
|
|
1957
|
-
|
|
2220
|
+
// Batched evidence per PR, read once per adapter (= once per sweep) and shared by listing and review/gate.
|
|
2221
|
+
const detailCache = new Map<string, Promise<GraphqlPrDetail | undefined>>();
|
|
2222
|
+
const prefetchDetails = async (repository: string, numbers: readonly number[]): Promise<void> => {
|
|
2223
|
+
const missing = [...new Set(numbers)].filter((number) => !detailCache.has(`${repository}#${number}`));
|
|
2224
|
+
if (missing.length === 0) return;
|
|
2225
|
+
const fetched = graphqlPrDetails(repository, missing, executeAsync);
|
|
2226
|
+
for (const number of missing) detailCache.set(`${repository}#${number}`, fetched.then((map) => map.get(number)));
|
|
2227
|
+
await fetched;
|
|
2228
|
+
};
|
|
2229
|
+
const detailFor = (repository: string, number: number): Promise<GraphqlPrDetail | undefined> =>
|
|
2230
|
+
detailCache.get(`${repository}#${number}`) ?? Promise.resolve(undefined);
|
|
2231
|
+
const prFiles = async (repository: string, number: number): Promise<string[] | undefined> =>
|
|
2232
|
+
(await detailFor(repository, number))?.files ?? filesFor(repository, number);
|
|
2233
|
+
const latestFileChangesFor = async (draft: SweepDraft, repository: string): Promise<Record<string, string> | undefined> => {
|
|
1958
2234
|
if (!draft.changedFiles?.length) return undefined;
|
|
1959
2235
|
const changes: Record<string, string> = {};
|
|
1960
2236
|
for (let page = 1; page <= 100; page++) {
|
|
1961
|
-
const batch =
|
|
1962
|
-
['api', `repos/${repository}/pulls/${draft.number}/commits?per_page=100&page=${page}`]
|
|
2237
|
+
const batch = await ghJsonAsync<Array<{ sha: string; commit: { committer: { date: string } } }>>(
|
|
2238
|
+
['api', `repos/${repository}/pulls/${draft.number}/commits?per_page=100&page=${page}`]);
|
|
1963
2239
|
if (!Array.isArray(batch) || batch.length > 100) throw new Error('Incomplete draft commit inventory');
|
|
1964
2240
|
for (const commit of batch) {
|
|
1965
2241
|
const changedAt = commit.commit?.committer?.date;
|
|
1966
2242
|
if (!commit.sha || !changedAt || !Number.isFinite(Date.parse(changedAt))) return undefined;
|
|
1967
|
-
const detail =
|
|
1968
|
-
['api', `repos/${repository}/commits/${commit.sha}?per_page=100`]
|
|
2243
|
+
const detail = await ghJsonAsync<{ files?: Array<{ filename: string; previous_filename?: string }> }>(
|
|
2244
|
+
['api', `repos/${repository}/commits/${commit.sha}?per_page=100`]);
|
|
1969
2245
|
// GitHub truncates very large commit file lists; never infer coverage from a partial list.
|
|
1970
2246
|
if (!Array.isArray(detail.files) || detail.files.length >= 100) return undefined;
|
|
1971
2247
|
for (const file of detail.files) {
|
|
@@ -1979,24 +2255,30 @@ export function githubDraftSweepAdapters(execute: GhExecute = executeGh, git: Dr
|
|
|
1979
2255
|
}
|
|
1980
2256
|
return undefined;
|
|
1981
2257
|
};
|
|
1982
|
-
const
|
|
1983
|
-
const head = ghJson<{ head?: { sha?: string } }>(['api', `repos/${repository}/pulls/${draft.number}`], execute);
|
|
1984
|
-
const sha = head?.head?.sha;
|
|
1985
|
-
if (!sha) return undefined;
|
|
2258
|
+
const issueCommentsFor = async (repository: string, number: number, what: 'review' | 'landing'): Promise<string[]> => {
|
|
1986
2259
|
const comments: string[] = [];
|
|
1987
2260
|
for (let page = 1; page <= 100; page++) {
|
|
1988
|
-
const batch =
|
|
1989
|
-
if (!Array.isArray(batch) || batch.length > 100) throw new Error(
|
|
2261
|
+
const batch = await ghJsonAsync<Array<{ body?: string }>>(['api', `repos/${repository}/issues/${number}/comments?per_page=100&page=${page}`]);
|
|
2262
|
+
if (!Array.isArray(batch) || batch.length > 100) throw new Error(`Incomplete ${what} comment inventory`);
|
|
1990
2263
|
comments.push(...batch.map((comment) => comment.body ?? ''));
|
|
1991
|
-
if (batch.length < 100)
|
|
1992
|
-
if (page === 100) throw new Error('Review comment inventory exceeded pagination limit');
|
|
2264
|
+
if (batch.length < 100) return comments;
|
|
1993
2265
|
}
|
|
1994
|
-
|
|
2266
|
+
throw new Error(`${what === 'review' ? 'Review' : 'Landing'} comment inventory exceeded pagination limit`);
|
|
2267
|
+
};
|
|
2268
|
+
const reviewGateForDraft = async (draft: SweepDraft, repository: string): Promise<SweepReviewGate | undefined> => {
|
|
2269
|
+
const cached = await detailFor(repository, draft.number);
|
|
2270
|
+
const sha = cached?.headSha
|
|
2271
|
+
?? (await ghJsonAsync<{ head?: { sha?: string } }>(['api', `repos/${repository}/pulls/${draft.number}`]))?.head?.sha;
|
|
2272
|
+
if (!sha) return undefined;
|
|
2273
|
+
const comments = cached?.comments ?? await issueCommentsFor(repository, draft.number, 'review');
|
|
2274
|
+
const reviews = cached?.reviews
|
|
2275
|
+
?? await ghJsonAsync<Array<{ state?: string; body?: string }>>(['api', `repos/${repository}/pulls/${draft.number}/reviews?per_page=100`]);
|
|
1995
2276
|
if (!Array.isArray(reviews) || reviews.length > 100) throw new Error('Incomplete review inventory');
|
|
1996
2277
|
const text = [...comments, ...reviews.map((review) => review.body ?? '')].join('\n');
|
|
1997
|
-
const status =
|
|
1998
|
-
|
|
1999
|
-
['api', `repos/${repository}/commits/${sha}/check-runs?per_page=100`],
|
|
2278
|
+
const [status, checkRuns] = await Promise.all([
|
|
2279
|
+
ghJsonAsync<{ state?: string }>(['api', `repos/${repository}/commits/${sha}/status`]),
|
|
2280
|
+
ghJsonAsync<{ check_runs?: Array<{ conclusion?: string | null }> }>(['api', `repos/${repository}/commits/${sha}/check-runs?per_page=100`]),
|
|
2281
|
+
]);
|
|
2000
2282
|
if (!checkRuns || !Array.isArray(checkRuns.check_runs)) throw new Error('Incomplete check-run inventory');
|
|
2001
2283
|
const conclusions = checkRuns.check_runs.map((run) => run.conclusion ?? null);
|
|
2002
2284
|
const gatePass = (status?.state === 'success' || status?.state === undefined)
|
|
@@ -2009,15 +2291,42 @@ export function githubDraftSweepAdapters(execute: GhExecute = executeGh, git: Dr
|
|
|
2009
2291
|
...(gatePass && !gateFail ? { gate: 'pass' as const } : gateFail ? { gate: 'fail' as const } : {}),
|
|
2010
2292
|
};
|
|
2011
2293
|
};
|
|
2012
|
-
const landingCommentsFor = (repository: string, number: number): string[] =>
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
}
|
|
2020
|
-
|
|
2294
|
+
const landingCommentsFor = async (repository: string, number: number): Promise<string[]> =>
|
|
2295
|
+
((await detailFor(repository, number))?.comments ?? await issueCommentsFor(repository, number, 'landing'))
|
|
2296
|
+
.filter((body) => /\blanding-verified\b/i.test(body));
|
|
2297
|
+
const mergeCommitMessageFor = async (repository: string, pr: GithubPull): Promise<string | undefined> => {
|
|
2298
|
+
if (!pr.merge_commit_sha) return undefined;
|
|
2299
|
+
const cached = (await detailFor(repository, pr.number))?.mergeCommit;
|
|
2300
|
+
if (cached && cached.oid === pr.merge_commit_sha) return cached.message;
|
|
2301
|
+
return (await ghJsonAsync<{ commit: { message: string } }>(['api', `repos/${repository}/commits/${pr.merge_commit_sha}`])).commit.message;
|
|
2302
|
+
};
|
|
2303
|
+
// The whole draft / merged inventory is enriched once (bounded concurrency) and then paged from memory.
|
|
2304
|
+
const draftRows = new Map<string, Promise<SweepDraft[]>>();
|
|
2305
|
+
const mergedRows = new Map<string, Promise<SweepMergedPr[]>>();
|
|
2306
|
+
const enrichedDrafts = (repository: string): Promise<SweepDraft[]> => {
|
|
2307
|
+
if (!draftRows.has(repository)) draftRows.set(repository, (async () => {
|
|
2308
|
+
const prs = (await listed(open, repository, 'open')).filter((pr) => pr.draft);
|
|
2309
|
+
await prefetchDetails(repository, prs.map((pr) => pr.number));
|
|
2310
|
+
const matches = ledgerMatches ??= listRunLedgers().matches;
|
|
2311
|
+
return mapBounded(prs, DRAFT_SWEEP_CONCURRENCY, async (pr): Promise<SweepDraft> => ({
|
|
2312
|
+
number: pr.number, title: pr.title, branch: pr.head.ref,
|
|
2313
|
+
labels: pr.labels.map((item) => item.name), createdAt: pr.created_at, body: pr.body ?? undefined,
|
|
2314
|
+
changedFiles: await prFiles(repository, pr.number), ...(pr.updated_at ? { updatedAt: pr.updated_at } : {}),
|
|
2315
|
+
runId: currentDraftSweepRunId(matches, repository, pr.number) }));
|
|
2316
|
+
})());
|
|
2317
|
+
return draftRows.get(repository)!;
|
|
2318
|
+
};
|
|
2319
|
+
const enrichedMerged = (repository: string): Promise<SweepMergedPr[]> => {
|
|
2320
|
+
if (!mergedRows.has(repository)) mergedRows.set(repository, (async () => {
|
|
2321
|
+
const prs = (await listed(closed, repository, 'closed')).filter((pr) => pr.merged_at !== null && pr.base?.ref === 'main');
|
|
2322
|
+
await prefetchDetails(repository, prs.map((pr) => pr.number));
|
|
2323
|
+
return mapBounded(prs, DRAFT_SWEEP_CONCURRENCY, async (pr): Promise<SweepMergedPr> => ({
|
|
2324
|
+
number: pr.number, title: pr.title, branch: pr.head.ref, body: pr.body ?? undefined,
|
|
2325
|
+
mergedAt: pr.merged_at ?? undefined, changedFiles: await prFiles(repository, pr.number),
|
|
2326
|
+
mergeCommitMessage: await mergeCommitMessageFor(repository, pr),
|
|
2327
|
+
landingVerifiedComments: await landingCommentsFor(repository, pr.number) }));
|
|
2328
|
+
})());
|
|
2329
|
+
return mergedRows.get(repository)!;
|
|
2021
2330
|
};
|
|
2022
2331
|
let ledgerMatches: readonly RunLedgerMatch[] | undefined;
|
|
2023
2332
|
let running: ReadonlyMap<string, string> | undefined;
|
|
@@ -2031,22 +2340,35 @@ export function githubDraftSweepAdapters(execute: GhExecute = executeGh, git: Dr
|
|
|
2031
2340
|
return draftSweepRunStatus(ledgerMatches, running, repository, draft.number);
|
|
2032
2341
|
};
|
|
2033
2342
|
return {
|
|
2034
|
-
|
|
2343
|
+
openPullRequests: (repository) => listed(open, repository, 'open'),
|
|
2344
|
+
runLedgerMatches: () => (ledgerMatches ??= listRunLedgers().matches),
|
|
2345
|
+
getPrFiles: async (repository, number) => prFiles(repository, number),
|
|
2346
|
+
getPr: async (repository, number) => {
|
|
2347
|
+
const pr = ghJson<GithubPull>(['api', `repos/${repository}/pulls/${number}`], execute);
|
|
2348
|
+
if (!pr || pr.number !== number || typeof pr.head?.ref !== 'string' || !Array.isArray(pr.labels)) return undefined;
|
|
2349
|
+
return { number: pr.number, title: pr.title, branch: pr.head.ref, labels: pr.labels.map((label) => label.name),
|
|
2350
|
+
createdAt: pr.created_at, mergedAt: pr.merged_at ?? undefined };
|
|
2351
|
+
},
|
|
2352
|
+
listRecentClosed: async (repository, createdSince) => {
|
|
2353
|
+
const rows: SweepMergedPr[] = [];
|
|
2354
|
+
for (let page = 1; page <= 100; page++) {
|
|
2355
|
+
const batch = githubPullsPage(repository, 'closed', page, execute);
|
|
2356
|
+
if (!Array.isArray(batch) || batch.length > 100 || batch.some((pr) => !pr.updated_at || !Number.isFinite(Date.parse(pr.updated_at))
|
|
2357
|
+
|| !Array.isArray(pr.labels) || !pr.created_at || !Number.isFinite(Date.parse(pr.created_at)))) throw new Error('Incomplete closed metric inventory');
|
|
2358
|
+
for (const pr of batch) if (pr.base?.ref === 'main' && (pr.head?.ref?.startsWith('self-impl/')
|
|
2359
|
+
|| pr.labels.some((label) => label.name?.startsWith('elanous:')))) rows.push({
|
|
2360
|
+
number: pr.number, title: pr.title, branch: pr.head.ref,
|
|
2361
|
+
labels: pr.labels.map((label) => label.name),
|
|
2362
|
+
createdAt: pr.created_at, mergedAt: pr.merged_at ?? undefined,
|
|
2363
|
+
});
|
|
2364
|
+
if (batch.length < 100 || Date.parse(batch.at(-1)!.updated_at!) < createdSince.getTime()) return rows;
|
|
2365
|
+
}
|
|
2366
|
+
throw new Error('Closed metric inventory exceeded pagination limit');
|
|
2367
|
+
},
|
|
2035
2368
|
listDrafts: async (page, perPage, repository): Promise<SweepDraft[]> =>
|
|
2036
|
-
(await
|
|
2037
|
-
.slice((page - 1) * perPage, page * perPage)
|
|
2038
|
-
.map((pr) => ({ number: pr.number, title: pr.title, branch: pr.head.ref,
|
|
2039
|
-
labels: pr.labels.map((item) => item.name), createdAt: pr.created_at, body: pr.body ?? undefined,
|
|
2040
|
-
changedFiles: filesFor(repository, pr.number), ...(pr.updated_at ? { updatedAt: pr.updated_at } : {}),
|
|
2041
|
-
runId: currentDraftSweepRunId(ledgerMatches ??= listRunLedgers().matches, repository, pr.number) })),
|
|
2369
|
+
(await enrichedDrafts(repository)).slice((page - 1) * perPage, page * perPage),
|
|
2042
2370
|
listMerged: async (page, perPage, repository): Promise<SweepMergedPr[]> =>
|
|
2043
|
-
(await
|
|
2044
|
-
.slice((page - 1) * perPage, page * perPage)
|
|
2045
|
-
.map((pr) => ({ number: pr.number, title: pr.title, branch: pr.head.ref, body: pr.body ?? undefined,
|
|
2046
|
-
mergedAt: pr.merged_at ?? undefined, changedFiles: filesFor(repository, pr.number),
|
|
2047
|
-
mergeCommitMessage: pr.merge_commit_sha ? ghJson<{ commit: { message: string } }>(
|
|
2048
|
-
['api', `repos/${repository}/commits/${pr.merge_commit_sha}`], execute).commit.message : undefined,
|
|
2049
|
-
landingVerifiedComments: landingCommentsFor(repository, pr.number) })),
|
|
2371
|
+
(await enrichedMerged(repository)).slice((page - 1) * perPage, page * perPage),
|
|
2050
2372
|
getRunStatus: async (draft, repository) => runStatusFor(draft, repository),
|
|
2051
2373
|
getLatestFileChanges: async (draft, repository) => latestFileChangesFor(draft, repository),
|
|
2052
2374
|
hasFinalRunResult: async (draft, repository) => {
|
|
@@ -2097,6 +2419,17 @@ export function githubDraftSweepAdapters(execute: GhExecute = executeGh, git: Dr
|
|
|
2097
2419
|
execute(['pr', 'close', String(number), '--repo', repository, '--comment', comment]);
|
|
2098
2420
|
},
|
|
2099
2421
|
getClaimOwner: async (repository, number) => claimCommentOwner(repository, number, execute),
|
|
2422
|
+
getActiveClaimOwner: async (repository, number) => {
|
|
2423
|
+
const pages = ghJson<Array<Array<{ body: string; created_at: string }>>>(['api', '--paginate', '--slurp',
|
|
2424
|
+
`repos/${repository}/issues/${number}/comments?per_page=100`], execute);
|
|
2425
|
+
if (!Array.isArray(pages) || pages.some((page) => !Array.isArray(page)
|
|
2426
|
+
|| page.some((comment) => typeof comment.body !== 'string' || !Number.isFinite(Date.parse(comment.created_at)))))
|
|
2427
|
+
throw new Error('Incomplete claim comments');
|
|
2428
|
+
const last = pages.flat().sort((a, b) => Date.parse(b.created_at) - Date.parse(a.created_at))
|
|
2429
|
+
.map((comment) => comment.body)
|
|
2430
|
+
.find((body) => draftClaimOwner(body) !== undefined || body.startsWith('🔧 처리 끝 —'));
|
|
2431
|
+
return last ? draftClaimOwner(last) : undefined;
|
|
2432
|
+
},
|
|
2100
2433
|
getReviewGate: async (draft, repository) => reviewGateForDraft(draft, repository),
|
|
2101
2434
|
};
|
|
2102
2435
|
}
|
|
@@ -2107,6 +2440,28 @@ export interface HarnessDraftSweepDeps {
|
|
|
2107
2440
|
readonly write?: (line: string) => void;
|
|
2108
2441
|
readonly execute?: GhExecute;
|
|
2109
2442
|
readonly now?: () => Date;
|
|
2443
|
+
/** RUN-TTL step adapters. Omitted with injected `adapters` ⇒ the step is skipped (fakes never reach real gh). */
|
|
2444
|
+
readonly runTtl?: RunTtlAdapters;
|
|
2445
|
+
}
|
|
2446
|
+
|
|
2447
|
+
/** RUN-TTL step inside the draft sweep — fail-soft: its failure never changes the sweep verdict. */
|
|
2448
|
+
async function draftSweepRunTtl(repository: string, deps: HarnessDraftSweepDeps,
|
|
2449
|
+
shared?: GithubDraftSweepAdapters): Promise<RunTtlResult | { error: string } | undefined> {
|
|
2450
|
+
if (deps.adapters && !deps.runTtl) return undefined;
|
|
2451
|
+
try {
|
|
2452
|
+
const { runRunTtlSweep } = await import('../self-implement/run-ttl.js');
|
|
2453
|
+
const config = (() => { try { return getUserConfig().tools?.selfImplement; } catch { return undefined; } })();
|
|
2454
|
+
const adapters = deps.runTtl ?? await githubRunTtlAdapters(repository, deps.execute ?? executeGh,
|
|
2455
|
+
deps.execute ? async (args) => deps.execute!(args) : executeGhAsync, undefined, shared ?? {});
|
|
2456
|
+
return await runRunTtlSweep({
|
|
2457
|
+
adapters, mode: config?.runTtl?.mode ?? 'shadow', ttlHours: config?.runTtlHours, cap: config?.runTtl?.cap,
|
|
2458
|
+
...(deps.now ? { now: () => deps.now!().getTime() } : {}),
|
|
2459
|
+
});
|
|
2460
|
+
} catch (error) {
|
|
2461
|
+
const reason = sweepFailureReason(error);
|
|
2462
|
+
try { debug.log('self-implement.run-ttl', 'failed', { reason }); } catch { /* fail-soft */ }
|
|
2463
|
+
return { error: reason };
|
|
2464
|
+
}
|
|
2110
2465
|
}
|
|
2111
2466
|
|
|
2112
2467
|
function draftClaimOwner(body: string): string | undefined {
|
|
@@ -2180,10 +2535,19 @@ function installHarnessDraftSweepCommand(harnessCmd: Command, deps: HarnessDraft
|
|
|
2180
2535
|
if (!DRAFT_SWEEP_REPOSITORY.test(repository) || repository.includes('..')) {
|
|
2181
2536
|
throw new HarnessCliInputError(`invalid --repo (expected owner/name): ${repository}`);
|
|
2182
2537
|
}
|
|
2183
|
-
|
|
2538
|
+
// One adapter instance per sweep: its open listing and ledger scan are shared with the RUN-TTL step.
|
|
2539
|
+
const github = deps.adapters ? undefined : githubDraftSweepAdapters(deps.execute);
|
|
2540
|
+
const result: DraftSweepResult = await runDraftSweep({ repository, apply: opts.apply === true, adapters: deps.adapters ?? github! });
|
|
2184
2541
|
const reason = result.error ? sweepFailureReason(result.error) : undefined;
|
|
2185
|
-
|
|
2542
|
+
const ttlStarted = Date.now();
|
|
2543
|
+
const runTtl = await draftSweepRunTtl(repository, deps, github);
|
|
2544
|
+
try { debug.log('self-dev.draft-sweep', 'timing', { phase: 'run-ttl', ms: Date.now() - ttlStarted, count: runTtl && !('error' in runTtl) ? runTtl.considered : 0 }); } catch { /* fail-soft */ }
|
|
2545
|
+
if (opts.json) write(JSON.stringify({ ...(reason ? { ...result, error: reason } : result), ...(runTtl ? { runTtl } : {}) }));
|
|
2186
2546
|
else {
|
|
2547
|
+
if (runTtl) {
|
|
2548
|
+
write('error' in runTtl ? `run-ttl: 실패 (${runTtl.error})`
|
|
2549
|
+
: `run-ttl(${runTtl.mode}, ${runTtl.ttlHours}h): 대상 ${runTtl.considered} · 낡음 ${runTtl.stale.length} · 메모 ${runTtl.memo.length} · 표식 ${runTtl.label.length} · 이미 표식 ${runTtl.alreadyMarked.length} · 상한 초과 ${runTtl.skippedOverCap.length}${runTtl.mode === 'shadow' ? ' (shadow — 쓰기 0)' : ''}`);
|
|
2550
|
+
}
|
|
2187
2551
|
write(`draft sweep ${result.repository}: ${result.complete ? 'complete' : `incomplete (${reason})`} · ${result.apply ? 'apply' : 'dry-run'}`);
|
|
2188
2552
|
for (const entry of result.entries) write(`#${entry.number} ${entry.action}: ${entry.reason}${entry.statusLabel ? ` → ${entry.statusLabel}` : ''}${entry.error ? ` ERROR: ${sweepFailureReason(entry.error)}` : ''}`);
|
|
2189
2553
|
if (result.complete) {
|
|
@@ -2513,6 +2877,7 @@ export function installHarnessCliCommand(program: Command, deps: HarnessCliComma
|
|
|
2513
2877
|
installHarnessProcessObservationCommand(harnessCmd, deps.processObservation);
|
|
2514
2878
|
installHarnessBudgetCommand(harnessCmd);
|
|
2515
2879
|
installHarnessSalvageCommand(harnessCmd);
|
|
2880
|
+
installHarnessSalvageRetentionCommand(harnessCmd);
|
|
2516
2881
|
installHarnessGoalCommand(harnessCmd, deps.goalLookup, deps.goalArchive);
|
|
2517
2882
|
installHarnessDraftSweepCommand(harnessCmd, deps.draftSweep);
|
|
2518
2883
|
const queue = harnessCmd.command('queue').description('자리별 영속 발사 대기열');
|
|
@@ -2567,9 +2932,11 @@ export function installHarnessCliCommand(program: Command, deps: HarnessCliComma
|
|
|
2567
2932
|
queue.command('reconcile <id>').description('불확정 발사를 확인하고 종료 또는 미발사 증거가 있으면 예약 해소').action((id: string) => queueAction(async () => {
|
|
2568
2933
|
console.log(`${id} ${await reconcileHarnessQueue(id, queueDeps)}`);
|
|
2569
2934
|
}));
|
|
2570
|
-
queue.command('tick').description('자리별 맨 앞
|
|
2935
|
+
queue.command('tick').description('자리별 맨 앞 항목을 라운드로빈으로 돌며 자리 몫·Pod 풀 여유·마무리 관문을 확인하고 발사 — 여유가 있으면 한 tick 에 여러 건(30초 간격 · harness.queue.burstMax 기본 5)').action(() => queueAction(async () => {
|
|
2571
2936
|
const result = await tickHarnessQueue(queueDeps);
|
|
2572
2937
|
console.log(`${result.outcome}${result.item ? ` ${result.item.id}` : ''}: ${result.reason}`);
|
|
2938
|
+
// QUEUE-BURST: the first line stays as before; every further launch of the burst gets its own line.
|
|
2939
|
+
for (const row of result.launched?.slice(1) ?? []) console.log(`launched ${row.id}: spawned`);
|
|
2573
2940
|
}));
|
|
2574
2941
|
|
|
2575
2942
|
const ask = deps.ask;
|