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,6 +1,7 @@
|
|
|
1
1
|
import { CLAIM_IDLE_HOURS, PR_LABELS, STALLED_DRAFT_HOURS } from '../github/pr-labels.js';
|
|
2
2
|
import { debug } from '../debug/log.js';
|
|
3
|
-
import { decideDraft, sameGoalPr, type DraftTriagePr } from './draft-triage-rules.js';
|
|
3
|
+
import { decideDraft, laterMergedTwins, sameGoalPr, type DraftTriagePr } from './draft-triage-rules.js';
|
|
4
|
+
import { branchLineageSlug } from '../cli/pr-lineage.js';
|
|
4
5
|
|
|
5
6
|
export interface SweepDraft extends DraftTriagePr {
|
|
6
7
|
readonly title: string;
|
|
@@ -13,12 +14,17 @@ export interface SweepDraft extends DraftTriagePr {
|
|
|
13
14
|
|
|
14
15
|
export interface SweepMergedPr extends DraftTriagePr {
|
|
15
16
|
readonly title: string;
|
|
17
|
+
readonly createdAt?: string;
|
|
18
|
+
readonly mergedAt?: string;
|
|
19
|
+
readonly labels?: readonly string[];
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
export interface DraftSweepAdapters {
|
|
19
23
|
/** Each call returns one complete page; a rejection must not be mistaken for an empty page. */
|
|
20
24
|
listDrafts(page: number, perPage: number, repository: string): Promise<readonly SweepDraft[]>;
|
|
21
25
|
listMerged(page: number, perPage: number, repository: string): Promise<readonly SweepMergedPr[]>;
|
|
26
|
+
/** Complete closed PR inventory for the creation cohort (including closed without merge). */
|
|
27
|
+
listRecentClosed?(repository: string, createdSince: Date): Promise<readonly SweepMergedPr[]>;
|
|
22
28
|
/** Undefined means no run assessment; a merged twin or 24h idle draft can still authorize a close. */
|
|
23
29
|
getRunStatus(draft: SweepDraft, repository: string): Promise<string | undefined>;
|
|
24
30
|
/** Undefined means liveness could not be established. */
|
|
@@ -27,6 +33,8 @@ export interface DraftSweepAdapters {
|
|
|
27
33
|
closeDraft(repository: string, number: number, comment: string): Promise<void>;
|
|
28
34
|
/** Resolve the latest claim comment owner; a failed lookup must not authorize a close. */
|
|
29
35
|
getClaimOwner?(repository: string, number: number): Promise<string | undefined>;
|
|
36
|
+
/** Current owner, accounting for a later claim release; only draft metrics need this distinction. */
|
|
37
|
+
getActiveClaimOwner?(repository: string, number: number): Promise<string | undefined>;
|
|
30
38
|
/** Whether the current owned run has a self-implement.result final observation, even if its worktree remains. */
|
|
31
39
|
hasFinalRunResult?(draft: SweepDraft, repository: string): Promise<boolean | undefined>;
|
|
32
40
|
/** Per-file last change on the draft branch; undefined means file coverage is unverified. */
|
|
@@ -38,6 +46,8 @@ export interface DraftSweepAdapters {
|
|
|
38
46
|
* `review: 'pass'` is a review PASS; `mustFix` means must-fix remains; `gate: 'pass'` is a green gate.
|
|
39
47
|
*/
|
|
40
48
|
getReviewGate?(draft: SweepDraft, repository: string): Promise<SweepReviewGate | undefined>;
|
|
49
|
+
/** One PR by number (any state); undefined means it could not be read. Only overlap metrics need it. */
|
|
50
|
+
getPr?(repository: string, number: number): Promise<SweepMergedPr | undefined>;
|
|
41
51
|
}
|
|
42
52
|
|
|
43
53
|
/** Review PASS and gate posture used only by the daily old-draft count. */
|
|
@@ -96,6 +106,307 @@ export interface DraftSweepDaily {
|
|
|
96
106
|
readonly blocked: number;
|
|
97
107
|
}
|
|
98
108
|
|
|
109
|
+
/** Open harness drafts now; conversion is the 48h creation cohort, not the daily over-24h census. */
|
|
110
|
+
export interface DraftMetrics {
|
|
111
|
+
readonly inventory: number;
|
|
112
|
+
readonly oldestAgeHours: number | null;
|
|
113
|
+
readonly needsOwner: number;
|
|
114
|
+
readonly converted48h: number;
|
|
115
|
+
readonly cohort48h: number;
|
|
116
|
+
readonly conversion48h: number | null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export async function collectDraftMetrics(repository: string, adapters: Pick<DraftSweepAdapters, 'listDrafts' | 'listRecentClosed' | 'getActiveClaimOwner'>, now: Date): Promise<DraftMetrics> {
|
|
120
|
+
const clock = now.getTime();
|
|
121
|
+
if (!Number.isFinite(clock)) throw new Error('Invalid draft metric clock');
|
|
122
|
+
// A fixed, fully matured creation cohort: each PR has had its full 48h to land.
|
|
123
|
+
const cutoff = clock - 96 * 3_600_000;
|
|
124
|
+
const matured = clock - 48 * 3_600_000;
|
|
125
|
+
if (!adapters.listRecentClosed) throw new Error('48h closed cohort unavailable');
|
|
126
|
+
const [drafts, closed] = await Promise.all([
|
|
127
|
+
collectPages((page, size) => adapters.listDrafts(page, size, repository)),
|
|
128
|
+
adapters.listRecentClosed(repository, new Date(cutoff)),
|
|
129
|
+
]);
|
|
130
|
+
const valid = (pr: SweepDraft | SweepMergedPr): boolean => Number.isInteger(pr.number) && pr.number > 0
|
|
131
|
+
&& typeof pr.branch === 'string' && pr.branch.length > 0 && typeof pr.title === 'string'
|
|
132
|
+
&& typeof pr.createdAt === 'string' && Number.isFinite(Date.parse(pr.createdAt));
|
|
133
|
+
if (drafts.some((pr) => !valid(pr) || !Array.isArray(pr.labels) || pr.labels.some((name) => typeof name !== 'string'))
|
|
134
|
+
|| closed.some((pr) => !valid(pr)
|
|
135
|
+
|| (pr.mergedAt !== undefined && !Number.isFinite(Date.parse(pr.mergedAt)))
|
|
136
|
+
|| !Array.isArray(pr.labels) || pr.labels.some((name) => typeof name !== 'string')))
|
|
137
|
+
throw new Error('Incomplete draft metric inventory');
|
|
138
|
+
if (new Set(drafts.map((pr) => pr.number)).size !== drafts.length
|
|
139
|
+
|| new Set(closed.map((pr) => pr.number)).size !== closed.length) throw new Error('Duplicate draft metric PR');
|
|
140
|
+
const open = drafts.filter(harnessDraft);
|
|
141
|
+
const owners = await Promise.all(open.map(async (draft) => {
|
|
142
|
+
if (!adapters.getActiveClaimOwner) throw new Error('Draft owner lookup unavailable');
|
|
143
|
+
const owner = await adapters.getActiveClaimOwner(repository, draft.number);
|
|
144
|
+
if (owner !== undefined && typeof owner !== 'string') throw new Error('Invalid draft claim owner');
|
|
145
|
+
return owner;
|
|
146
|
+
}));
|
|
147
|
+
const cohort = [...open.filter((pr) => Date.parse(pr.createdAt) >= cutoff && Date.parse(pr.createdAt) <= matured),
|
|
148
|
+
...closed.filter((pr) => (pr.branch.startsWith('self-impl/') || pr.labels!.some((name) => name.startsWith('elanous:')))
|
|
149
|
+
&& pr.createdAt && Date.parse(pr.createdAt) >= cutoff && Date.parse(pr.createdAt) <= matured)];
|
|
150
|
+
if (new Set(cohort.map((pr) => pr.number)).size !== cohort.length) throw new Error('Overlapping draft metric inventory');
|
|
151
|
+
const converted48h = cohort.filter((pr) => 'mergedAt' in pr && typeof pr.mergedAt === 'string'
|
|
152
|
+
&& Date.parse(pr.mergedAt) >= Date.parse(pr.createdAt!) && Date.parse(pr.mergedAt) <= clock
|
|
153
|
+
&& Date.parse(pr.mergedAt) - Date.parse(pr.createdAt!) <= 48 * 3_600_000).length;
|
|
154
|
+
return {
|
|
155
|
+
inventory: open.length,
|
|
156
|
+
oldestAgeHours: open.length ? Math.max(...open.map((pr) => Math.max(0, (clock - Date.parse(pr.createdAt)) / 3_600_000))) : null,
|
|
157
|
+
needsOwner: owners.filter((owner) => !owner?.trim()).length,
|
|
158
|
+
converted48h,
|
|
159
|
+
cohort48h: cohort.length,
|
|
160
|
+
conversion48h: cohort.length ? converted48h / cohort.length : null,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** One `dev-pipeline` `ask-preflight` observation (written by ask-launch-flow before each launch). */
|
|
165
|
+
export interface OverlapPreflightRow {
|
|
166
|
+
/** ISO time as the log store returns it; anything unparseable makes the source incomplete. */
|
|
167
|
+
readonly ts: unknown;
|
|
168
|
+
/** Either the payload itself, or a Pod-forwarded wrapper `{ origin: 'pod', originalData: '<json>' }`. */
|
|
169
|
+
readonly data: Record<string, unknown> | null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Overlap launches = ask runs launched in the last 24h (no blocker, or forced past them) whose preflight
|
|
174
|
+
* names a concrete overlap: an `open-pr` / `sibling-pr` warning naming `#N`, or a `live-run` warning naming a run.
|
|
175
|
+
* The second sibling is this launch's own PR (branch run suffix `-r<runId[0..6]>`), else the PR on the warned sibling's lineage,
|
|
176
|
+
* else (DRAFT-NOT-ARCHIVE `needs-owner-only`) a `salvaged` salvage branch of the same run or lineage with no PR yet —
|
|
177
|
+
* linked as «salvaged — not landed», never auto-landed; once that branch gets a PR, the PR's merge counts as usual.
|
|
178
|
+
* ⛔ `null` is «unmeasured», never «zero»:
|
|
179
|
+
* - source incomplete (rows unreadable, zero rows, or a row whose payload cannot be parsed) ⇒ every count null, `sourceIncomplete`;
|
|
180
|
+
* - a launch with no proof whose scan was blind (open-PR scan capped/unknown, live-run scan unknown, no planned paths) ⇒ `unmeasured`, not «no overlap»;
|
|
181
|
+
* - GitHub unreadable ⇒ landing fields null;
|
|
182
|
+
* - salvage source unreadable ⇒ launches with no PR sibling count as `salvageUnmeasured`, not as «no sibling».
|
|
183
|
+
*/
|
|
184
|
+
export interface OverlapMetrics {
|
|
185
|
+
/** Launched runs whose preflight proves an overlap. */
|
|
186
|
+
readonly launches24h: number | null;
|
|
187
|
+
/** Launched runs in the window (one per runId). */
|
|
188
|
+
readonly launched: number | null;
|
|
189
|
+
/** Launched runs with no proven overlap whose overlap scan was blind. */
|
|
190
|
+
readonly unmeasured: number | null;
|
|
191
|
+
readonly sourceIncomplete: boolean;
|
|
192
|
+
/** Overlap launches whose second sibling PR was found (open draft or closed). */
|
|
193
|
+
readonly linked: number | null;
|
|
194
|
+
/** Linked second siblings merged with no human-hand label (approval / from-human / keep). */
|
|
195
|
+
readonly autoLanded: number | null;
|
|
196
|
+
readonly autoRate: number | null;
|
|
197
|
+
/** Median hours from the overlap launch to the second sibling's merge, over landed second siblings. */
|
|
198
|
+
readonly secondSiblingMedianHours: number | null;
|
|
199
|
+
/** Linked second siblings that live only on a salvage branch (no PR): «salvaged — not landed». Included in `linked`. */
|
|
200
|
+
readonly salvaged: number | null;
|
|
201
|
+
/** Linkable launches with no PR sibling whose salvage source could not be read — `linked` is a lower bound. */
|
|
202
|
+
readonly salvageUnmeasured: number | null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export type OverlapMetricAdapters = Pick<DraftSweepAdapters, 'listDrafts' | 'listRecentClosed' | 'getPr'> & {
|
|
206
|
+
/** Every ask-preflight row since `since`, all universes; null means the store could not be read. */
|
|
207
|
+
listPreflightRows(since: Date): Promise<readonly OverlapPreflightRow[] | null>;
|
|
208
|
+
/** Every `self-implement.draft-not-archive` `salvaged` row since `since`; null (or absent) means unreadable. */
|
|
209
|
+
listSalvagedRows?(since: Date): Promise<readonly OverlapPreflightRow[] | null>;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const HUMAN_HAND_LABELS: ReadonlySet<string> = new Set(PR_LABELS
|
|
213
|
+
.filter((entry) => entry.sweep.action === 'exclude' || (entry.axis === 'state' && entry.sweep.action === 'none')
|
|
214
|
+
|| (entry.axis === 'origin' && (entry.appliedBy as readonly string[]).includes('human')))
|
|
215
|
+
.map((entry) => entry.name));
|
|
216
|
+
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
217
|
+
typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
218
|
+
|
|
219
|
+
/** Unwraps a Pod-forwarded row (`originalData` as a JSON string or object); null when the payload is unreadable. */
|
|
220
|
+
export function overlapPreflightPayload(data: unknown): Record<string, unknown> | null {
|
|
221
|
+
if (!isRecord(data)) return null;
|
|
222
|
+
let payload: unknown = data;
|
|
223
|
+
if ('originalData' in data) {
|
|
224
|
+
payload = data.originalData;
|
|
225
|
+
if (typeof payload === 'string') {
|
|
226
|
+
try { payload = JSON.parse(payload); } catch { return null; }
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (!isRecord(payload) || !Array.isArray(payload.warnings) || !Array.isArray(payload.blockers)) return null;
|
|
230
|
+
return payload;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** Unwraps a DRAFT-NOT-ARCHIVE `salvaged` row (Pod-forwarded or local); null when it names no salvage branch. */
|
|
234
|
+
export function salvagedPayload(data: unknown): Record<string, unknown> | null {
|
|
235
|
+
if (!isRecord(data)) return null;
|
|
236
|
+
let payload: unknown = data;
|
|
237
|
+
if ('originalData' in data) {
|
|
238
|
+
payload = data.originalData;
|
|
239
|
+
if (typeof payload === 'string') {
|
|
240
|
+
try { payload = JSON.parse(payload); } catch { return null; }
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (!isRecord(payload) || typeof payload.salvageBranch !== 'string' || !payload.salvageBranch.startsWith('salvage/')) return null;
|
|
244
|
+
return payload;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
type SalvagedSibling = { at: number; runKey: string | null; slug: string | null; salvageBranch: string };
|
|
248
|
+
const runKeyOf = (runId: unknown): string | null => {
|
|
249
|
+
const key = typeof runId === 'string' ? runId.replace(/^run-/i, '').replace(/[^a-z0-9]/gi, '').slice(0, 6).toLowerCase() : '';
|
|
250
|
+
return key.length === 6 ? key : null;
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
/** Salvaged siblings in [since, clock]; null when the source is unreadable or any row cannot be parsed. */
|
|
254
|
+
function salvagedSiblings(rows: readonly OverlapPreflightRow[] | null | undefined, since: number, clock: number): SalvagedSibling[] | null {
|
|
255
|
+
if (!Array.isArray(rows)) return null;
|
|
256
|
+
const out: SalvagedSibling[] = [];
|
|
257
|
+
for (const row of rows) {
|
|
258
|
+
const at = Date.parse(String(row?.ts));
|
|
259
|
+
const payload = salvagedPayload(row?.data);
|
|
260
|
+
if (!Number.isFinite(at) || payload === null) return null;
|
|
261
|
+
if (at < since || at > clock) continue;
|
|
262
|
+
const salvageBranch = payload.salvageBranch as string;
|
|
263
|
+
const runKey = runKeyOf(payload.runId) ?? (/^salvage\/run-([0-9a-f]{6})\//i.exec(salvageBranch)?.[1]?.toLowerCase() ?? null);
|
|
264
|
+
out.push({ at, runKey, slug: typeof payload.branch === 'string' ? branchLineageSlug(payload.branch) : null, salvageBranch });
|
|
265
|
+
}
|
|
266
|
+
return out;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* «Salvage branches today N» for the hourly report: distinct salvage branches whose `salvaged` event falls in [dayStart, now].
|
|
271
|
+
* ⛔ Unreadable source or an unparsable row ⇒ null, never 0.
|
|
272
|
+
*/
|
|
273
|
+
export function countSalvagedToday(rows: readonly OverlapPreflightRow[] | null | undefined, dayStart: Date, now: Date): number | null {
|
|
274
|
+
const siblings = salvagedSiblings(rows, dayStart.getTime(), now.getTime());
|
|
275
|
+
return siblings === null ? null : new Set(siblings.map((sibling) => sibling.salvageBranch)).size;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
type OverlapLaunch = { at: number; runKey: string | null; prs: number[]; siblings: number[]; proven: boolean; blind: boolean };
|
|
279
|
+
|
|
280
|
+
/** Reads one launched preflight: proven overlap, blind (unmeasured), or measured clean. */
|
|
281
|
+
function overlapLaunch(payload: Record<string, unknown>, at: number): OverlapLaunch | null {
|
|
282
|
+
const blockers = payload.blockers as unknown[];
|
|
283
|
+
const forced = payload.bypassed === true;
|
|
284
|
+
// Only a launch counts: a clean preflight, or one the caller forced past its blockers.
|
|
285
|
+
if (blockers.length > 0 && !forced) return null;
|
|
286
|
+
const entries = [...(payload.warnings as unknown[]), ...(forced ? blockers : [])]
|
|
287
|
+
.filter(isRecord).map((entry) => ({ kind: entry.kind, name: typeof entry.name === 'string' ? entry.name : '' }));
|
|
288
|
+
const numbers = (kind: string) => entries.filter((entry) => entry.kind === kind)
|
|
289
|
+
.flatMap((entry) => [...entry.name.matchAll(/#(\d+)/g)].map((match) => Number(match[1])));
|
|
290
|
+
const siblings = numbers('sibling-pr');
|
|
291
|
+
const prs = [...new Set([...numbers('open-pr'), ...siblings])];
|
|
292
|
+
const liveRuns = entries.filter((entry) => entry.kind === 'live-run' && entry.name.trim() !== '').length;
|
|
293
|
+
const axisChecked = (axis: unknown) => isRecord(axis) && axis.state === 'checked';
|
|
294
|
+
// ⛔ A capped open-PR scan («열린 PR 조회 상한 N») saw only part of the PRs — its «no hit» is not a measurement.
|
|
295
|
+
const capped = entries.some((entry) => entry.kind === 'open-pr' && !/#\d+/.test(entry.name));
|
|
296
|
+
const blind = capped || !axisChecked(payload.openPrs) || !axisChecked(payload.liveRuns)
|
|
297
|
+
|| !Array.isArray(payload.paths) || payload.paths.length === 0;
|
|
298
|
+
return { at, runKey: runKeyOf(payload.runId), prs, siblings, proven: prs.length > 0 || liveRuns > 0, blind };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export async function collectOverlapMetrics(repository: string, adapters: OverlapMetricAdapters, now: Date): Promise<OverlapMetrics> {
|
|
302
|
+
const clock = now.getTime();
|
|
303
|
+
if (!Number.isFinite(clock)) throw new Error('Invalid overlap metric clock');
|
|
304
|
+
const since = clock - 24 * 3_600_000;
|
|
305
|
+
const landingUnmeasured = { linked: null, autoLanded: null, autoRate: null, secondSiblingMedianHours: null, salvaged: null, salvageUnmeasured: null };
|
|
306
|
+
const incomplete = (reason: string, extra: Record<string, unknown> = {}): OverlapMetrics => {
|
|
307
|
+
try { debug.log('drafts.metrics', 'overlap-source-incomplete', { reason, ...extra }); } catch { /* fail-soft */ }
|
|
308
|
+
return { launches24h: null, launched: null, unmeasured: null, sourceIncomplete: true, ...landingUnmeasured };
|
|
309
|
+
};
|
|
310
|
+
const rows = await adapters.listPreflightRows(new Date(since));
|
|
311
|
+
if (rows === null || !Array.isArray(rows)) return incomplete('unreadable');
|
|
312
|
+
if (rows.length === 0) return incomplete('zero-events');
|
|
313
|
+
// One launch per run: a run re-authored after a block logs several preflights (and one store can be reached twice).
|
|
314
|
+
const runs = new Map<string, OverlapLaunch>();
|
|
315
|
+
let unparsable = 0;
|
|
316
|
+
for (const row of rows) {
|
|
317
|
+
const at = Date.parse(String(row?.ts));
|
|
318
|
+
if (!Number.isFinite(at)) { unparsable++; continue; }
|
|
319
|
+
if (at < since || at > clock) continue;
|
|
320
|
+
const payload = overlapPreflightPayload(row?.data);
|
|
321
|
+
if (payload === null) { unparsable++; continue; }
|
|
322
|
+
const launch = overlapLaunch(payload, at);
|
|
323
|
+
if (!launch) continue;
|
|
324
|
+
const key = launch.runKey ?? `${at}:${String(payload.goalFile ?? '')}`;
|
|
325
|
+
const prior = runs.get(key);
|
|
326
|
+
runs.set(key, prior ? {
|
|
327
|
+
at: Math.min(prior.at, launch.at), runKey: launch.runKey,
|
|
328
|
+
prs: [...new Set([...prior.prs, ...launch.prs])], siblings: [...new Set([...prior.siblings, ...launch.siblings])],
|
|
329
|
+
proven: prior.proven || launch.proven, blind: prior.blind || launch.blind,
|
|
330
|
+
} : launch);
|
|
331
|
+
}
|
|
332
|
+
if (unparsable > 0) return incomplete('unparsable-rows', { unparsable, rows: rows.length });
|
|
333
|
+
const all = [...runs.values()];
|
|
334
|
+
const launches = all.filter((launch) => launch.proven);
|
|
335
|
+
const counts = { launches24h: launches.length, launched: all.length, unmeasured: all.filter((launch) => !launch.proven && launch.blind).length, sourceIncomplete: false };
|
|
336
|
+
const linkable = launches.filter((launch) => launch.runKey !== null || launch.siblings.length > 0);
|
|
337
|
+
// No overlap launch is a measured empty sample; overlaps that name neither a run nor a sibling leave landing unmeasured.
|
|
338
|
+
if (launches.length === 0) return { ...counts, linked: 0, autoLanded: 0, autoRate: null, secondSiblingMedianHours: null, salvaged: 0, salvageUnmeasured: 0 };
|
|
339
|
+
if (linkable.length === 0) return { ...counts, ...landingUnmeasured };
|
|
340
|
+
try {
|
|
341
|
+
if (!adapters.getPr || !adapters.listRecentClosed) throw new Error('Overlap PR lookup unavailable');
|
|
342
|
+
const [drafts, closed] = await Promise.all([
|
|
343
|
+
collectPages((page, size) => adapters.listDrafts(page, size, repository)),
|
|
344
|
+
adapters.listRecentClosed(repository, new Date(since)),
|
|
345
|
+
]);
|
|
346
|
+
const candidates = [...drafts, ...closed].filter((pr) => Number.isInteger(pr.number) && typeof pr.branch === 'string'
|
|
347
|
+
&& typeof pr.createdAt === 'string' && Number.isFinite(Date.parse(pr.createdAt)));
|
|
348
|
+
const firsts = new Map<number, SweepMergedPr | undefined>();
|
|
349
|
+
let linked = 0, autoLanded = 0, salvaged = 0, salvageUnmeasured = 0;
|
|
350
|
+
const hours: number[] = [];
|
|
351
|
+
// Read lazily: only a launch with no PR sibling needs the salvage source. ⛔ A read failure is «unmeasured», not «none».
|
|
352
|
+
let salvage: SalvagedSibling[] | null | undefined;
|
|
353
|
+
const salvageSource = async (): Promise<SalvagedSibling[] | null> => {
|
|
354
|
+
if (salvage !== undefined) return salvage;
|
|
355
|
+
let rows: readonly OverlapPreflightRow[] | null = null;
|
|
356
|
+
try { rows = adapters.listSalvagedRows ? await adapters.listSalvagedRows(new Date(since)) : null; } catch { rows = null; }
|
|
357
|
+
salvage = salvagedSiblings(rows, since, clock);
|
|
358
|
+
if (salvage === null) try { debug.log('drafts.metrics', 'overlap-salvage-unmeasured', { reason: rows === null ? 'unreadable' : 'unparsable-rows' }); } catch { /* fail-soft */ }
|
|
359
|
+
return salvage;
|
|
360
|
+
};
|
|
361
|
+
for (const launch of linkable) {
|
|
362
|
+
const after = candidates.filter((pr) => !launch.prs.includes(pr.number) && Date.parse(pr.createdAt!) >= launch.at)
|
|
363
|
+
.sort((a, b) => Date.parse(a.createdAt!) - Date.parse(b.createdAt!));
|
|
364
|
+
let second = launch.runKey === null ? undefined
|
|
365
|
+
: after.find((pr) => pr.branch.endsWith(`-r${launch.runKey}`) || pr.branch.endsWith(`-r${launch.runKey}-early`));
|
|
366
|
+
let slug: string | null = null;
|
|
367
|
+
if (!second && launch.siblings.length > 0) {
|
|
368
|
+
const number = launch.siblings[0]!;
|
|
369
|
+
if (!firsts.has(number)) firsts.set(number, await adapters.getPr(repository, number));
|
|
370
|
+
const first = firsts.get(number);
|
|
371
|
+
if (!first) throw new Error('Sibling PR unreadable');
|
|
372
|
+
slug = branchLineageSlug(first.branch);
|
|
373
|
+
if (slug !== null) second = after.find((pr) => branchLineageSlug(pr.branch) === slug);
|
|
374
|
+
}
|
|
375
|
+
if (!second) {
|
|
376
|
+
const source = await salvageSource();
|
|
377
|
+
if (source === null) { salvageUnmeasured++; continue; }
|
|
378
|
+
const branch = source.filter((sibling) => sibling.at >= launch.at
|
|
379
|
+
&& ((launch.runKey !== null && sibling.runKey === launch.runKey) || (slug !== null && sibling.slug === slug)))
|
|
380
|
+
.sort((a, b) => a.at - b.at)[0]?.salvageBranch;
|
|
381
|
+
if (branch === undefined) continue;
|
|
382
|
+
// A salvage branch later landed through its own PR counts through that PR's merge.
|
|
383
|
+
second = after.find((pr) => pr.branch === branch);
|
|
384
|
+
if (!second) { linked++; salvaged++; continue; }
|
|
385
|
+
}
|
|
386
|
+
linked++;
|
|
387
|
+
const mergedAt = 'mergedAt' in second && typeof second.mergedAt === 'string' ? Date.parse(second.mergedAt) : NaN;
|
|
388
|
+
if (!Number.isFinite(mergedAt) || mergedAt < launch.at || mergedAt > clock) continue;
|
|
389
|
+
hours.push((mergedAt - launch.at) / 3_600_000);
|
|
390
|
+
if (!(second.labels ?? []).some((name) => HUMAN_HAND_LABELS.has(name))) autoLanded++;
|
|
391
|
+
}
|
|
392
|
+
hours.sort((a, b) => a - b);
|
|
393
|
+
const mid = Math.floor(hours.length / 2);
|
|
394
|
+
return {
|
|
395
|
+
...counts,
|
|
396
|
+
linked,
|
|
397
|
+
autoLanded,
|
|
398
|
+
autoRate: linked ? autoLanded / linked : null,
|
|
399
|
+
secondSiblingMedianHours: hours.length ? (hours.length % 2 ? hours[mid]! : (hours[mid - 1]! + hours[mid]!) / 2) : null,
|
|
400
|
+
// ⛔ A salvage source we could not read leaves the salvaged count unknown, not zero.
|
|
401
|
+
salvaged: salvage === null ? null : salvaged,
|
|
402
|
+
salvageUnmeasured,
|
|
403
|
+
};
|
|
404
|
+
} catch (error) {
|
|
405
|
+
try { debug.log('drafts.metrics', 'overlap-landing-unmeasured', { error: error instanceof Error ? error.message : String(error) }); } catch { /* fail-soft */ }
|
|
406
|
+
return { ...counts, ...landingUnmeasured };
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
99
410
|
const stateLabels = PR_LABELS.filter((label) => label.axis === 'state');
|
|
100
411
|
const label = (action: 'mark-stalled' | 'close', name?: string): string => {
|
|
101
412
|
const match = PR_LABELS.find((entry) => entry.axis === 'state' && entry.sweep.action === action && (name === undefined || entry.name.endsWith(name)));
|
|
@@ -204,6 +515,31 @@ export function sweepFailureReason(error: unknown): string {
|
|
|
204
515
|
return line && line.length > 0 ? line : 'sweep failed';
|
|
205
516
|
}
|
|
206
517
|
|
|
518
|
+
/** Harness draft created at least STALLED_DRAFT_HOURS before `now` — the daily census population. */
|
|
519
|
+
function dailyCensusDraft(draft: SweepDraft, now: Date): boolean {
|
|
520
|
+
if (!harnessDraft(draft)) return false;
|
|
521
|
+
const created = Date.parse(draft.createdAt);
|
|
522
|
+
return Number.isFinite(created) && created <= now.getTime() - STALLED_DRAFT_HOURS * 3_600_000;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/** In-flight per-PR lookups during one sweep (GitHub calls and ledger reads). */
|
|
526
|
+
export const DRAFT_SWEEP_CONCURRENCY = 6;
|
|
527
|
+
|
|
528
|
+
/** Runs `work` over `items` with at most `limit` in flight; the first rejection rejects the whole map. */
|
|
529
|
+
export async function mapBounded<T, R>(items: readonly T[], limit: number, work: (item: T) => Promise<R>): Promise<R[]> {
|
|
530
|
+
const results = new Array<R>(items.length);
|
|
531
|
+
let next = 0;
|
|
532
|
+
let failed = false;
|
|
533
|
+
const workers = Array.from({ length: Math.max(1, Math.min(limit, items.length)) }, async () => {
|
|
534
|
+
while (!failed && next < items.length) {
|
|
535
|
+
const index = next++;
|
|
536
|
+
try { results[index] = await work(items[index]!); } catch (error) { failed = true; throw error; }
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
await Promise.all(workers);
|
|
540
|
+
return results;
|
|
541
|
+
}
|
|
542
|
+
|
|
207
543
|
function harnessDraft(draft: SweepDraft): boolean {
|
|
208
544
|
return draft.branch.startsWith('self-impl/') || draft.labels.some((name) => name.startsWith('elanous:'));
|
|
209
545
|
}
|
|
@@ -219,15 +555,12 @@ export function draftSweepDaily(
|
|
|
219
555
|
now: Date,
|
|
220
556
|
reviewGate: ReadonlyMap<number, SweepReviewGate | undefined>,
|
|
221
557
|
): DraftSweepDaily {
|
|
222
|
-
const cutoff = now.getTime() - STALLED_DRAFT_HOURS * 3_600_000;
|
|
223
558
|
let over24h = 0;
|
|
224
559
|
let closable = 0;
|
|
225
560
|
let harvestable = 0;
|
|
226
561
|
let blocked = 0;
|
|
227
562
|
for (const draft of drafts) {
|
|
228
|
-
if (!
|
|
229
|
-
const created = Date.parse(draft.createdAt);
|
|
230
|
-
if (!Number.isFinite(created) || created > cutoff) continue;
|
|
563
|
+
if (!dailyCensusDraft(draft, now)) continue;
|
|
231
564
|
over24h += 1;
|
|
232
565
|
if (merged.some((pr) => pr.number !== draft.number && sameGoalPr(draft, pr))) closable += 1;
|
|
233
566
|
const posture = reviewGate.get(draft.number);
|
|
@@ -251,6 +584,17 @@ export async function runDraftSweep({ repository, adapters, apply = false, now =
|
|
|
251
584
|
if (!result.complete && result.error) debug.log('drafts.cleanup', 'failed', { reason: sweepFailureReason(result.error) });
|
|
252
585
|
return result;
|
|
253
586
|
};
|
|
587
|
+
const started = Date.now();
|
|
588
|
+
let phaseStarted = started;
|
|
589
|
+
// A failed phase still reports its elapsed time (and the total) — a timeout cause must be visible either way.
|
|
590
|
+
const timing = (phase: string, count: number, outcome?: 'failed'): void => {
|
|
591
|
+
const at = Date.now();
|
|
592
|
+
try {
|
|
593
|
+
debug.log('self-dev.draft-sweep', 'timing', { phase, ms: at - phaseStarted, count, ...(outcome ? { outcome } : {}) });
|
|
594
|
+
if (outcome) debug.log('self-dev.draft-sweep', 'timing', { phase: 'total', ms: at - started, count, outcome });
|
|
595
|
+
} catch { /* fail-soft */ }
|
|
596
|
+
phaseStarted = at;
|
|
597
|
+
};
|
|
254
598
|
let drafts: SweepDraft[] = [];
|
|
255
599
|
let merged: SweepMergedPr[] = [];
|
|
256
600
|
let liveBranches: ReadonlySet<string> | undefined;
|
|
@@ -264,40 +608,49 @@ export async function runDraftSweep({ repository, adapters, apply = false, now =
|
|
|
264
608
|
if (drafts.some((pr) => !Number.isInteger(pr.number) || !pr.branch || !Array.isArray(pr.labels) || !Number.isFinite(Date.parse(pr.createdAt)))
|
|
265
609
|
|| merged.some((pr) => !Number.isInteger(pr.number) || !pr.branch)) throw new Error('Invalid PR inventory');
|
|
266
610
|
} catch (error) {
|
|
611
|
+
timing('inventory', drafts.length + merged.length, 'failed');
|
|
267
612
|
result.error = sweepFailureReason(error);
|
|
268
613
|
if (drafts.length > 0) result.daily = draftSweepDaily(drafts, merged, now, new Map());
|
|
269
614
|
return finish();
|
|
270
615
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
616
|
+
timing('inventory', drafts.length + merged.length);
|
|
617
|
+
const statuses = new Map<number, string | undefined>();
|
|
618
|
+
const finality = new Map<number, boolean | undefined>();
|
|
619
|
+
const latestFileChanges = new Map<number, Readonly<Record<string, string>> | undefined>();
|
|
274
620
|
try {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
latestFileChanges = new Map<number, Readonly<Record<string, string>> | undefined>();
|
|
278
|
-
for (const draft of drafts) {
|
|
621
|
+
// Per-draft lookups are independent: bounded concurrency, results keyed by PR number (decisions stay in list order).
|
|
622
|
+
await mapBounded(drafts, DRAFT_SWEEP_CONCURRENCY, async (draft) => {
|
|
279
623
|
const status = await adapters.getRunStatus(draft, repository);
|
|
280
624
|
// A prior final cannot terminate a new active run on the same draft.
|
|
281
625
|
const active = status === 'running' || status === 'probable-running';
|
|
282
626
|
const final = active ? false : await adapters.hasFinalRunResult?.(draft, repository);
|
|
283
627
|
finality.set(draft.number, final);
|
|
284
628
|
statuses.set(draft.number, final ? 'self-implement.result final' : status);
|
|
285
|
-
|
|
629
|
+
// Per-file history only matters to all-files-landed, which needs a later same-lineage merge.
|
|
630
|
+
if (draft.changedFiles?.length && adapters.getLatestFileChanges && laterMergedTwins(draft, merged).length > 0) {
|
|
286
631
|
latestFileChanges.set(draft.number, await adapters.getLatestFileChanges(draft, repository));
|
|
287
632
|
}
|
|
288
|
-
}
|
|
633
|
+
});
|
|
289
634
|
} catch (error) {
|
|
635
|
+
timing('run-status', drafts.length, 'failed');
|
|
290
636
|
result.error = sweepFailureReason(error);
|
|
291
637
|
return finish();
|
|
292
638
|
}
|
|
293
|
-
|
|
639
|
+
timing('run-status', drafts.length);
|
|
640
|
+
const reviewGate = new Map<number, SweepReviewGate | undefined>();
|
|
294
641
|
if (adapters.getReviewGate) {
|
|
642
|
+
// Review/gate posture only feeds the daily census, which only counts harness drafts older than 24h.
|
|
643
|
+
const censused = drafts.filter((draft) => dailyCensusDraft(draft, now));
|
|
295
644
|
try {
|
|
296
|
-
|
|
645
|
+
await mapBounded(censused, DRAFT_SWEEP_CONCURRENCY, async (draft) => {
|
|
646
|
+
reviewGate.set(draft.number, await adapters.getReviewGate!(draft, repository));
|
|
647
|
+
});
|
|
297
648
|
} catch (error) {
|
|
649
|
+
timing('review-gate', censused.length, 'failed');
|
|
298
650
|
result.error = sweepFailureReason(error);
|
|
299
651
|
return finish();
|
|
300
652
|
}
|
|
653
|
+
timing('review-gate', censused.length);
|
|
301
654
|
}
|
|
302
655
|
result.daily = draftSweepDaily(drafts, merged, now, reviewGate);
|
|
303
656
|
result.complete = true;
|
|
@@ -418,6 +771,8 @@ export async function runDraftSweep({ repository, adapters, apply = false, now =
|
|
|
418
771
|
entry.error = String(error);
|
|
419
772
|
}
|
|
420
773
|
}
|
|
774
|
+
timing('decide', drafts.length);
|
|
775
|
+
try { debug.log('self-dev.draft-sweep', 'timing', { phase: 'total', ms: Date.now() - started, count: drafts.length }); } catch { /* fail-soft */ }
|
|
421
776
|
result.unobserved = unobserved;
|
|
422
777
|
result.closed = closes;
|
|
423
778
|
result.claimed = claimed;
|
|
@@ -58,6 +58,24 @@ const referencesDraft = (text: string, number: number): boolean =>
|
|
|
58
58
|
const landingCommentReferencesDraft = (text: string, number: number): boolean =>
|
|
59
59
|
new RegExp(`\\blanding-verified\\s*:\\s*(?:draft|superseded|수확)\\s*#${number}(?!\\d)\\b`, 'i').test(text);
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Merged PRs that can cover a draft's files (all-files-landed): merged after the draft was created, on the same
|
|
63
|
+
* goal lineage, slot or branch stem. Empty ⇒ all-files-landed cannot fire, so the draft's per-file history is not needed.
|
|
64
|
+
*/
|
|
65
|
+
export function laterMergedTwins(draft: DraftTriagePr, mergedTwins: readonly DraftTriagePr[]): DraftTriagePr[] {
|
|
66
|
+
const lineage = goalId(draft.branch);
|
|
67
|
+
const draftSlot = slot(draft.body);
|
|
68
|
+
const stem = branchStem(draft.branch);
|
|
69
|
+
const created = Date.parse(draft.createdAt ?? '');
|
|
70
|
+
const compatible = (pr: DraftTriagePr): boolean =>
|
|
71
|
+
!(lineage && goalId(pr.branch) && goalId(pr.branch) !== lineage)
|
|
72
|
+
&& !(draftSlot && slot(pr.body) && slot(pr.body) !== draftSlot);
|
|
73
|
+
return mergedTwins.filter((pr) => pr.number !== draft.number && Number.isFinite(created)
|
|
74
|
+
&& Number.isFinite(Date.parse(pr.mergedAt ?? '')) && Date.parse(pr.mergedAt!) > created
|
|
75
|
+
&& compatible(pr) && ((lineage && goalId(pr.branch) === lineage)
|
|
76
|
+
|| (draftSlot && slot(pr.body) === draftSlot) || (stem && branchStem(pr.branch) === stem)));
|
|
77
|
+
}
|
|
78
|
+
|
|
61
79
|
export function decideDraft({ draft, runStatus, mergedTwins, openDrafts, ageHours, liveBranches, finalRunResult }: DraftTriageInput): DraftDecision {
|
|
62
80
|
const held = draft.labels?.find((label) => PROTECTED_LABELS.has(label));
|
|
63
81
|
if (held) return { action: 'keep', reason: `label:${held}` };
|
|
@@ -90,10 +108,7 @@ export function decideDraft({ draft, runStatus, mergedTwins, openDrafts, ageHour
|
|
|
90
108
|
if (harvest) return { action: 'close', reason: `superseded-by #${harvest.number} (harvest #${draft.number})` };
|
|
91
109
|
const created = Date.parse(draft.createdAt ?? '');
|
|
92
110
|
const files = draft.changedFiles;
|
|
93
|
-
const later =
|
|
94
|
-
&& Number.isFinite(Date.parse(pr.mergedAt ?? '')) && Date.parse(pr.mergedAt!) > created
|
|
95
|
-
&& compatible(pr) && ((lineage && goalId(pr.branch) === lineage)
|
|
96
|
-
|| (draftSlot && slot(pr.body) === draftSlot) || (stem && branchStem(pr.branch) === stem)));
|
|
111
|
+
const later = laterMergedTwins(draft, mergedTwins);
|
|
97
112
|
if (files?.length && files.every((file) => {
|
|
98
113
|
const changed = Date.parse(draft.latestFileChanges?.[file] ?? '');
|
|
99
114
|
return Number.isFinite(changed) && changed >= created && later.some((pr) =>
|
|
@@ -1494,6 +1494,7 @@ export function orchestrateSelfDev(opts: OrchestrateSelfDevOptions): Promise<Sel
|
|
|
1494
1494
|
...(disp?.branch ? { branch: disp.branch } : {}),
|
|
1495
1495
|
...(disp?.harvestable === true && disp.branch ? { harvestable: true } : {}),
|
|
1496
1496
|
...(disp?.worktreePath ? { worktreePath: disp.worktreePath } : {}),
|
|
1497
|
+
...(disp?.ok !== undefined ? { ok: disp.ok } : {}),
|
|
1497
1498
|
...(disp?.prUrl ? { prUrl: disp.prUrl } : {}),
|
|
1498
1499
|
...(disp?.prNumber !== undefined ? { prNumber: disp.prNumber } : {}),
|
|
1499
1500
|
...(disp?.merged !== undefined ? { merged: disp.merged } : {}),
|