elanous 0.2.18 → 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/chunks/{1307-30dff22c9e1f3073.js → 1307-77fad80cf54477c6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1612-f60c0c93ec773f81.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2116-6429c188eb846ab8.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/2549-89c518a4c8ed4d00.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3857-995ea9f5ef33d71e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4144-fad095b48a1862ee.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/7748-6e51bfe927019f5c.js +25 -0
- package/apps/pwa/out/_next/static/chunks/8401-35e94e9091f5eb8d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9247-c1753a059e7bac08.js +1 -0
- package/apps/pwa/out/_next/static/chunks/a9f2b54d-33c0689b70ed8cf3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/{page-d92f7ee3994e84c7.js → page-c855c24de7db74fe.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-4733c967b56a4277.js → page-85b316bb4235980b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/{page-d22fd849cc22f104.js → page-9b2b26a33cb2cd42.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-bb742f995c6769e7.js → page-c9955fde67b1bcb5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/{page-02b073a2ac793b83.js → page-7a852956c74015a3.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-c5a5c621be0e04ae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-8a9b3594bc23289b.js → page-4ba4abe096b6c2a8.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-575d4a8b4f7becb2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/{page-308111abadf35670.js → page-f5d56ff5f45ad164.js} +1 -1
- 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-5d97b2901e0d1ea6.js → page-7c058069c5e7fa63.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-dc5da0b8a2edb8a4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-8c3d8e63ae2e1c98.js → page-466bf0ee16ad1432.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-9b72a07d424701c6.js → page-a23bc71713bcc08f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-ed4a6f799f005d09.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-a572f58cd3ca5864.js → page-9cd565005b93aca6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-55f0f40c6db95f15.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/{page-be5ea965a07fbc41.js → page-474cb5888ea0e8f3.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-35bfec2e631d3eae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/{page-fa244bd146a00ae8.js → page-9ca21f9ad3208a61.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-44d56d6391965a6a.js → page-f9ccb1b1adbea14d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-83a700d4c754d802.js → page-ec102c9b8b691e37.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{not-found-7b3170f374f77b9d.js → not-found-a9c2cfa8dda922a5.js} +1 -1
- 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-1467047104141297.js → page-db03ae3589a81c9e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-4a6463b97f0ad94f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/{page-102310930791197a.js → page-75bffecc9aa1004e.js} +1 -1
- 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-6ed0b73bae8351d8.js → page-31d5b875760bb62d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-c26fa10aac6c21c4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/{page-43336055451f6e37.js → page-68793c6e10331d66.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/share/{page-3cc47dca0922ab9a.js → page-46d5201d2207a511.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/{page-a77ee355a2304ff0.js → page-d81c07a7b4f618ae.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/{page-b17cd46cf0e9b8cf.js → page-be1c4e8697f22360.js} +1 -1
- 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-7bf54c9bb9274af8.js → page-0fd895fab9b62c1b.js} +1 -1
- 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-134da468538baf48.js → page-34882c84f84021c5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-0506a1e0e4bdb2f5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{webpack-c2782b330447ffb6.js → webpack-c260571513bf43c2.js} +1 -1
- package/apps/pwa/out/_next/static/css/{e99e3cff94ced78a.css → 599369d853c61df7.css} +1 -1
- package/apps/pwa/out/_next/static/css/c23d959d3ee15e57.css +1 -0
- package/apps/pwa/out/_next/static/css/d3427996da11e480.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +14 -14
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +14 -14
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +14 -14
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +14 -14
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +14 -14
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +14 -14
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +14 -14
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +14 -14
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +14 -14
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +14 -14
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +14 -14
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +16 -15
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +14 -14
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +14 -14
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +14 -14
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +16 -15
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +14 -14
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +14 -14
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +24 -23
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +14 -14
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +14 -14
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +14 -14
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +14 -14
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +14 -14
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +14 -14
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +14 -14
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +14 -14
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +15 -15
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +14 -14
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +14 -14
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +14 -14
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +14 -14
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +14 -14
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +14 -14
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +14 -14
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +14 -14
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +14 -14
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +14 -14
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +14 -14
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +14 -14
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +14 -14
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +14 -14
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +14 -14
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +14 -14
- package/graphs/demo/editor-recipes.yaml +22 -0
- package/graphs/ops/guardian.yaml +26 -0
- package/graphs/ops/recipes.yaml +3 -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/coord-channel-watch.sh +2 -2
- package/scripts/discord-seat-probe.ts +3 -1
- package/scripts/land-global-checks.ts +89 -0
- package/scripts/lib/pch-regress-map.ts +24 -0
- package/scripts/morning-report.ts +18 -4
- package/scripts/pch-regress.ts +88 -0
- package/scripts/release-loop/gate-diff.ts +18 -0
- package/scripts/release-loop/gate-node.ts +628 -65
- package/scripts/release-loop/gate-partial.ts +106 -0
- package/scripts/release-loop/gate-timing.ts +229 -0
- 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 +67 -5
- package/scripts/release-loop/shard-plan.ts +65 -0
- package/scripts/release-loop/unattended-release.ts +12 -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/scripts/video/scene-table.ts +159 -0
- package/src/acp/core-turn-bridge.ts +39 -2
- 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/presence.ts +40 -0
- package/src/away/release-watch.ts +272 -0
- package/src/chat/index.ts +2 -1
- package/src/chat/short-question.ts +3 -40
- package/src/cli/away-cli.ts +68 -0
- package/src/cli/config-drift.ts +513 -0
- package/src/cli/guardian-cli.ts +49 -0
- package/src/cli/intake-cli.ts +13 -7
- package/src/cli/log-event-names.ts +5 -0
- package/src/cli/pod-cli.ts +5 -1
- package/src/cli/pr-cli.ts +20 -0
- package/src/cli/pty-drive-cli.ts +14 -3
- package/src/cli/release-cli.ts +52 -12
- package/src/cli/retro-cli.ts +78 -0
- package/src/cli/setup-cli.ts +31 -1
- package/src/cli/tasks-cli.ts +193 -14
- package/src/context-bus/context-now-public.ts +1 -1
- package/src/context-bus/context-now-surfaces.ts +16 -6
- package/src/context-bus/context-now.ts +97 -3
- package/src/core-turn/short-question.ts +100 -0
- package/src/dashboard/dashboard-status-lines.ts +24 -4
- package/src/dashboard/index.ts +50 -5
- package/src/dashboard/input/slash-executor.ts +6 -2
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +68 -40
- package/src/dashboard/slash-runtime/decide-slash.ts +60 -0
- package/src/dashboard/slash-runtime/help-from-registry.ts +62 -23
- package/src/dashboard/slash-runtime/help-groups.ts +44 -0
- package/src/dashboard/slash-runtime/model-choices.ts +125 -0
- package/src/domains/guardian-tick.ts +138 -0
- package/src/domains/outbound-alert.ts +44 -13
- package/src/domains/outbound-log-probe.ts +6 -1
- package/src/domains/schedule-registry.ts +23 -0
- package/src/domains/telegram-kind-route.ts +39 -0
- package/src/domains/watch-default-scheduler.ts +95 -0
- package/src/esc-abort-gate.ts +44 -0
- package/src/graph-runner/graph-run-api.ts +205 -0
- package/src/graph-runner/runner.ts +36 -6
- package/src/harness/harness-cli-command.ts +420 -51
- package/src/harness/harness-queue.ts +315 -25
- package/src/harness/harness-salvage-cli.ts +140 -0
- package/src/harness/harness-worktree-add.ts +68 -0
- package/src/harness/ledger-write-guard.ts +6 -16
- package/src/hq/fence-outcomes.ts +63 -0
- package/src/hq/fence-wrapper.ts +33 -0
- package/src/hq/hq.ts +17 -2
- package/src/index.ts +118 -6
- package/src/instance/test-write-guard-spy-first.fixture.ts +13 -0
- package/src/instance/test-write-guard.ts +200 -0
- package/src/intake-plane/digest.ts +39 -15
- package/src/intake-plane/harness-target.ts +4 -3
- package/src/intake-plane/intake-sources.ts +1 -0
- package/src/intake-plane/items.ts +1 -0
- package/src/loops/orchestrator/degradation.ts +57 -0
- package/src/loops/orchestrator/finish-rate.ts +33 -3
- package/src/loops/orchestrator/tick.ts +166 -45
- package/src/maturity/feature-maturity.ts +6 -4
- package/src/maturity/tui-slash-maturity.ts +1 -0
- package/src/mss/logging/log-store.ts +3 -0
- 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 +54 -8
- package/src/nexus/api/loop-edges.ts +292 -17
- package/src/nexus/api/ops-api.ts +112 -29
- package/src/nexus/api/ops-board-route.ts +48 -0
- package/src/nexus/index.ts +7 -0
- package/src/onboarding/entry-hints.ts +5 -0
- package/src/onboarding.ts +24 -6
- package/src/release-loop/checklist.ts +2 -0
- package/src/release-loop/feature-store.ts +68 -18
- package/src/release-loop/gate-shards.ts +158 -0
- package/src/release-loop/light-rc.ts +173 -0
- package/src/release-loop/placement.ts +51 -17
- package/src/release-loop/release-schedule.ts +6 -2
- package/src/release-loop/rubric.ts +57 -1
- 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/dev-cli.ts +36 -0
- package/src/self-dev/draft-sweep.ts +371 -16
- package/src/self-dev/draft-triage-rules.ts +19 -4
- package/src/self-dev/orchestrate.ts +1 -0
- package/src/self-dev/pr-preview.ts +247 -0
- package/src/self-dev/shard-run.ts +23 -7
- package/src/self-implement/blocked-draft-policy.ts +101 -0
- package/src/self-implement/execution-clarification.ts +11 -0
- package/src/self-implement/gate-cli.ts +29 -8
- package/src/self-implement/gate-env-retry.ts +83 -0
- package/src/self-implement/goal-author-clarification.ts +88 -13
- package/src/self-implement/goal-clarification-escalation.ts +2 -0
- package/src/self-implement/orchestrator.ts +220 -6
- package/src/self-implement/run-stop.ts +123 -0
- package/src/self-implement/run-ttl.ts +258 -0
- package/src/self-implement/salvage-branches.ts +254 -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/actions.ts +63 -2
- package/src/task-agent/board.ts +132 -0
- package/src/task-agent/card-evidence.ts +292 -0
- package/src/task-agent/completion-evidence.ts +126 -0
- package/src/task-agent/judge.ts +25 -0
- package/src/task-agent/live-moves.ts +276 -0
- package/src/task-agent/mission.ts +33 -7
- package/src/task-agent/shadow.ts +192 -15
- package/src/task-agent/task-hand.ts +124 -17
- package/src/task-orchestrator/surfaces/pod-command-job.ts +82 -8
- 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 +233 -21
- package/src/task-orchestrator/surfaces/pod-pool.ts +155 -34
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +21 -5
- package/src/telegram-away-command.ts +38 -0
- package/src/telegram-commands.ts +52 -0
- package/src/telegram-report-log-probe.ts +21 -0
- package/src/telegram-report.ts +27 -3
- package/src/testing/real-cli-spawn.ts +106 -0
- package/src/tui-client/dashboard-session.ts +1 -0
- package/src/user-config.ts +190 -5
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3857-4f936cdb30322aa6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4554-5761612531d0146b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5977-a11f37254196b659.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8401-8615a660c84cdb03.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +0 -25
- package/apps/pwa/out/_next/static/chunks/9247-269e4464cc4b491c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/a9f2b54d-7865aba0f4d2ee49.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-60c3da708f9a0ccc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-a0eb02638b1e6f31.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-6eaf7bac37830c21.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-c39554c321576982.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-bd5d586429167fea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-59b7834d497c2255.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-c130b63862437814.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-5f23d0c66a3b4793.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-a3c58f5c72a5f3f9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-a2c4625596f8d69c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-0e9099531d239f11.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-e8e1ecaea7cd71d2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-a1b7ffed710a93e0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-2da6f9b8a38c6978.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-f331e38fefc00393.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-44d5f767491692bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-61553851aa606146.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-a1f585e154e61c59.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-5150ef17b5383a57.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b9d8ce70792c1672.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-a5a3de231f2cc7d5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-31ac4afa2dcd915c.js +0 -1
- package/apps/pwa/out/_next/static/css/7fa358fec7da7c47.css +0 -1
- /package/apps/pwa/out/_next/static/{P6YTVgzuZz5oTZhY4Din_ → Bam1YziK2iH9bN4K0utGk}/_buildManifest.js +0 -0
- /package/apps/pwa/out/_next/static/{P6YTVgzuZz5oTZhY4Din_ → 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,13 +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';
|
|
28
|
+
import { installHarnessSalvageCommand, installHarnessSalvageRetentionCommand } from './harness-salvage-cli.js';
|
|
26
29
|
import { decideNestedElanousLaunch, readNestedElanousDepth } from './nested-elanous-policy.js';
|
|
27
30
|
import { dispatchTask, type DispatchTaskInput, type DispatchTaskDeps } from '../execution-loop/dispatch-task.js';
|
|
28
31
|
import { launchRequestId, preLaunchGate, type PreLaunchGateDeps } from '../execution-loop/launch-gate.js';
|
|
@@ -31,6 +34,9 @@ import { CODEX_PROVIDER, GROK_PROVIDER, decideBudget, decideLaunchBudget, readBu
|
|
|
31
34
|
import { DEFAULT_FALLBACK_CHAIN } from '../oauth/fallback-chain.js';
|
|
32
35
|
import { installDeliverableVerifyCliCommand, type InstallDeliverableVerifyCliDeps } from './deliverable-verify-cli.js';
|
|
33
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';
|
|
34
40
|
import { resolveRepositoryName } from './repository-name.js';
|
|
35
41
|
import { installHarnessCliSinkHook } from './harness-cli-sink.js';
|
|
36
42
|
import { addHarnessQueue, HarnessQueueDuplicateError, harnessQueueReceiptPath, listHarnessQueue, queueSeatForCwd, reconcileHarnessQueue, removeHarnessQueue, setHarnessQueuePriority, tickHarnessQueue, type HarnessQueueDeps, type QueueItem, type QueueSeat } from './harness-queue.js';
|
|
@@ -669,11 +675,40 @@ async function onPod(opts: unknown, entrance: 'cli-harness-ask' | 'cli-harness-s
|
|
|
669
675
|
const classified = classifyHarnessPodExit({ status }, output, { ...(runId ? { runId } : {}) });
|
|
670
676
|
recordClassifiedHarnessPodExit(runId ?? harnessPodRunId(output), classified.reason, status,
|
|
671
677
|
{ entrance, seat: o.seat, output });
|
|
678
|
+
recordHarnessPodStop(runId ?? harnessPodRunId(output), status, classified);
|
|
672
679
|
for (const line of classified.lines) console.error(line);
|
|
673
680
|
process.exitCode = status;
|
|
674
681
|
}
|
|
675
682
|
}
|
|
676
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
|
+
|
|
677
712
|
const LAUNCH_BUDGET_PROVIDER: Readonly<Record<string, string>> = { 'codex-rotate': CODEX_PROVIDER, grok: GROK_PROVIDER };
|
|
678
713
|
|
|
679
714
|
/**
|
|
@@ -1800,7 +1835,7 @@ type GithubPull = {
|
|
|
1800
1835
|
title: string;
|
|
1801
1836
|
draft: boolean;
|
|
1802
1837
|
state: 'open' | 'closed';
|
|
1803
|
-
head: { ref: string };
|
|
1838
|
+
head: { ref: string; sha?: string };
|
|
1804
1839
|
base?: { ref: string };
|
|
1805
1840
|
labels: Array<{ name: string }>;
|
|
1806
1841
|
created_at: string;
|
|
@@ -1843,6 +1878,147 @@ export async function supersedeMergedGoalDrafts(
|
|
|
1843
1878
|
await supersedeDraftsOnMerge({ repository, merged, adapters: githubDraftSweepAdapters(execute, git, cwd) });
|
|
1844
1879
|
}
|
|
1845
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
|
+
|
|
1846
2022
|
function ghJson<T>(args: string[], execute: GhExecute): T {
|
|
1847
2023
|
return JSON.parse(execute(args)) as T;
|
|
1848
2024
|
}
|
|
@@ -1907,7 +2083,94 @@ function githubRemoteRepository(url: string): string | undefined {
|
|
|
1907
2083
|
return match?.[1];
|
|
1908
2084
|
}
|
|
1909
2085
|
|
|
1910
|
-
|
|
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 {
|
|
1911
2174
|
const inventory = async (repository: string, state: 'open' | 'closed'): Promise<GithubPull[]> => {
|
|
1912
2175
|
// ⛔ A large repository has tens of thousands of closed PRs (2026-09-28: 21k → «exceeded pagination limit», no sweep at all).
|
|
1913
2176
|
// Merged twins only matter after the oldest open draft was opened, so the closed listing stops at that time.
|
|
@@ -1936,13 +2199,14 @@ export function githubDraftSweepAdapters(execute: GhExecute = executeGh, git: Dr
|
|
|
1936
2199
|
if (!cache.has(repository)) cache.set(repository, inventory(repository, state));
|
|
1937
2200
|
return cache.get(repository)!;
|
|
1938
2201
|
};
|
|
2202
|
+
const ghJsonAsync = async <T>(args: string[]): Promise<T> => JSON.parse(await executeAsync(args)) as T;
|
|
1939
2203
|
// GitHub stops listing a pull request's files at 3,000, and the cut looks like a normal last page.
|
|
1940
2204
|
// A list that reaches the cap cannot prove it is complete, so it is reported as unknown (undefined):
|
|
1941
2205
|
// a draft without a provable file list never qualifies for the all-files-landed close.
|
|
1942
|
-
const filesFor = (repository: string, number: number): string[] | undefined => {
|
|
2206
|
+
const filesFor = async (repository: string, number: number): Promise<string[] | undefined> => {
|
|
1943
2207
|
const files: string[] = [];
|
|
1944
2208
|
for (let page = 1; page <= 100; page++) {
|
|
1945
|
-
const batch =
|
|
2209
|
+
const batch = await ghJsonAsync<Array<{ filename: string }>>(['api', `repos/${repository}/pulls/${number}/files?per_page=100&page=${page}`]);
|
|
1946
2210
|
if (!Array.isArray(batch) || batch.length > 100 || batch.some((file) => typeof file.filename !== 'string')) throw new Error('Incomplete PR file inventory');
|
|
1947
2211
|
files.push(...batch.map((file) => file.filename));
|
|
1948
2212
|
if (files.length >= GITHUB_PR_FILES_LIST_CAP) {
|
|
@@ -1953,18 +2217,31 @@ export function githubDraftSweepAdapters(execute: GhExecute = executeGh, git: Dr
|
|
|
1953
2217
|
}
|
|
1954
2218
|
throw new Error('PR file inventory exceeded pagination limit');
|
|
1955
2219
|
};
|
|
1956
|
-
|
|
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> => {
|
|
1957
2234
|
if (!draft.changedFiles?.length) return undefined;
|
|
1958
2235
|
const changes: Record<string, string> = {};
|
|
1959
2236
|
for (let page = 1; page <= 100; page++) {
|
|
1960
|
-
const batch =
|
|
1961
|
-
['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}`]);
|
|
1962
2239
|
if (!Array.isArray(batch) || batch.length > 100) throw new Error('Incomplete draft commit inventory');
|
|
1963
2240
|
for (const commit of batch) {
|
|
1964
2241
|
const changedAt = commit.commit?.committer?.date;
|
|
1965
2242
|
if (!commit.sha || !changedAt || !Number.isFinite(Date.parse(changedAt))) return undefined;
|
|
1966
|
-
const detail =
|
|
1967
|
-
['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`]);
|
|
1968
2245
|
// GitHub truncates very large commit file lists; never infer coverage from a partial list.
|
|
1969
2246
|
if (!Array.isArray(detail.files) || detail.files.length >= 100) return undefined;
|
|
1970
2247
|
for (const file of detail.files) {
|
|
@@ -1978,24 +2255,30 @@ export function githubDraftSweepAdapters(execute: GhExecute = executeGh, git: Dr
|
|
|
1978
2255
|
}
|
|
1979
2256
|
return undefined;
|
|
1980
2257
|
};
|
|
1981
|
-
const
|
|
1982
|
-
const head = ghJson<{ head?: { sha?: string } }>(['api', `repos/${repository}/pulls/${draft.number}`], execute);
|
|
1983
|
-
const sha = head?.head?.sha;
|
|
1984
|
-
if (!sha) return undefined;
|
|
2258
|
+
const issueCommentsFor = async (repository: string, number: number, what: 'review' | 'landing'): Promise<string[]> => {
|
|
1985
2259
|
const comments: string[] = [];
|
|
1986
2260
|
for (let page = 1; page <= 100; page++) {
|
|
1987
|
-
const batch =
|
|
1988
|
-
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`);
|
|
1989
2263
|
comments.push(...batch.map((comment) => comment.body ?? ''));
|
|
1990
|
-
if (batch.length < 100)
|
|
1991
|
-
if (page === 100) throw new Error('Review comment inventory exceeded pagination limit');
|
|
2264
|
+
if (batch.length < 100) return comments;
|
|
1992
2265
|
}
|
|
1993
|
-
|
|
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`]);
|
|
1994
2276
|
if (!Array.isArray(reviews) || reviews.length > 100) throw new Error('Incomplete review inventory');
|
|
1995
2277
|
const text = [...comments, ...reviews.map((review) => review.body ?? '')].join('\n');
|
|
1996
|
-
const status =
|
|
1997
|
-
|
|
1998
|
-
['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
|
+
]);
|
|
1999
2282
|
if (!checkRuns || !Array.isArray(checkRuns.check_runs)) throw new Error('Incomplete check-run inventory');
|
|
2000
2283
|
const conclusions = checkRuns.check_runs.map((run) => run.conclusion ?? null);
|
|
2001
2284
|
const gatePass = (status?.state === 'success' || status?.state === undefined)
|
|
@@ -2008,15 +2291,42 @@ export function githubDraftSweepAdapters(execute: GhExecute = executeGh, git: Dr
|
|
|
2008
2291
|
...(gatePass && !gateFail ? { gate: 'pass' as const } : gateFail ? { gate: 'fail' as const } : {}),
|
|
2009
2292
|
};
|
|
2010
2293
|
};
|
|
2011
|
-
const landingCommentsFor = (repository: string, number: number): string[] =>
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
}
|
|
2019
|
-
|
|
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)!;
|
|
2020
2330
|
};
|
|
2021
2331
|
let ledgerMatches: readonly RunLedgerMatch[] | undefined;
|
|
2022
2332
|
let running: ReadonlyMap<string, string> | undefined;
|
|
@@ -2030,22 +2340,35 @@ export function githubDraftSweepAdapters(execute: GhExecute = executeGh, git: Dr
|
|
|
2030
2340
|
return draftSweepRunStatus(ledgerMatches, running, repository, draft.number);
|
|
2031
2341
|
};
|
|
2032
2342
|
return {
|
|
2033
|
-
|
|
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
|
+
},
|
|
2034
2368
|
listDrafts: async (page, perPage, repository): Promise<SweepDraft[]> =>
|
|
2035
|
-
(await
|
|
2036
|
-
.slice((page - 1) * perPage, page * perPage)
|
|
2037
|
-
.map((pr) => ({ number: pr.number, title: pr.title, branch: pr.head.ref,
|
|
2038
|
-
labels: pr.labels.map((item) => item.name), createdAt: pr.created_at, body: pr.body ?? undefined,
|
|
2039
|
-
changedFiles: filesFor(repository, pr.number), ...(pr.updated_at ? { updatedAt: pr.updated_at } : {}),
|
|
2040
|
-
runId: currentDraftSweepRunId(ledgerMatches ??= listRunLedgers().matches, repository, pr.number) })),
|
|
2369
|
+
(await enrichedDrafts(repository)).slice((page - 1) * perPage, page * perPage),
|
|
2041
2370
|
listMerged: async (page, perPage, repository): Promise<SweepMergedPr[]> =>
|
|
2042
|
-
(await
|
|
2043
|
-
.slice((page - 1) * perPage, page * perPage)
|
|
2044
|
-
.map((pr) => ({ number: pr.number, title: pr.title, branch: pr.head.ref, body: pr.body ?? undefined,
|
|
2045
|
-
mergedAt: pr.merged_at ?? undefined, changedFiles: filesFor(repository, pr.number),
|
|
2046
|
-
mergeCommitMessage: pr.merge_commit_sha ? ghJson<{ commit: { message: string } }>(
|
|
2047
|
-
['api', `repos/${repository}/commits/${pr.merge_commit_sha}`], execute).commit.message : undefined,
|
|
2048
|
-
landingVerifiedComments: landingCommentsFor(repository, pr.number) })),
|
|
2371
|
+
(await enrichedMerged(repository)).slice((page - 1) * perPage, page * perPage),
|
|
2049
2372
|
getRunStatus: async (draft, repository) => runStatusFor(draft, repository),
|
|
2050
2373
|
getLatestFileChanges: async (draft, repository) => latestFileChangesFor(draft, repository),
|
|
2051
2374
|
hasFinalRunResult: async (draft, repository) => {
|
|
@@ -2096,6 +2419,17 @@ export function githubDraftSweepAdapters(execute: GhExecute = executeGh, git: Dr
|
|
|
2096
2419
|
execute(['pr', 'close', String(number), '--repo', repository, '--comment', comment]);
|
|
2097
2420
|
},
|
|
2098
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
|
+
},
|
|
2099
2433
|
getReviewGate: async (draft, repository) => reviewGateForDraft(draft, repository),
|
|
2100
2434
|
};
|
|
2101
2435
|
}
|
|
@@ -2106,6 +2440,28 @@ export interface HarnessDraftSweepDeps {
|
|
|
2106
2440
|
readonly write?: (line: string) => void;
|
|
2107
2441
|
readonly execute?: GhExecute;
|
|
2108
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
|
+
}
|
|
2109
2465
|
}
|
|
2110
2466
|
|
|
2111
2467
|
function draftClaimOwner(body: string): string | undefined {
|
|
@@ -2179,10 +2535,19 @@ function installHarnessDraftSweepCommand(harnessCmd: Command, deps: HarnessDraft
|
|
|
2179
2535
|
if (!DRAFT_SWEEP_REPOSITORY.test(repository) || repository.includes('..')) {
|
|
2180
2536
|
throw new HarnessCliInputError(`invalid --repo (expected owner/name): ${repository}`);
|
|
2181
2537
|
}
|
|
2182
|
-
|
|
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! });
|
|
2183
2541
|
const reason = result.error ? sweepFailureReason(result.error) : undefined;
|
|
2184
|
-
|
|
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 } : {}) }));
|
|
2185
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
|
+
}
|
|
2186
2551
|
write(`draft sweep ${result.repository}: ${result.complete ? 'complete' : `incomplete (${reason})`} · ${result.apply ? 'apply' : 'dry-run'}`);
|
|
2187
2552
|
for (const entry of result.entries) write(`#${entry.number} ${entry.action}: ${entry.reason}${entry.statusLabel ? ` → ${entry.statusLabel}` : ''}${entry.error ? ` ERROR: ${sweepFailureReason(entry.error)}` : ''}`);
|
|
2188
2553
|
if (result.complete) {
|
|
@@ -2511,6 +2876,8 @@ export function installHarnessCliCommand(program: Command, deps: HarnessCliComma
|
|
|
2511
2876
|
installDeliverableVerifyCliCommand(harnessCmd, deps.deliverableVerify);
|
|
2512
2877
|
installHarnessProcessObservationCommand(harnessCmd, deps.processObservation);
|
|
2513
2878
|
installHarnessBudgetCommand(harnessCmd);
|
|
2879
|
+
installHarnessSalvageCommand(harnessCmd);
|
|
2880
|
+
installHarnessSalvageRetentionCommand(harnessCmd);
|
|
2514
2881
|
installHarnessGoalCommand(harnessCmd, deps.goalLookup, deps.goalArchive);
|
|
2515
2882
|
installHarnessDraftSweepCommand(harnessCmd, deps.draftSweep);
|
|
2516
2883
|
const queue = harnessCmd.command('queue').description('자리별 영속 발사 대기열');
|
|
@@ -2565,9 +2932,11 @@ export function installHarnessCliCommand(program: Command, deps: HarnessCliComma
|
|
|
2565
2932
|
queue.command('reconcile <id>').description('불확정 발사를 확인하고 종료 또는 미발사 증거가 있으면 예약 해소').action((id: string) => queueAction(async () => {
|
|
2566
2933
|
console.log(`${id} ${await reconcileHarnessQueue(id, queueDeps)}`);
|
|
2567
2934
|
}));
|
|
2568
|
-
queue.command('tick').description('자리별 맨 앞
|
|
2935
|
+
queue.command('tick').description('자리별 맨 앞 항목을 라운드로빈으로 돌며 자리 몫·Pod 풀 여유·마무리 관문을 확인하고 발사 — 여유가 있으면 한 tick 에 여러 건(30초 간격 · harness.queue.burstMax 기본 5)').action(() => queueAction(async () => {
|
|
2569
2936
|
const result = await tickHarnessQueue(queueDeps);
|
|
2570
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`);
|
|
2571
2940
|
}));
|
|
2572
2941
|
|
|
2573
2942
|
const ask = deps.ask;
|