elanous 0.2.21 → 0.2.22
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-08b716476f1d50af.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2489-ff55a35b202c0f94.js → 2489-d238d3e2eb5b2bf5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3857-5985236d9307f676.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5633-d6852385000738ef.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5977-5b9e8a45a5b0c823.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8857-aa1cc4ac90af4d40.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9247-55bcfbb0e8057f59.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-88459e0903da2803.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-d781be3e3a9cfd74.js → page-d85c43f46f58716b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/{page-b0a80997903852d8.js → page-4e645600579ae952.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-2b1263165630d9c8.js → page-87edbfd19117823b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-7f0c2b7801ac6a54.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-bf1f030a9d1adffd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-e2d6bed48ac20120.js → page-cbaf66b1e1804adf.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/{page-111cabe4d1fd69ae.js → page-80ed511e02c81412.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/control/{page-544dd4bd4847eeb7.js → page-9f5610caaa06178c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/{page-3ee191319bf97d87.js → page-ec8f3575e339de74.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-86d753e460f1a989.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-4166a7d51cf0a57d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-a449f1ed472b81c2.js → page-2a19c5cd3b562580.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-9045094d6d3b5cd0.js → page-b6a09cde2e07521a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/{page-c06673596849ef45.js → page-f7eae1518ca0c353.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-456a8573ad811381.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/{layout-9f81233f29dcd409.js → layout-8462d6a543cbb2d9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-2974cdfb87c0f9c9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-f927ffa25f3c86d1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-abd86cd832aaa011.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-79daf68f6256adaa.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-8c3f168fc3f91939.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-7a12f61be4a579d1.js → page-786fb4504d0f2fa9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{not-found-c85b0589591a99c9.js → not-found-75209f14597dcb8f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/{page-725a4da02dd129eb.js → page-bf30affe3a85af7e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1187a44b7515434b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-3d0910185a66ed54.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-f401ee1dd5a26e25.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-5dfe7604fd842784.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/{page-21bec4afeb2f0917.js → page-76cf838050a8d681.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/{page-dc39ee7a36d26385.js → page-7ec8e289b4c14df6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-84ae759f76053672.js → page-8c8ac17d52be2a79.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/{page-3db58ddb61fe2be9.js → page-d359ab048d63a9d9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-c5315e30f3528dc4.js → page-b8b345c880bf78d1.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-2fe368efb6de3b6a.js → page-f151cc10ad026940.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-9875f103057da433.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-9789328af9a9aa62.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/{page-c69762987b751174.js → page-bf495a1e284f4e00.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-a263d94bca4be22a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-3962eab8ec58d16e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-9abcfac07e02a542.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-bee49f927c333abb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-bac2d56dcb02d955.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-937cbcc13176295c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d09495594f7f480d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/{page-7f8d7fcdf058a845.js → page-69cb93d1a2e57e0e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/{page-22163556cd38bc88.js → page-72ec543928d67c3e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-82906eeda0186a7b.js +1 -0
- package/apps/pwa/out/_next/static/css/f2bd5257e6af88d8.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 +14 -14
- 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 +14 -14
- 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/live-run/index.html +1 -1
- package/apps/pwa/out/live-run/index.txt +14 -14
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +14 -14
- 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/catalog/catalog.yaml +1 -1
- package/graphs/drafts/recipes.yaml +2 -2
- package/graphs/test-diet/recipes.yaml +3 -0
- package/graphs/test-diet/test-diet.yaml +3 -1
- package/package.json +1 -1
- package/scripts/f-session-monitor.ts +69 -0
- package/scripts/hq/standby-snapshot.ts +7 -3
- package/scripts/knowledge-compress.ts +11 -0
- package/scripts/nightly-docops.ts +90 -3
- package/scripts/research-coo-admin-dates.ts +39 -0
- package/scripts/skills/public-check.ts +7 -2
- package/scripts/smart-merge/resync-shadow.ts +5 -3
- package/scripts/test-diet/lib.ts +81 -1
- package/scripts/test-diet/node.ts +48 -2
- package/scripts/verify-coo-admin-evidence.ts +83 -0
- package/src/acp/client.ts +4 -2
- package/src/agent/run-context.ts +4 -0
- package/src/agent-env/profile.ts +25 -5
- package/src/agent-mission/driver.ts +26 -12
- package/src/agent-mission/supervisor-metrics.ts +17 -2
- package/src/agent-substrate/acp-reviewer.ts +12 -5
- package/src/agent-substrate/pr-reviewer.ts +11 -4
- package/src/agent-substrate/review-intent.ts +102 -3
- package/src/autopilot/build/llm-conflict-merge.ts +22 -8
- package/src/autopilot/pr-manager.ts +18 -8
- package/src/away/release-watch.ts +47 -8
- package/src/briefing/brief-items.ts +104 -11
- package/src/chat/index.ts +1 -0
- package/src/cli/brief-cli.ts +20 -2
- package/src/cli/config-classify.ts +83 -0
- package/src/cli/control-cli.ts +14 -0
- package/src/cli/decisions-cli.ts +26 -16
- package/src/cli/logs-model-input.ts +1 -1
- package/src/cli/pr-cli.ts +65 -8
- package/src/cli/release-cli.ts +21 -4
- package/src/cli/tasks-cli.ts +108 -1
- package/src/connectors/types.ts +6 -6
- package/src/context-bus/context-now-surfaces.ts +18 -0
- package/src/context-bus/coord-events.ts +32 -4
- package/src/context-bus/long-term-memory.ts +23 -6
- package/src/context-bus/nightly-condense.ts +169 -21
- package/src/control/control-status.ts +87 -0
- package/src/control-plane/light-member.ts +5 -3
- package/src/control-plane/member.ts +172 -7
- package/src/core-turn/run-goal-loop.ts +22 -1
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +9 -0
- package/src/decisions/decision-cards.ts +20 -1
- package/src/decisions/decision-ledger.ts +11 -8
- package/src/decisions/web-push-decision-cards.ts +19 -3
- package/src/discord-self-message.ts +2 -2
- package/src/domains/coo-admin-dates.evidence.json +876 -0
- package/src/domains/coo-admin-tool.ts +48 -14
- package/src/domains/ops-status.ts +53 -0
- package/src/domains/outbound-alert.ts +24 -1
- package/src/exec-requests/planner.ts +6 -0
- package/src/graph-kinds/registry.ts +6 -6
- package/src/graph-runner/graph-cli.ts +34 -2
- package/src/graph-runner/graph-run-api.ts +7 -0
- package/src/graph-runner/graph-tick.ts +73 -12
- package/src/graph-runner/runner.ts +43 -11
- package/src/graph-unify/wf-to-graph.ts +79 -0
- package/src/graph-wizard/generate.ts +34 -2
- package/src/graph-wizard/steps.ts +13 -0
- package/src/harness/harness-cli-command.ts +121 -23
- package/src/harness/harness-queue.ts +58 -14
- package/src/harness/launch-advice.ts +23 -0
- package/src/index.ts +36 -7
- package/src/knowledge/conclusion-compress.ts +93 -0
- package/src/knowledge/know-find.ts +9 -2
- package/src/knowledge/query.ts +20 -0
- package/src/llm.ts +26 -16
- package/src/loops/neighbor-runtime.ts +88 -0
- package/src/loops/orchestrator/author-depth.ts +29 -11
- package/src/loops/orchestrator/overlap.ts +7 -1
- package/src/loops/orchestrator/resync-shadow.ts +1 -1
- package/src/loops/orchestrator/tick.ts +35 -2
- package/src/loops/orchestrator/traffic.ts +2 -0
- package/src/loops/registry.ts +5 -0
- package/src/market/publish.ts +17 -1
- package/src/maturity/feature-maturity.ts +2 -1
- package/src/mss/logging/redaction.ts +17 -0
- package/src/nexus/api/draft-metrics.ts +22 -8
- package/src/nexus/api/graph-peer-edit.ts +77 -0
- package/src/nexus/api/graph-wizard-api.ts +9 -1
- package/src/nexus/api/graphs-api.ts +145 -7
- package/src/nexus/api/http-server.ts +93 -8
- package/src/nexus/api/plugins-market.ts +24 -0
- package/src/nexus/index.ts +3 -2
- package/src/oauth/claude-pty-maturity.ts +24 -0
- package/src/oauth/fallback-chain.ts +27 -14
- package/src/plugins/maker/market-bundle.ts +9 -0
- package/src/plugins/maker/plugin-maker.ts +9 -3
- package/src/plugins/maker/wizard-generate.ts +73 -9
- package/src/policy/llm-policy.ts +11 -6
- package/src/release-loop/checklist-board.ts +27 -0
- package/src/release-loop/checklist.ts +38 -0
- package/src/seat-address/seat-address.ts +1 -0
- package/src/seat-loop/seat-loop.ts +174 -20
- package/src/seat-loop/seat-report.ts +29 -1
- package/src/self-dev/dev-cli.ts +90 -8
- package/src/self-dev/dev-pipeline.ts +20 -1
- package/src/self-dev/draft-sweep.ts +45 -8
- package/src/self-dev/orchestrate.ts +3 -0
- package/src/self-dev/review-provider-fallback.ts +83 -0
- package/src/self-dev/run-supervisor.ts +5 -1
- package/src/self-implement/harness-policy-gates.ts +7 -5
- package/src/self-implement/host-regate.ts +28 -14
- package/src/self-implement/l8-merge-queue.ts +80 -8
- package/src/self-implement/orchestrator.ts +68 -27
- package/src/self-implement/seams.ts +5 -3
- package/src/self-implement/self-implement-cli.ts +4 -0
- package/src/shell-env-bootstrap.ts +19 -4
- package/src/steward/triage.ts +25 -8
- package/src/storage/file-lock.ts +33 -4
- package/src/task-agent/completion-evidence.ts +31 -3
- package/src/task-agent/cover.ts +61 -0
- package/src/task-agent/live-moves.ts +126 -3
- package/src/task-agent/shadow.ts +19 -4
- package/src/task-agent/task-hand.ts +2 -0
- package/src/task-orchestrator/surfaces/pod-pool.ts +138 -15
- package/src/task-orchestrator/surfaces/self-implement.ts +7 -0
- package/src/telegram-commands.ts +8 -0
- package/src/telegram-project-command.ts +6 -0
- package/src/telegram.ts +2 -2
- package/src/typecheck-ratchet.ts +2 -1
- package/src/user-config.ts +35 -4
- package/src/version/packed-revision.json +1 -1
- package/src/workflow-runtime/executor.ts +24 -2
- package/src/workflow-runtime/triggers/registry.ts +27 -0
- package/src/workflow-runtime/triggers/schedule-source.ts +30 -3
- package/apps/pwa/out/_next/static/chunks/1307-c97eba9fb72a9a5e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3857-995ea9f5ef33d71e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5977-59fedea50d13c762.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8857-f1762140ef9a96b1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9247-7581825d511a64c0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9497-b0e758d1cd4ae8d7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-937c92292a4ab4f0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-ea998f7f5d156bea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-33c328b64ca508f8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-e4bcb2c503dab04b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-300897dcbbd5ed76.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-103fa3191c1b2e08.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-be8b4615c919e36c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-c685b104e0dfa609.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-8d693331cfbb6add.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-cef4f30a4c22fae1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66ea1b54fc83fa15.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-8cc707bd09a497d9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-90b55be1f15ce0e8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cb16606e08be65db.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-16dc64b74ed6a40a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-8d04132dd0ac5e10.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-f23484de467c9ea3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-5456692bda39aafb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-d75f7cd65845ce98.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-5603f5e568b25ce3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-d2cc0f4e89f42028.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-a6eb73b8a4840f1c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-c0a3f4d10edb83fa.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-6c0b88068c143f15.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-141436fd26dd56fc.js +0 -1
- package/apps/pwa/out/_next/static/css/e81df11490b4413b.css +0 -1
- /package/apps/pwa/out/_next/static/{MObU8ujkMciAKr6ljAV05 → aBi6EyMtLsrl7J8eH4kF7}/_buildManifest.js +0 -0
- /package/apps/pwa/out/_next/static/{MObU8ujkMciAKr6ljAV05 → aBi6EyMtLsrl7J8eH4kF7}/_ssgManifest.js +0 -0
|
@@ -7,6 +7,10 @@ import { debug } from '../debug/log.js';
|
|
|
7
7
|
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
8
8
|
import { getUserConfig, type SeatLoopConfig } from '../user-config.js';
|
|
9
9
|
import { collectDraftMetrics, collectOverlapMetrics, countSalvagedToday, type DraftMetrics, type OverlapMetrics } from '../self-dev/draft-sweep.js';
|
|
10
|
+
import { queryRunningRuns } from '../self-implement/running-runs.js';
|
|
11
|
+
import { readTaskAgentCover } from '../cli/tasks-cli.js';
|
|
12
|
+
import { formatTaskAgentCover } from '../task-agent/cover.js';
|
|
13
|
+
import type { TasksCliDeps } from '../cli/tasks-cli.js';
|
|
10
14
|
import { seatDay, seatLedgerPath, type OpCandidate, type SeatEntry } from './seat-loop.js';
|
|
11
15
|
|
|
12
16
|
export type SeatReportDeps = {
|
|
@@ -19,8 +23,10 @@ export type SeatReportDeps = {
|
|
|
19
23
|
send?: (body: string, seat: string, pr: number) => Promise<void> | void;
|
|
20
24
|
draftMetrics?: (now: Date) => Promise<DraftMetrics>;
|
|
21
25
|
overlapMetrics?: (now: Date) => Promise<OverlapMetrics>;
|
|
26
|
+
coverLogs?: TasksCliDeps['coverLogs'];
|
|
22
27
|
/** DRAFT-NOT-ARCHIVE salvage branches since the KST day start; null = unreadable. */
|
|
23
28
|
salvagedToday?: (dayStart: Date, now: Date) => Promise<number | null>;
|
|
29
|
+
runningRuns?: (runIds: readonly string[]) => { running: number; unknown: number } | null;
|
|
24
30
|
};
|
|
25
31
|
export type SeatReportResult = { seat: string; date: string; body: string; posted: boolean };
|
|
26
32
|
const repoRoot = resolve(import.meta.dir, '../..');
|
|
@@ -144,8 +150,30 @@ export async function seatReport(seat: string, deps: SeatReportDeps = {}): Promi
|
|
|
144
150
|
}))(new Date(`${date}T00:00:00+09:00`), now);
|
|
145
151
|
} catch { salvagedToday = null; }
|
|
146
152
|
draftDetail += ` · 수확 가지 오늘 ${salvagedToday === null ? '못 잼' : salvagedToday}`;
|
|
153
|
+
try {
|
|
154
|
+
const cover = readTaskAgentCover({}, deps.coverLogs);
|
|
155
|
+
draftDetail += ` · TASK-AGENT cover ${formatTaskAgentCover(cover.rows[0]!)}${cover.unreadableStores ? ` (logs.db ${cover.unreadableStores}개 못 읽음)` : ''}`;
|
|
156
|
+
} catch {
|
|
157
|
+
draftDetail += ` · TASK-AGENT cover ${formatTaskAgentCover({ verb: 'land', byTaskAgent: null, total: null, observedActions: null, ratio: null, state: 'unreadable', liveActions: null, shadowActions: null, stewardTransition: 'unmeasured' })}`;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const runIds = [...new Set(entries.filter((entry) => entry.status === 'launched' && entry.runId).map((entry) => entry.runId!))];
|
|
161
|
+
let runningDetail = ' · 도는 런 0';
|
|
162
|
+
if (runIds.length) {
|
|
163
|
+
try {
|
|
164
|
+
const counts = (deps.runningRuns ?? ((ids: readonly string[]) => {
|
|
165
|
+
const observed = queryRunningRuns({ runIds: ids, caller: 'seat-report' });
|
|
166
|
+
if (observed.completeness !== 'complete') return null;
|
|
167
|
+
return {
|
|
168
|
+
running: observed.entries.filter((entry) => ids.includes(entry.runId) && entry.status === 'running').length,
|
|
169
|
+
unknown: observed.entries.filter((entry) => ids.includes(entry.runId) && (entry.status === 'probable-running' || entry.status === 'unknown')).length,
|
|
170
|
+
};
|
|
171
|
+
}))(runIds);
|
|
172
|
+
runningDetail = counts === null ? ' · 도는 런 못 잼'
|
|
173
|
+
: ` · 도는 런 ${counts.running}(불확실 ${counts.unknown})`;
|
|
174
|
+
} catch { runningDetail = ' · 도는 런 못 잼'; }
|
|
147
175
|
}
|
|
148
|
-
const body = `**[${seat}]** {{TS}} → 보고 ${date}: ${detail.join(' · ') || (classified.length ? 'OP 그림자 판단' : '원장 기록 없음')}${opDetail}${draftDetail}`;
|
|
176
|
+
const body = `**[${seat}]** {{TS}} → 보고 ${date}: ${detail.join(' · ') || (classified.length ? 'OP 그림자 판단' : '원장 기록 없음')}${opDetail}${draftDetail}${runningDetail}`;
|
|
149
177
|
const pr = (deps.config ?? getUserConfig().loops?.seat)?.reportPr;
|
|
150
178
|
const posted = deps.post === true && pr !== undefined;
|
|
151
179
|
if (posted) await (deps.send ?? ((text, id, number) => defaultSend(text, id, number, deps.repo ?? repoRoot)))(body, seat, pr);
|
package/src/self-dev/dev-cli.ts
CHANGED
|
@@ -45,6 +45,7 @@ import { getUserConfig, type UserConfig } from '../user-config.js';
|
|
|
45
45
|
import { resolveChildLlmPreference, type ChildLlmPreferenceInput, type ResolvedChildLlmPreference } from '../self-implement/child-llm-preference.js';
|
|
46
46
|
import { applyLaunchOverloadFailover, codexProviderNamed } from '../oauth/overload-failover-launch.js';
|
|
47
47
|
import { readCachedGrokQuota } from '../oauth/codex-account-store.js';
|
|
48
|
+
import { appendRunLedgerEntry } from '../self-implement/run-ledger.js';
|
|
48
49
|
import { queryAbandonedDraftPrs, type AbandonedDraftPr } from '../cli/logs-abandoned-draft-prs.js';
|
|
49
50
|
import { branchLineageSlug } from '../cli/pr-lineage.js';
|
|
50
51
|
import { decideDraft, type DraftTriagePr } from './draft-triage-rules.js';
|
|
@@ -1494,6 +1495,20 @@ export function formatChildLlmInterpretationLine(
|
|
|
1494
1495
|
return `[dev] child-llm: entered=${resolution.entered} · resolved=${resolution.resolvedId} · tier=${resolution.tier} · thinking=${resolution.supportsThinking}${tail}`;
|
|
1495
1496
|
}
|
|
1496
1497
|
|
|
1498
|
+
/** Only the weekly Grok credits period measures the weekly cap; missing or malformed periods are unmeasured. */
|
|
1499
|
+
export function grokWeeklyUsedPctFromUsageJson(stdout: string): number | undefined {
|
|
1500
|
+
try {
|
|
1501
|
+
const parsed = JSON.parse(stdout) as { rows?: Array<{ provider?: string; credits?: { status?: string; periodType?: string | null; usedPercent?: unknown } }> };
|
|
1502
|
+
if (!Array.isArray(parsed.rows)) return undefined;
|
|
1503
|
+
const percents = parsed.rows
|
|
1504
|
+
.filter((row) => row?.provider === 'grok' && row.credits?.status === 'ok'
|
|
1505
|
+
&& row.credits.periodType === 'USAGE_PERIOD_TYPE_WEEKLY')
|
|
1506
|
+
.map((row) => row.credits?.usedPercent)
|
|
1507
|
+
.filter((pct): pct is number => typeof pct === 'number' && Number.isFinite(pct) && pct >= 0 && pct <= 100);
|
|
1508
|
+
return percents.length > 0 ? Math.max(...percents) : undefined;
|
|
1509
|
+
} catch { return undefined; }
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1497
1512
|
/** `elanous usage --json` 의 grok 행 → 잔량 판정. 한 행이라도 100% 미만이면 usable, 모두 100% 이상이면 exhausted. */
|
|
1498
1513
|
export function grokQuotaFromUsageJson(stdout: string): 'usable' | 'exhausted' | 'unknown' {
|
|
1499
1514
|
try {
|
|
@@ -1519,17 +1534,75 @@ export function setLaunchGrokQuotaReaderForTesting(reader: (() => 'usable' | 'ex
|
|
|
1519
1534
|
launchGrokQuotaReaderForTesting = reader;
|
|
1520
1535
|
}
|
|
1521
1536
|
|
|
1537
|
+
let launchGrokWeeklyReaderForTesting: (() => number | undefined) | undefined;
|
|
1538
|
+
export function setLaunchGrokWeeklyReaderForTesting(reader: (() => number | undefined) | undefined): void {
|
|
1539
|
+
launchGrokWeeklyReaderForTesting = reader;
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
/**
|
|
1543
|
+
* 발사 한 번에 `elanous usage --json`(약 3초) 을 «한 번만» 부른다 — 주간 사용률(재배분)과 잔량(경고)이 같은 출력을 나눈다.
|
|
1544
|
+
* 한 CLI 프로세스 = 한 발사이므로 프로세스 안 60초 캐시로 충분하다(실패 null 도 캐시 — 같은 발사에서 20초 타임아웃을 두 번 물지 않게).
|
|
1545
|
+
*/
|
|
1546
|
+
let launchUsageJsonCache: { at: number; stdout: string | null } | undefined;
|
|
1547
|
+
const LAUNCH_USAGE_CACHE_MS = 60_000;
|
|
1548
|
+
export function runLaunchUsageJsonOnce(): string | null {
|
|
1549
|
+
const now = Date.now();
|
|
1550
|
+
if (launchUsageJsonCache && now - launchUsageJsonCache.at < LAUNCH_USAGE_CACHE_MS) return launchUsageJsonCache.stdout;
|
|
1551
|
+
let stdout: string | null = null;
|
|
1552
|
+
try {
|
|
1553
|
+
const cli = new URL('../../bin/elanous.mjs', import.meta.url).pathname;
|
|
1554
|
+
const r = spawnSync(process.execPath, [cli, ...(IN_TEST_PROCESS() || process.env.ELANOUS_TEST_MODE === '1' ? ['--test'] : []), 'usage', '--json'], { encoding: 'utf8', timeout: 20_000 });
|
|
1555
|
+
stdout = r.status === 0 ? r.stdout : null;
|
|
1556
|
+
} catch { stdout = null; }
|
|
1557
|
+
launchUsageJsonCache = { at: now, stdout };
|
|
1558
|
+
return stdout;
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
/** Launch's weekly percentage comes from the live usage period, not the coarse cached usable flag. */
|
|
1562
|
+
export function readGrokWeeklyUsedPctForLaunch(runUsage?: () => string | null): number | undefined {
|
|
1563
|
+
if (launchGrokWeeklyReaderForTesting && !runUsage) return launchGrokWeeklyReaderForTesting();
|
|
1564
|
+
try {
|
|
1565
|
+
const stdout = (runUsage ?? runLaunchUsageJsonOnce)();
|
|
1566
|
+
return stdout ? grokWeeklyUsedPctFromUsageJson(stdout) : undefined;
|
|
1567
|
+
} catch { return undefined; }
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
export function rebalanceGrokChildForLaunch(
|
|
1571
|
+
child: ChildLlmSelection,
|
|
1572
|
+
cap: number,
|
|
1573
|
+
used: number | undefined,
|
|
1574
|
+
runId?: string,
|
|
1575
|
+
deps: { write?: (line: string) => void; record?: typeof appendRunLedgerEntry } = {},
|
|
1576
|
+
): ChildLlmSelection {
|
|
1577
|
+
if (child.provider !== 'grok') return child;
|
|
1578
|
+
const measured = typeof used === 'number' && Number.isFinite(used) && used >= 0 && used <= 100;
|
|
1579
|
+
const rebalanced = measured && used >= cap;
|
|
1580
|
+
const selected = rebalanced
|
|
1581
|
+
? { ...child, provider: 'openai-codex', model: defaultChildLlmModel('openai-codex'), codexQuotaPolicy: 'credits' as const }
|
|
1582
|
+
: child;
|
|
1583
|
+
const reason = !measured ? '측정 불가' : rebalanced ? `grok 주간 사용률 ${used}% ≥ 상한 ${cap}%` : `grok 주간 사용률 ${used}% < 상한 ${cap}%`;
|
|
1584
|
+
if (rebalanced || !measured) (deps.write ?? ((line) => process.stderr.write(line)))(
|
|
1585
|
+
`[dev] child-llm ${child.provider} → ${selected.provider} · ${reason}\n`,
|
|
1586
|
+
);
|
|
1587
|
+
if (rebalanced || !measured) {
|
|
1588
|
+
const data = { requestedProvider: child.provider, selectedProvider: selected.provider,
|
|
1589
|
+
weeklyUsedPct: measured ? used : null, capPct: cap, reason, selection: child.source };
|
|
1590
|
+
// 실제 전환과 «측정 못 해 그대로 둠» 을 다른 이벤트로 — 관측에서 둘을 섞지 않는다.
|
|
1591
|
+
const event = rebalanced ? 'launch-provider-rebalanced' : 'launch-provider-weekly-unmeasured';
|
|
1592
|
+
debug.log('self-implement', event, { runId: runId ?? null, ...data });
|
|
1593
|
+
if (runId) (deps.record ?? appendRunLedgerEntry)({ timestamp: new Date().toISOString(), runId,
|
|
1594
|
+
event, data: { runId, ...data } });
|
|
1595
|
+
}
|
|
1596
|
+
return selected;
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1522
1599
|
export function readGrokQuotaForLaunch(
|
|
1523
1600
|
deps: { readCached?: typeof readCachedGrokQuota; runUsage?: () => string | null } = {},
|
|
1524
1601
|
): 'usable' | 'exhausted' | 'unknown' {
|
|
1525
1602
|
if (launchGrokQuotaReaderForTesting && !deps.readCached && !deps.runUsage) return launchGrokQuotaReaderForTesting();
|
|
1526
1603
|
const cached = (deps.readCached ?? readCachedGrokQuota)();
|
|
1527
1604
|
if (cached !== 'unknown') return cached;
|
|
1528
|
-
const runUsage = deps.runUsage ??
|
|
1529
|
-
const cli = new URL('../../bin/elanous.mjs', import.meta.url).pathname;
|
|
1530
|
-
const r = spawnSync(process.execPath, [cli, 'usage', '--json'], { encoding: 'utf8', timeout: 20_000 });
|
|
1531
|
-
return r.status === 0 ? r.stdout : null;
|
|
1532
|
-
});
|
|
1605
|
+
const runUsage = deps.runUsage ?? runLaunchUsageJsonOnce;
|
|
1533
1606
|
let stdout: string | null = null;
|
|
1534
1607
|
try { stdout = runUsage(); } catch { stdout = null; }
|
|
1535
1608
|
return stdout ? grokQuotaFromUsageJson(stdout) : 'unknown';
|
|
@@ -1851,6 +1924,7 @@ export function buildDriveAliasDevSpec(
|
|
|
1851
1924
|
command: string | undefined,
|
|
1852
1925
|
opts: DevCliOpts,
|
|
1853
1926
|
explicitOptionNames?: readonly string[],
|
|
1927
|
+
runId?: string,
|
|
1854
1928
|
): DevPipelineSpec {
|
|
1855
1929
|
if (opts.elanous === true) {
|
|
1856
1930
|
throw new DevPipelineError('drive: --elanous 는 지원하지 않음 — TUI target은 elanous dev --elanous 를 사용');
|
|
@@ -1861,7 +1935,7 @@ export function buildDriveAliasDevSpec(
|
|
|
1861
1935
|
if (!opts.goal?.trim()) {
|
|
1862
1936
|
throw new DevPipelineError('drive: --goal 필요');
|
|
1863
1937
|
}
|
|
1864
|
-
const spec = buildDevCliSpec({ text: command.trim() }, { kind: 'self' }, opts, explicitOptionNames, 'cli-drive');
|
|
1938
|
+
const spec = buildDevCliSpec({ text: command.trim() }, { kind: 'self' }, opts, explicitOptionNames, 'cli-drive', undefined, undefined, runId);
|
|
1865
1939
|
return { ...spec, completion: 'worktree-only', autoReview: false };
|
|
1866
1940
|
}
|
|
1867
1941
|
|
|
@@ -1872,6 +1946,9 @@ export function buildDevCliSpec(
|
|
|
1872
1946
|
explicitOptionNames?: readonly string[],
|
|
1873
1947
|
entrance: EntranceId = 'cli-dev-ask',
|
|
1874
1948
|
readGrokQuota: typeof readCachedGrokQuota = readGrokQuotaForLaunch,
|
|
1949
|
+
readGrokWeekly: () => number | undefined = readGrokWeeklyUsedPctForLaunch,
|
|
1950
|
+
runId?: string,
|
|
1951
|
+
recordLaunch?: typeof appendRunLedgerEntry,
|
|
1875
1952
|
): DevPipelineSpec {
|
|
1876
1953
|
if (opts.plan === true) {
|
|
1877
1954
|
throw new DevPipelineError(`--plan 은 은퇴했고 명시적으로 거부됨 · 대응 문: ${DEV_PLAN_REPLACEMENT}`);
|
|
@@ -2016,8 +2093,13 @@ export function buildDevCliSpec(
|
|
|
2016
2093
|
}
|
|
2017
2094
|
|
|
2018
2095
|
if (path === 'self-mission') {
|
|
2019
|
-
const
|
|
2020
|
-
|
|
2096
|
+
const requestedChildLlm = buildChildLlmSelection({ ...opts, readPreference: () => getUserConfig() });
|
|
2097
|
+
// 주간 상한 재배분(grok → codex)을 먼저, 과부하 페일오버(codex → grok)를 그 뒤에 — 과부하는 «지금 실패 중»이라 더 강한 신호다.
|
|
2098
|
+
const capped = requestedChildLlm?.provider === 'grok'
|
|
2099
|
+
? rebalanceGrokChildForLaunch(requestedChildLlm, getUserConfig().llm.grokWeeklyCapPct ?? 80, readGrokWeekly(), runId,
|
|
2100
|
+
recordLaunch ? { record: recordLaunch } : {})
|
|
2101
|
+
: requestedChildLlm;
|
|
2102
|
+
const childLlm = applyLaunchOverloadFailover(capped, {
|
|
2021
2103
|
provider: opts.childLlmProvider,
|
|
2022
2104
|
model: opts.childLlmModel,
|
|
2023
2105
|
codexPinned: childLlmCodexPinned(opts.childLlmProvider),
|
|
@@ -43,7 +43,8 @@ import { addSelfDevRunParticipant, processBirthId, saveSelfDevRun } from './run-
|
|
|
43
43
|
import { getUserConfig, resolveRoleLlm } from '../user-config.js';
|
|
44
44
|
import { reviewReasoningEffort } from '../model-tier/review-effort.js';
|
|
45
45
|
import { getProvider, inferProviderFromModel } from '../llm.js';
|
|
46
|
-
import { buildReviewProviderAttempts, defaultReviewFallbackModel, runReviewWithFallback, reviewFallbackModelsFromConfig } from './review-provider-fallback.js';
|
|
46
|
+
import { buildReviewProviderAttempts, defaultReviewFallbackModel, runReviewWithFallback, reviewFallbackModelsFromConfig, runSubscriptionReviewer, type SubscriptionReviewerDeps } from './review-provider-fallback.js';
|
|
47
|
+
import { loadLlmPolicy } from '../policy/llm-policy.js';
|
|
47
48
|
import type { DefaultSeamsOptions } from '../self-implement/seams.js';
|
|
48
49
|
import { harnessTargetOptions, resolveHarnessTarget, revalidateHarnessTarget, type HarnessTargetResolution } from '../self-implement/harness-target-options.js';
|
|
49
50
|
import { provisionRepository, type RepoProvisionResult } from '../self-implement/repo-provision.js';
|
|
@@ -1077,6 +1078,8 @@ export async function buildDefaultSelfImplementSeams(
|
|
|
1077
1078
|
toolReviewerRoll?: () => number;
|
|
1078
1079
|
/** Tool-reviewer factory (same prompt→text shape as llmReview). Tests stub this; default is ACP. */
|
|
1079
1080
|
makeToolReviewLLM?: () => UnmannedToolReviewLLM | Promise<UnmannedToolReviewLLM>;
|
|
1081
|
+
subscriptionReviewerDeps?: SubscriptionReviewerDeps;
|
|
1082
|
+
makeSubscriptionReviewLLM?: (spawn: ReturnType<typeof import('./review-provider-fallback.js').subscriptionReviewerSpawn>) => (prompt: string) => Promise<string>;
|
|
1080
1083
|
} = {},
|
|
1081
1084
|
): Promise<SelfImplementSeams> {
|
|
1082
1085
|
const { defaultSeams } = await import('../self-implement/seams.js');
|
|
@@ -1143,6 +1146,21 @@ export async function buildDefaultSelfImplementSeams(
|
|
|
1143
1146
|
reviewerCanSelfRead = false;
|
|
1144
1147
|
}
|
|
1145
1148
|
}
|
|
1149
|
+
let subscriptionReviewLLM: ((prompt: string) => Promise<string>) | undefined;
|
|
1150
|
+
if (reviewerIsDefaultApiCall) {
|
|
1151
|
+
const userConfig = getUserConfig();
|
|
1152
|
+
const policy = loadLlmPolicy();
|
|
1153
|
+
const spec = policy.policy.roles.reviewer ?? userConfig.roleLlm?.reviewer;
|
|
1154
|
+
if (spec) {
|
|
1155
|
+
const cap = policy.policy.caps.claude.harness;
|
|
1156
|
+
subscriptionReviewLLM = (prompt) => runSubscriptionReviewer(spec, cap, prompt, defaultApiReview, async (text, spawn) => {
|
|
1157
|
+
if (deps.makeSubscriptionReviewLLM) return deps.makeSubscriptionReviewLLM(spawn)(text);
|
|
1158
|
+
const { makeAcpReviewLLM } = await import('../agent-substrate/acp-reviewer.js');
|
|
1159
|
+
return makeAcpReviewLLM({ cwd: targetOptions?.repoRoot ?? childScope.elanousBinRoot, backend: 'claude', backendSpec: spawn.backendSpec, env: spawn.env })(text);
|
|
1160
|
+
}, process.env, deps.subscriptionReviewerDeps);
|
|
1161
|
+
reviewerCanSelfRead = undefined;
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1146
1164
|
const opensPr = plan.completion !== 'worktree-only';
|
|
1147
1165
|
const target = plan.target === undefined ? undefined : revalidateHarnessTarget(plan.target);
|
|
1148
1166
|
if (target?.status === 'outside-home') {
|
|
@@ -1159,6 +1177,7 @@ export async function buildDefaultSelfImplementSeams(
|
|
|
1159
1177
|
...(plan.self?.maxWaitSec !== undefined ? { implementMaxWaitSec: plan.self.maxWaitSec } : {}),
|
|
1160
1178
|
...(plan.self?.activityGraceSec !== undefined ? { implementActivityGraceSec: plan.self.activityGraceSec } : {}),
|
|
1161
1179
|
llmReview,
|
|
1180
|
+
...(subscriptionReviewLLM ? { subscriptionReviewLLM } : {}),
|
|
1162
1181
|
...(reviewerCanSelfRead !== undefined ? { reviewerCanSelfRead } : {}),
|
|
1163
1182
|
...(deps.reviewScopeDiff ? { reviewScopeDiff: deps.reviewScopeDiff } : {}),
|
|
1164
1183
|
onProgress: ({ stage, message }) => {
|
|
@@ -62,6 +62,9 @@ export interface DraftSweepOptions {
|
|
|
62
62
|
adapters: DraftSweepAdapters;
|
|
63
63
|
apply?: boolean;
|
|
64
64
|
now?: Date;
|
|
65
|
+
/** Per-tick close cap; resolve it with resolveDraftSweepCloseCap. Absent or invalid ⇒ DRAFT_SWEEP_CLOSE_CAP. */
|
|
66
|
+
closeCap?: number;
|
|
67
|
+
closeCapSource?: DraftSweepCloseCapSource;
|
|
65
68
|
}
|
|
66
69
|
|
|
67
70
|
export interface DraftSweepEntry {
|
|
@@ -83,8 +86,11 @@ export interface DraftSweepResult {
|
|
|
83
86
|
counts: Record<string, number>;
|
|
84
87
|
/** Drafts whose run could not be observed from here (kept, or closed by the idle rule). */
|
|
85
88
|
unobserved?: number;
|
|
86
|
-
/** Closes decided this tick (at most
|
|
89
|
+
/** Closes decided this tick (at most `closeCap`). */
|
|
87
90
|
closed?: number;
|
|
91
|
+
/** The per-tick close cap this sweep used, and where it came from. */
|
|
92
|
+
closeCap?: number;
|
|
93
|
+
closeCapSource?: DraftSweepCloseCapSource;
|
|
88
94
|
/** Fresh running claims protected this tick. */
|
|
89
95
|
claimed?: number;
|
|
90
96
|
/** Running claims older than the idle window, including those kept by liveness. */
|
|
@@ -420,8 +426,36 @@ const approvalLabel = PR_LABELS.find((entry) => entry.axis === 'state' && entry.
|
|
|
420
426
|
const keepLabel = PR_LABELS.find((entry) => entry.sweep.action === 'exclude')?.name;
|
|
421
427
|
const releaseHoldLabel = PR_LABELS.find((entry) => entry.axis === 'addon' && entry.sweep.action === 'none')?.name;
|
|
422
428
|
const PAGE_SIZE = 100;
|
|
423
|
-
/** At most this many closes per tick, so a wrong rule cannot close everything at once. */
|
|
429
|
+
/** At most this many closes per tick, so a wrong rule cannot close everything at once. Default for `tools.selfImplement.draftSweepCloseCap`. */
|
|
424
430
|
export const DRAFT_SWEEP_CLOSE_CAP = 10;
|
|
431
|
+
export const DRAFT_SWEEP_CLOSE_CAP_MIN = 1;
|
|
432
|
+
export const DRAFT_SWEEP_CLOSE_CAP_MAX = 100;
|
|
433
|
+
export type DraftSweepCloseCapSource = 'flag' | 'config' | 'default';
|
|
434
|
+
|
|
435
|
+
export interface DraftSweepCloseCapResolution {
|
|
436
|
+
readonly closeCap: number;
|
|
437
|
+
readonly closeCapSource: DraftSweepCloseCapSource;
|
|
438
|
+
/** One line per rejected input (flag or config); each rejection falls through to the next source. */
|
|
439
|
+
readonly warnings: readonly string[];
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function validCloseCap(value: unknown): number | undefined {
|
|
443
|
+
const parsed = typeof value === 'string' && /^\s*\d+\s*$/.test(value) ? Number(value) : value;
|
|
444
|
+
return typeof parsed === 'number' && Number.isSafeInteger(parsed)
|
|
445
|
+
&& parsed >= DRAFT_SWEEP_CLOSE_CAP_MIN && parsed <= DRAFT_SWEEP_CLOSE_CAP_MAX ? parsed : undefined;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/** Flag over config over default; a value outside 1–100 (or not an integer) is rejected with a warning. */
|
|
449
|
+
export function resolveDraftSweepCloseCap(input: { flag?: unknown; config?: unknown }): DraftSweepCloseCapResolution {
|
|
450
|
+
const warnings: string[] = [];
|
|
451
|
+
for (const [source, value, name] of [['flag', input.flag, '--close-cap'], ['config', input.config, 'tools.selfImplement.draftSweepCloseCap']] as const) {
|
|
452
|
+
if (value === undefined || value === null) continue;
|
|
453
|
+
const cap = validCloseCap(value);
|
|
454
|
+
if (cap !== undefined) return { closeCap: cap, closeCapSource: source, warnings };
|
|
455
|
+
warnings.push(`${name}=${JSON.stringify(value)} 무시 — ${DRAFT_SWEEP_CLOSE_CAP_MIN}~${DRAFT_SWEEP_CLOSE_CAP_MAX} 정수만 (기본 ${DRAFT_SWEEP_CLOSE_CAP})`);
|
|
456
|
+
}
|
|
457
|
+
return { closeCap: DRAFT_SWEEP_CLOSE_CAP, closeCapSource: 'default', warnings };
|
|
458
|
+
}
|
|
425
459
|
|
|
426
460
|
/** Post-merge shortcut: only the existing superseded-by decision can authorize a close. */
|
|
427
461
|
export async function supersedeDraftsOnMerge(input: {
|
|
@@ -571,8 +605,11 @@ export function draftSweepDaily(
|
|
|
571
605
|
}
|
|
572
606
|
|
|
573
607
|
/** A failed inventory or liveness lookup is never treated as proof that a draft can be closed. */
|
|
574
|
-
export async function runDraftSweep({ repository, adapters, apply = false, now = new Date() }: DraftSweepOptions): Promise<DraftSweepResult> {
|
|
575
|
-
const
|
|
608
|
+
export async function runDraftSweep({ repository, adapters, apply = false, now = new Date(), closeCap: requestedCap, closeCapSource: requestedSource }: DraftSweepOptions): Promise<DraftSweepResult> {
|
|
609
|
+
const validCap = validCloseCap(requestedCap);
|
|
610
|
+
const closeCap = validCap ?? DRAFT_SWEEP_CLOSE_CAP;
|
|
611
|
+
const closeCapSource: DraftSweepCloseCapSource = validCap === undefined ? 'default' : requestedSource ?? 'flag';
|
|
612
|
+
const result: DraftSweepResult = { repository, apply, complete: false, entries: [], counts: {}, closeCap, closeCapSource };
|
|
576
613
|
const record = (entry: DraftSweepEntry): void => {
|
|
577
614
|
result.entries.push(entry);
|
|
578
615
|
result.counts[entry.reason] = (result.counts[entry.reason] ?? 0) + 1;
|
|
@@ -591,7 +628,7 @@ export async function runDraftSweep({ repository, adapters, apply = false, now =
|
|
|
591
628
|
const at = Date.now();
|
|
592
629
|
try {
|
|
593
630
|
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 });
|
|
631
|
+
if (outcome) debug.log('self-dev.draft-sweep', 'timing', { phase: 'total', ms: at - started, count, outcome, closeCap, closeCapSource });
|
|
595
632
|
} catch { /* fail-soft */ }
|
|
596
633
|
phaseStarted = at;
|
|
597
634
|
};
|
|
@@ -706,7 +743,7 @@ export async function runDraftSweep({ repository, adapters, apply = false, now =
|
|
|
706
743
|
record({ number: draft.number, action: 'keep', reason: 'claim-expired-but-live', applied: false });
|
|
707
744
|
continue;
|
|
708
745
|
}
|
|
709
|
-
const action = closes >=
|
|
746
|
+
const action = closes >= closeCap ? 'keep' : 'close';
|
|
710
747
|
if (action === 'close') closes += 1;
|
|
711
748
|
const entry: DraftSweepEntry = { number: draft.number, action,
|
|
712
749
|
reason: action === 'close' ? runStatus === 'self-implement.result final'
|
|
@@ -737,7 +774,7 @@ export async function runDraftSweep({ repository, adapters, apply = false, now =
|
|
|
737
774
|
finalRunResult, ageHours: runStatus ? ageHours : idleHours });
|
|
738
775
|
if (!runStatus && (decision.reason === 'unobserved' || decision.reason === 'stale-unobserved')) unobserved += 1;
|
|
739
776
|
if (!contradiction && decision.action === 'close') {
|
|
740
|
-
if (closes >=
|
|
777
|
+
if (closes >= closeCap) decision = { action: 'keep', reason: 'close-cap' };
|
|
741
778
|
else closes += 1;
|
|
742
779
|
}
|
|
743
780
|
const action = contradiction ? 'report' : decision.action;
|
|
@@ -772,7 +809,7 @@ export async function runDraftSweep({ repository, adapters, apply = false, now =
|
|
|
772
809
|
}
|
|
773
810
|
}
|
|
774
811
|
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 */ }
|
|
812
|
+
try { debug.log('self-dev.draft-sweep', 'timing', { phase: 'total', ms: Date.now() - started, count: drafts.length, closed: closes, closeCap, closeCapSource }); } catch { /* fail-soft */ }
|
|
776
813
|
result.unobserved = unobserved;
|
|
777
814
|
result.closed = closes;
|
|
778
815
|
result.claimed = claimed;
|
|
@@ -298,6 +298,8 @@ export interface SelfDevJobResult {
|
|
|
298
298
|
providerErrors?: { count: number; provider: string; category: 'quota' | 'credential' | 'request' | 'other' };
|
|
299
299
|
/** Stable child run identity used to join its run-ledger proposal. */
|
|
300
300
|
runId?: string;
|
|
301
|
+
/** Self review verdict bound to the PR head observed by the child; absent if either observation is missing. */
|
|
302
|
+
selfReview?: { verdict: 'pass' | 'fail'; head: string };
|
|
301
303
|
/** Whether the child review actually ran; omitted preserves legacy producers. */
|
|
302
304
|
reviewed?: boolean;
|
|
303
305
|
/** Why the child did not run review; omitted preserves legacy review-unobserved handling. */
|
|
@@ -1499,6 +1501,7 @@ export function orchestrateSelfDev(opts: OrchestrateSelfDevOptions): Promise<Sel
|
|
|
1499
1501
|
...(disp?.prNumber !== undefined ? { prNumber: disp.prNumber } : {}),
|
|
1500
1502
|
...(disp?.merged !== undefined ? { merged: disp.merged } : {}),
|
|
1501
1503
|
...(disp?.checkedHeadCommit ? { checkedHeadCommit: disp.checkedHeadCommit } : {}),
|
|
1504
|
+
...(disp?.selfReview ? { selfReview: disp.selfReview } : {}),
|
|
1502
1505
|
// ⭐ `A1` — 판정 3종을 «끝까지» 옮긴다. 여기서 빠지면 트리아지가 눈을 잃는다.
|
|
1503
1506
|
...(disp?.mergeReason ? { mergeReason: disp.mergeReason } : {}),
|
|
1504
1507
|
...(!cancelling && disp?.stopReason ? { stopReason: disp.stopReason } : {}),
|
|
@@ -19,6 +19,89 @@
|
|
|
19
19
|
// 쿼터 축은 과부하에 «반응하지 않는다».
|
|
20
20
|
import { resolveModelAlias } from '../intelligence-map/model-alias.js';
|
|
21
21
|
import type { LLMProvider } from '../llm.js';
|
|
22
|
+
import { spawnSync } from 'node:child_process';
|
|
23
|
+
import { claudeBackend, resolveBackendSpawn } from '../agent-mission/driver.js';
|
|
24
|
+
import { checkClaudeSubscription } from '../agent-mission/claude-subscription.js';
|
|
25
|
+
import { getAcpBackend, type AcpBackendSpec } from '../acp/backend-registry.js';
|
|
26
|
+
import type { SubscriptionReviewerSpec } from '../user-config.js';
|
|
27
|
+
|
|
28
|
+
export interface SubscriptionReviewerDeps {
|
|
29
|
+
/** Returns measured Claude subscription usage; unknown usage fails closed. */
|
|
30
|
+
usedPercent?: () => number | undefined;
|
|
31
|
+
checkSubscription?: (env: Record<string, string>) => boolean;
|
|
32
|
+
runStatus?: (command: string, args: string[], env: Record<string, string>) => { status: number | null; stdout?: string | Buffer | null };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The runner and the subscription probe share the PTY backend's billing-env scrub. */
|
|
36
|
+
export function subscriptionReviewerSpawn(spec: SubscriptionReviewerSpec, env: NodeJS.ProcessEnv = process.env): {
|
|
37
|
+
command: string; args: string[]; env: Record<string, string>; backendSpec: AcpBackendSpec;
|
|
38
|
+
} {
|
|
39
|
+
const executor = spec.executor ?? 'cct';
|
|
40
|
+
const { env: cleanEnv } = resolveBackendSpawn(claudeBackend, env);
|
|
41
|
+
const command = executor === 'cct' ? 'teamclaude' : 'claude';
|
|
42
|
+
const args = executor === 'cct'
|
|
43
|
+
? ['run', '--auto-fallback', '--', '--dangerously-skip-permissions']
|
|
44
|
+
: ['--dangerously-skip-permissions'];
|
|
45
|
+
return { command, args, env: cleanEnv,
|
|
46
|
+
backendSpec: { ...getAcpBackend('claude'), command, args } };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Guard the ACP review before spawning. An unmeasured quota never authorizes subscription use. */
|
|
50
|
+
export function subscriptionReviewerAvailability(
|
|
51
|
+
spec: SubscriptionReviewerSpec,
|
|
52
|
+
cap: number,
|
|
53
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
54
|
+
deps: SubscriptionReviewerDeps = {},
|
|
55
|
+
): { spawn?: ReturnType<typeof subscriptionReviewerSpawn>; reason?: string } {
|
|
56
|
+
const spawn = subscriptionReviewerSpawn(spec, env);
|
|
57
|
+
const executor = spec.executor ?? 'cct';
|
|
58
|
+
const runStatus = deps.runStatus ?? ((command: string, args: string[], cleanEnv: Record<string, string>) =>
|
|
59
|
+
spawnSync(command, args, { env: { ...cleanEnv, NODE_ENV: process.env.NODE_ENV }, encoding: 'utf8', timeout: 5_000 }));
|
|
60
|
+
const usedPercent = deps.usedPercent ?? (() => {
|
|
61
|
+
const result = runStatus('teamclaude', ['status'], spawn.env);
|
|
62
|
+
if (result.status !== 0) return undefined;
|
|
63
|
+
// Only a named usage percentage is accepted; a random percentage in status is not quota evidence.
|
|
64
|
+
const match = String(result.stdout).match(/(?:usage|used)\s*[:=]?\s*(\d+(?:\.\d+)?)\s*%/i);
|
|
65
|
+
return match ? Number(match[1]) : undefined;
|
|
66
|
+
});
|
|
67
|
+
try {
|
|
68
|
+
const subscribed = deps.checkSubscription ?? ((cleanEnv: Record<string, string>) => {
|
|
69
|
+
if (executor === 'cc') return checkClaudeSubscription({ env: cleanEnv }).ok;
|
|
70
|
+
const status = runStatus('teamclaude', ['status'], cleanEnv);
|
|
71
|
+
return status.status === 0 && /(?:subscription|claude\.ai|claude\s+(?:max|pro))/i.test(String(status.stdout))
|
|
72
|
+
&& !/(?:logged\s*out|not\s+logged\s+in|api[_ -]?key|not\s+authenticated)/i.test(String(status.stdout));
|
|
73
|
+
});
|
|
74
|
+
if (!subscribed(spawn.env)) return { reason: `${executor} subscription check failed` };
|
|
75
|
+
const used = usedPercent();
|
|
76
|
+
if (!Number.isFinite(cap) || cap < 0 || cap > 100 || used === undefined
|
|
77
|
+
|| !Number.isFinite(used) || used < 0 || used >= cap) return { reason: `Claude subscription usage unavailable or at harness cap (${cap}%)` };
|
|
78
|
+
return { spawn };
|
|
79
|
+
} catch {
|
|
80
|
+
return { reason: `${executor} subscription check failed` };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Keep a single notice attached to the actual default review result. */
|
|
85
|
+
export async function runSubscriptionReviewer(
|
|
86
|
+
spec: SubscriptionReviewerSpec | undefined,
|
|
87
|
+
cap: number,
|
|
88
|
+
prompt: string,
|
|
89
|
+
defaultReview: (prompt: string) => Promise<string>,
|
|
90
|
+
acpReview: (prompt: string, spawn: ReturnType<typeof subscriptionReviewerSpawn>) => Promise<string>,
|
|
91
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
92
|
+
deps: SubscriptionReviewerDeps = {},
|
|
93
|
+
): Promise<string> {
|
|
94
|
+
if (!spec) return defaultReview(prompt);
|
|
95
|
+
const availability = subscriptionReviewerAvailability(spec, cap, env, deps);
|
|
96
|
+
if (availability.spawn) {
|
|
97
|
+
try {
|
|
98
|
+
const text = await acpReview(prompt, availability.spawn);
|
|
99
|
+
if (text.trim()) return text;
|
|
100
|
+
} catch { /* ACP failure is not a completed review. */ }
|
|
101
|
+
}
|
|
102
|
+
const reason = availability.reason ?? 'ACP review failed';
|
|
103
|
+
return `${await defaultReview(prompt)}\n[reviewer fallback: ${reason}; default reviewer used]`;
|
|
104
|
+
}
|
|
22
105
|
|
|
23
106
|
export type ReviewFailureReason =
|
|
24
107
|
| 'overloaded'
|
|
@@ -623,7 +623,11 @@ export async function superviseRun(opts: SuperviseRunOptions): Promise<Superviso
|
|
|
623
623
|
if (decision.stopReason && opts.taskAgentShadow !== false) {
|
|
624
624
|
// TASK-AGENT-SHADOW — 판단부를 그림자로 부른다(실행 0). 어떤 오류도 런의 결말을 바꾸지 않는다.
|
|
625
625
|
try {
|
|
626
|
-
|
|
626
|
+
const pr = results.find((result) => typeof result.prNumber === 'number' && result.prNumber > 0)?.prNumber;
|
|
627
|
+
const reviewed = results.filter((result) => result.prNumber === pr && result.selfReview && result.merged !== true);
|
|
628
|
+
const selfReview = pr && reviewed.length === 1 ? reviewed[0]!.selfReview : undefined;
|
|
629
|
+
await (opts.taskAgentShadow ?? recordTaskAgentShadowMove)({ runId, stopReason: decision.stopReason, results,
|
|
630
|
+
...(selfReview ? { selfReview } : {}), cycleId: `${runId ?? 'unknown'}:${decision.round}` });
|
|
627
631
|
} catch (error) {
|
|
628
632
|
try { debug.log('task-agent', 'shadow-move-failed', { runId, stopReason: decision.stopReason, error: String(error) }); } catch { /* fail-open */ }
|
|
629
633
|
}
|
|
@@ -5,16 +5,17 @@ import { runMockModuleRestoreGate } from '../../scripts/ci-mock-module-restore-g
|
|
|
5
5
|
import { runModelHardcodeGate } from '../../scripts/ci-model-hardcode-gate.js';
|
|
6
6
|
import { runDaemonPortGate } from '../../scripts/ci-daemon-port-gate.js';
|
|
7
7
|
import { runPublicExportLeakGate } from '../../scripts/ci-public-export-leak-gate.js';
|
|
8
|
+
import { runPublicExportImportGate } from '../../scripts/ci-public-export-import-gate.js';
|
|
8
9
|
|
|
9
|
-
type GateName = 'isolation-gate' | 'mock-module-restore-gate' | 'model-hardcode-gate' | 'daemon-port-gate' | 'public-export-leak';
|
|
10
|
+
type GateName = 'isolation-gate' | 'mock-module-restore-gate' | 'model-hardcode-gate' | 'daemon-port-gate' | 'public-export-leak' | 'public-export-import';
|
|
10
11
|
type GateOutput = { args: string[]; cwd: string; log: (line: string) => void; error: (line: string) => void };
|
|
11
|
-
type GateRunner = (out: GateOutput) => number
|
|
12
|
+
type GateRunner = (out: GateOutput) => number | Promise<number>;
|
|
12
13
|
|
|
13
|
-
export function runHarnessPolicyGates(input: {
|
|
14
|
+
export async function runHarnessPolicyGates(input: {
|
|
14
15
|
cwd: string;
|
|
15
16
|
changedFiles: readonly string[];
|
|
16
17
|
gates?: Partial<Record<GateName, GateRunner>>;
|
|
17
|
-
}): { passed: boolean; failures: Array<{ gate: GateName; lines: string[] }>; skipped?: string } {
|
|
18
|
+
}): Promise<{ passed: boolean; failures: Array<{ gate: GateName; lines: string[] }>; skipped?: string }> {
|
|
18
19
|
try {
|
|
19
20
|
if (!statSync(join(input.cwd, 'scripts')).isDirectory()) {
|
|
20
21
|
return { passed: true, failures: [], skipped: 'no-scripts' };
|
|
@@ -30,12 +31,13 @@ export function runHarnessPolicyGates(input: {
|
|
|
30
31
|
['daemon-port-gate', input.gates?.['daemon-port-gate'] ?? ((out) => runDaemonPortGate({ log: out.log, error: out.error, cwd: out.cwd, args: [] }))],
|
|
31
32
|
// LEAK1 — the same pre-export check as pr land; an unmeasured result is a nonzero failure, not a pass.
|
|
32
33
|
['public-export-leak', input.gates?.['public-export-leak'] ?? runPublicExportLeakGate],
|
|
34
|
+
['public-export-import', input.gates?.['public-export-import'] ?? runPublicExportImportGate],
|
|
33
35
|
];
|
|
34
36
|
const failures: Array<{ gate: GateName; lines: string[] }> = [];
|
|
35
37
|
for (const [gate, run] of runners) {
|
|
36
38
|
const lines: string[] = [];
|
|
37
39
|
try {
|
|
38
|
-
const status = run({ cwd: input.cwd, args: ['--changed-files', ...input.changedFiles], log: (line) => lines.push(line), error: (line) => lines.push(line) });
|
|
40
|
+
const status = await run({ cwd: input.cwd, args: ['--changed-files', ...input.changedFiles], log: (line) => lines.push(line), error: (line) => lines.push(line) });
|
|
39
41
|
if (status !== 0) failures.push({ gate, lines: [...lines, `✗ ${gate}: violation detected`] });
|
|
40
42
|
} catch (error) {
|
|
41
43
|
failures.push({ gate, lines: [...lines, `⚠ ${gate}: 게이트가 «못 쟀다» — ${error instanceof Error ? error.message : String(error)}`] });
|
|
@@ -8,7 +8,7 @@ import { publicExposureFiles, runExposeGate } from './expose-gate.js';
|
|
|
8
8
|
import { admitLandingMerge } from './frozen-merges.js';
|
|
9
9
|
import { releaseGitDiffPaths, releasePathHold, releasePathHoldShouldPost, releasePathHoldComment, releasePathHoldCommentsArgs, RELEASE_PATH_LABEL } from '../self-dev/release-path-guard.js';
|
|
10
10
|
import { detectTestInterference, parseFailureCount, runBunTest } from '../../scripts/detect-test-interference.js';
|
|
11
|
-
import { runTestInterferenceGate } from '../../scripts/ci-test-interference-gate.js';
|
|
11
|
+
import { MAX_INSPECTED_TEST_FILES, runTestInterferenceGate } from '../../scripts/ci-test-interference-gate.js';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* The PWA's static build type-checks every `src/**` file it imports, under Next's stricter
|
|
@@ -22,9 +22,10 @@ export function needsPwaBuild(files: readonly string[]): boolean {
|
|
|
22
22
|
|
|
23
23
|
export interface HostRegateInput { prNumber: number; headCommit: string; repoRoot: string; goalFile?: string; verifyOnly?: boolean; /** Called by a resume sweep that already holds this PR's claim. */ resumed?: true }
|
|
24
24
|
export interface HostRegateResult { passed: boolean; failures: Array<{ step: string; detail: string }>; os: string; status?: 'passed' | 'failed' | 'unmeasured' | 'frozen'; /** verifyOnly: 실제로 얹어 잰 base 끝 */ baseCommit?: string }
|
|
25
|
+
export type InterferenceVerdict = { passed: boolean; detail?: string; unmeasured?: boolean; /** neighbour tests left out so the selection fits the inspection cap */ neighborsTrimmed?: number };
|
|
25
26
|
export type HostRegateDeps = {
|
|
26
27
|
command?: (bin: string, args: readonly string[], cwd: string, env?: NodeJS.ProcessEnv) => { status: number | null; stdout: string; stderr: string };
|
|
27
|
-
interference?: (files: readonly string[], cwd: string) => Promise<
|
|
28
|
+
interference?: (files: readonly string[], cwd: string) => Promise<InterferenceVerdict>;
|
|
28
29
|
makeTemp?: () => string;
|
|
29
30
|
removeTemp?: (path: string) => void;
|
|
30
31
|
acquire?: (repoRoot: string) => Promise<() => void>;
|
|
@@ -46,17 +47,28 @@ const defaultCommand: NonNullable<HostRegateDeps['command']> = (bin, args, cwd,
|
|
|
46
47
|
};
|
|
47
48
|
|
|
48
49
|
/** The informational pr-land gate always returns zero; enforce its measured report and the combined test result instead. */
|
|
49
|
-
async function defaultInterference(files: readonly string[], cwd: string, opts: { neighbors?: boolean } = {}): Promise<
|
|
50
|
-
const tests = files.filter((file) => /\.test\.tsx?$/.test(file));
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
//
|
|
50
|
+
export async function defaultInterference(files: readonly string[], cwd: string, opts: { neighbors?: boolean; detect?: typeof detectTestInterference } = {}): Promise<InterferenceVerdict> {
|
|
51
|
+
const tests = [...new Set(files.filter((file) => /\.test\.tsx?$/.test(file)))].sort();
|
|
52
|
+
const changed = new Set(tests);
|
|
53
|
+
const neighbors = new Set<string>();
|
|
54
|
+
// verifyOnly(승인 탭)는 «이 PR 이 바꾼 시험»만 잰다 — 이웃의 기존 실패가 이 PR 을 막지 않게
|
|
55
|
+
// (2026-09-28 #21239 실측: scripts/lib 이웃 9 · nl-routing-measurement 기존 1 fail).
|
|
54
56
|
if (opts.neighbors !== false) for (const file of tests) {
|
|
55
57
|
for (const sibling of readdirSync(join(cwd, dirname(file)))) {
|
|
56
|
-
|
|
58
|
+
const path = join(dirname(file), sibling);
|
|
59
|
+
if (/\.test\.tsx?$/.test(sibling) && !changed.has(path)) neighbors.add(path);
|
|
57
60
|
}
|
|
58
61
|
}
|
|
59
|
-
|
|
62
|
+
// 이웃이 간섭 관문의 상한(MAX_INSPECTED_TEST_FILES)을 넘기면 그 관문이 잘라 «영영 측정 불가»가 된다
|
|
63
|
+
// (2026-10-08 #25180·#25187 실측: src/self-implement 시험 175 · src/seat-loop 11). 바뀐 시험은 전부 두고 남는 칸만 이웃으로 채운다.
|
|
64
|
+
// 바뀐 시험만으로 상한을 넘으면 정직하게 «측정 불가»로 남는다.
|
|
65
|
+
const sortedNeighbors = [...neighbors].sort();
|
|
66
|
+
const room = Math.max(0, MAX_INSPECTED_TEST_FILES - tests.length);
|
|
67
|
+
const kept = tests.length > MAX_INSPECTED_TEST_FILES ? sortedNeighbors : sortedNeighbors.slice(0, room);
|
|
68
|
+
const neighborsTrimmed = sortedNeighbors.length - kept.length;
|
|
69
|
+
const trimmed = neighborsTrimmed > 0 ? { neighborsTrimmed } : {};
|
|
70
|
+
const trimNote = neighborsTrimmed > 0 ? ` · neighbours trimmed ${neighborsTrimmed} to fit inspection cap ${MAX_INSPECTED_TEST_FILES}` : '';
|
|
71
|
+
const selected = [...tests, ...kept].sort();
|
|
60
72
|
if (!selected.length) return { passed: true };
|
|
61
73
|
// 간섭 관문은 시험 파일이 둘 이상일 때만 잰다 — 하나면 간섭이라는 말이 없으니 그 파일을 한 번 돌려 실패 수로 본다.
|
|
62
74
|
if (selected.length === 1) {
|
|
@@ -69,13 +81,13 @@ async function defaultInterference(files: readonly string[], cwd: string, opts:
|
|
|
69
81
|
let report: Awaited<ReturnType<typeof detectTestInterference>> | undefined;
|
|
70
82
|
await runTestInterferenceGate({
|
|
71
83
|
args: ['--changed-files', ...selected], log: () => {},
|
|
72
|
-
detect: async (paths) => (report = await detectTestInterference(paths, (batch) => runBunTest(batch, undefined, cwd))),
|
|
84
|
+
detect: async (paths) => (report = await (opts.detect ?? detectTestInterference)(paths, (batch) => runBunTest(batch, undefined, cwd))),
|
|
73
85
|
});
|
|
74
|
-
if (!report || report.status === 'unmeasurable' || selected.length > report.order.length) return { passed: false, unmeasured: true, detail:
|
|
86
|
+
if (!report || report.status === 'unmeasurable' || selected.length > report.order.length) return { passed: false, unmeasured: true, ...trimmed, detail: `test interference measurement unavailable or truncated (selected ${selected.length}, changed tests ${tests.length}, cap ${MAX_INSPECTED_TEST_FILES})${trimNote}` };
|
|
75
87
|
if (report.isolatedFailures !== 0 || report.combinedFailures !== 0 || report.status === 'interference') {
|
|
76
|
-
return { passed: false, detail: `isolated=${report.isolatedFailures}, combined=${report.combinedFailures}, interference=${report.status}` };
|
|
88
|
+
return { passed: false, ...trimmed, detail: `isolated=${report.isolatedFailures}, combined=${report.combinedFailures}, interference=${report.status}${trimNote}` };
|
|
77
89
|
}
|
|
78
|
-
return { passed: true };
|
|
90
|
+
return { passed: true, ...trimmed, ...(trimNote ? { detail: trimNote.slice(3) } : {}) };
|
|
79
91
|
}
|
|
80
92
|
|
|
81
93
|
const SLOT_WAIT_MS = 30 * 60_000;
|
|
@@ -133,9 +145,10 @@ export async function runHostRegate(input: HostRegateInput, deps: HostRegateDeps
|
|
|
133
145
|
let endLanding: ((merged?: boolean) => void) | undefined;
|
|
134
146
|
let landed = false;
|
|
135
147
|
let verifiedBase: string | undefined;
|
|
148
|
+
let interferenceNote: Record<string, unknown> = {};
|
|
136
149
|
const result = (event: 'passed' | 'failed' | 'unmeasured', step?: string, detail?: string): HostRegateResult => {
|
|
137
150
|
if (step) failures.push({ step, detail: detail ?? 'unknown' });
|
|
138
|
-
log(event, { pr: input.prNumber, files, os: process.platform, ...(step ? { failedStep: step } : {}) });
|
|
151
|
+
log(event, { pr: input.prNumber, files, os: process.platform, ...interferenceNote, ...(step ? { failedStep: step, detail: (detail ?? 'unknown').slice(0, 300) } : {}) });
|
|
139
152
|
return { passed: event === 'passed', failures, os: process.platform, ...(input.verifyOnly ? { status: event, ...(verifiedBase ? { baseCommit: verifiedBase } : {}) } : {}) };
|
|
140
153
|
};
|
|
141
154
|
const run = (bin: string, args: readonly string[], cwd: string, env?: NodeJS.ProcessEnv): string => {
|
|
@@ -211,6 +224,7 @@ export async function runHostRegate(input: HostRegateInput, deps: HostRegateDeps
|
|
|
211
224
|
} catch (e) { return result('unmeasured', 'worktree', String(e)); }
|
|
212
225
|
try {
|
|
213
226
|
const interference = await (deps.interference ?? ((f: readonly string[], c: string) => defaultInterference(f, c, { neighbors: !input.verifyOnly })))(files, worktree);
|
|
227
|
+
if (interference.neighborsTrimmed) interferenceNote = { neighborsTrimmed: interference.neighborsTrimmed, interferenceDetail: (interference.detail ?? '').slice(0, 300) };
|
|
214
228
|
if (!interference.passed) return result(interference.unmeasured ? 'unmeasured' : 'failed', 'test-interference', interference.detail);
|
|
215
229
|
} catch (e) { return result('unmeasured', 'test-interference', String(e)); }
|
|
216
230
|
// Landing can also be initiated by the host after the child gate; judge the checked PR here too.
|