elanous 0.2.31 → 0.2.32
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/{1834-50625904996a8fa8.js → 1834-98b64172bc1efe77.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/69-0a2c8209c6d4a158.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8097-9530c03bcc550f02.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-504204b58c82c253.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-3535a4a69f1fafaa.js → page-a9717a67c2eecbaf.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-5c9ec30aee62dfbf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-584191533721fa16.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-73c4b4bb3ac7ab5f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/{page-5a503541353e60a8.js → page-c0f1ca371f710b06.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-6025101384e361a5.js → page-46430d45a9e04eac.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-3b2ebf1dc64ff0be.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/{page-a645f00a06483e9a.js → page-a27b1e5a0d1b308f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-25654b82094f7d22.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/{page-e9bab7413b028b28.js → page-8ea49a4c6dd2c6e7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-ce5113aefea0a8d1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-e547e6ce55f1fb33.js → page-0e5a41fac1c75396.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-d27e1b9850fd54bf.js → page-31aaf1ed37c6df9a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-3dcc1fa411e1f4e0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-0fec29c7ba01894b.js → page-c69247e7134d329d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{layout-339a07410cd6f36b.js → layout-66dc5cd3a0999abe.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-26ff7ff651a7217d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-617b3976981efa96.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/{page-2fd93ed8c9f16580.js → page-d256f5d5d9552823.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/market/{page-9b564dbff0fefc35.js → page-ce8f1907b8a6101c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-33e182a316263116.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-814ba51385225776.js → page-32866485913d6a04.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{not-found-397d479626e5a459.js → not-found-7607cca9b84950c6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/{page-af1bba4e3251b728.js → page-eeab86176b03dc3b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-3ad3be5e7d3fdce9.js → page-5b93dbae3432b996.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-77a6cb90a9cf2b10.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/{page-4da93ad35d4a8d28.js → page-f47e768b5b242b5a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-eb41aa3367fc2505.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-4d13ab0b8a267e3f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-84dad32ff0fc0831.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-3a658d5fa6d16a25.js → page-95306f892aa2d0ee.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/{page-0157de6e85f8bb6b.js → page-940fb6c217f6b470.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-0a07363b130182a1.js → page-8c66a3b24a745630.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-841d73d4ac44ed49.js → page-7b675dfd35c36fc9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-6f43a3d1146e6377.js → page-8bd0d3cf7665bcfc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/{page-521ee23ac8c0abfa.js → page-c219f2ed171131be.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/share/{page-a9992ee285efaa83.js → page-40f8fa21bdae955b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/{page-3d48e85fa89ec04f.js → page-468f8313e4f1fbd9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-7bbb8fa353a246c4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-04d00ae9b0890349.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/{page-233c71cd5f68f0d7.js → page-4214b855d9233ea8.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/{page-70151d78de2f375e.js → page-936752c7aa58762e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/{page-dcac33a3e2cb72c0.js → page-ec052ad359df1797.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-8305570551091338.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/{page-0448145ccd5c45c1.js → page-fad650e4793adb65.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-d90e64743826e1d8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-a16bcfd49af86a32.js +1 -0
- package/apps/pwa/out/_next/static/css/b5d88eba78a3d7bf.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 +17 -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/implement-loop.yaml +5 -0
- package/package.json +1 -1
- package/scripts/ci-typecheck-changed.ts +26 -1
- package/scripts/measure-si-task-cpu.ts +143 -0
- package/scripts/ratchets/test-interference-patterns.allowlist.txt +0 -2
- package/scripts/release-loop/prepare-node.ts +102 -4
- package/src/acp/core-turn-bridge.ts +16 -4
- package/src/acp/elanous-extensions.ts +3 -0
- package/src/acp/server.ts +3 -0
- package/src/acp/tool-trace.ts +186 -11
- package/src/agent/self-ambient-cache.ts +156 -0
- package/src/agent/self-ambient.ts +8 -1
- package/src/agent/task-notification.ts +5 -0
- package/src/agent-substrate/pr-reviewer.ts +3 -1
- package/src/ask-user-question/tool.ts +22 -0
- package/src/autopilot/build/llm-conflict-merge.ts +56 -10
- package/src/autopilot/pr-manager.ts +23 -5
- package/src/capture/frame-state-detect.ts +39 -0
- package/src/capture/tui-self-report.ts +134 -6
- package/src/chat/index.ts +61 -15
- package/src/chat/pickers/modal-runtime.ts +6 -0
- package/src/chat/pickers/modals.ts +8 -2
- package/src/cli/ask-bare.ts +1 -0
- package/src/cli/import-cli.ts +12 -0
- package/src/cli/log-event-names.ts +10 -0
- package/src/cli/msg-cli.ts +1 -0
- package/src/cli/pod-cli.ts +14 -3
- package/src/cli/pr-cli.ts +67 -32
- package/src/cli/pr-land-base-read.ts +34 -0
- package/src/cli/pr-land-heavy-remote.ts +81 -25
- package/src/cli/pr-land-overlap-evidence.ts +46 -2
- package/src/cli/pr-land-pod-shadow-job.ts +26 -3
- package/src/cli/pr-land-refusal.ts +20 -5
- package/src/cli/pty-takeover-cli.ts +88 -14
- package/src/cli/sim-cli.ts +18 -0
- package/src/context-bus/ops-events.ts +6 -2
- package/src/core-turn/run-core-turn.ts +1 -0
- package/src/core-turn/run-goal-loop.ts +21 -0
- package/src/core-turn/types.ts +3 -0
- package/src/dashboard/acp-active-model.ts +42 -1
- package/src/dashboard/background-jobs-status.ts +66 -0
- package/src/dashboard/index.ts +117 -44
- package/src/dashboard/input/agent-wake.ts +242 -0
- package/src/dashboard/input/chat-main-entry-runtime.ts +5 -0
- package/src/dashboard/input/external-input-host.ts +23 -9
- package/src/dashboard/input/mouse-wiring.ts +108 -11
- package/src/dashboard/log-click-runtime.ts +118 -2
- package/src/dashboard/turn-preamble.ts +116 -40
- package/src/dashboard/turn-session-layers.ts +4 -2
- package/src/dashboard/turn-start-segments.ts +222 -0
- package/src/display/prompt-row-owner.ts +146 -0
- package/src/display/resize-listener.ts +33 -2
- package/src/fold-stack.ts +4 -2
- package/src/git-fs/index.ts +263 -36
- package/src/harness/gate-reserve.ts +87 -0
- package/src/harness/harness-incidents.ts +13 -0
- package/src/harness/harness-queue.ts +15 -0
- package/src/index.ts +8 -6
- package/src/input/external-input-queue.ts +34 -9
- package/src/llm/route-decision.ts +36 -0
- package/src/llm/tool-loop-steered-rejection.ts +8 -0
- package/src/llm-request-shape.ts +209 -0
- package/src/llm.ts +166 -267
- package/src/loops/orchestrator/tick.ts +24 -2
- package/src/maturity/feature-maturity.ts +1 -1
- package/src/mss/logging/render-categories.ts +5 -0
- package/src/ops/overview.ts +59 -2
- package/src/pr-tool/census.ts +19 -4
- package/src/pr-tool/inflight-registry.ts +143 -0
- package/src/pty-shell/pty-manifest.ts +12 -3
- package/src/pty-shell/tui-run-state.ts +34 -3
- package/src/seat-loop/traffic-signal.ts +2 -1
- package/src/self-dev/ask-launch-flow.ts +13 -0
- package/src/self-dev/draft-residue.ts +243 -24
- package/src/self-dev/shard-plan.ts +11 -3
- package/src/self-dev/shard-run.ts +103 -10
- package/src/self-implement/elanous-tui-spawn.ts +14 -0
- package/src/self-implement/follow-up-placement.ts +79 -0
- package/src/self-implement/gate-artifacts.ts +15 -4
- package/src/self-implement/gate-baseline.ts +96 -5
- package/src/self-implement/gate-cli.ts +7 -2
- package/src/self-implement/gate-remote.ts +26 -1
- package/src/self-implement/host-regate.ts +36 -4
- package/src/self-implement/orchestrator.ts +101 -11
- package/src/self-implement/run-ledger.ts +16 -14
- package/src/session-runtime/index.ts +22 -267
- package/src/shell-runner/background-surface.ts +15 -5
- package/src/skills/tools/agent.ts +39 -6
- package/src/skills/tools/list-dir.ts +11 -0
- package/src/skills/tools/schedule-wakeup.ts +408 -0
- package/src/skills/tools/shell.ts +245 -3
- package/src/status/bar.ts +26 -0
- package/src/steward/digest-delivery.ts +118 -0
- package/src/steward/hold-hygiene.ts +120 -0
- package/src/steward/triage.ts +10 -0
- package/src/surface/shell-runner-boot.ts +5 -2
- package/src/task-agent/task-hand.ts +24 -0
- package/src/task-orchestrator/surfaces/pod-command-job.ts +65 -11
- package/src/task-orchestrator/surfaces/pod-lease.ts +98 -0
- package/src/task-orchestrator/surfaces/pod-ledger-events.ts +1 -1
- package/src/task-orchestrator/surfaces/pod-pool.ts +3 -1
- package/src/task-orchestrator/surfaces/pod-reuse-cleanup.ts +10 -6
- package/src/task-orchestrator/surfaces/pod-warm-worker-pod.ts +525 -0
- package/src/task-orchestrator/surfaces/pod-warm-worker-runner.ts +90 -0
- package/src/task-orchestrator/surfaces/pod-warm-worker-runtime.ts +156 -2
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +7 -5
- package/src/tui/chat/execution-badge.ts +5 -1
- package/src/tui/input-lag-probe.ts +230 -0
- package/src/tui.ts +4 -0
- package/src/ux-sim/scenario.ts +255 -0
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/8097-92f87bd108322f66.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-bf3634a117322df7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-b9f51447f757fbf2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-2ebb8fd59b9461f9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-a2fc4e49a4fa4ba8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-f4b630cc8b5273cd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-61ac22cbab1ee4ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-c992eafe028b642e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-805ac2e137b7a121.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-349f2752d8cd37ad.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-d5c6dcf31c25cecc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-62f4475d3c38fb9b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-f219ccdbc0bf27df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-456666766e5ce6fc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-7404e377e8adb8c5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-14456240ed2a36ad.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-d2ced71132fac9b2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-f08358bdf251adb9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-149dc1caba411bb9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-9cb0c444903da65d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-17e504c396e0e389.js +0 -1
- package/apps/pwa/out/_next/static/css/3f5fc6f96e2cf7b2.css +0 -1
- /package/apps/pwa/out/_next/static/{LSXfY0u9QtfDbejY0Ukf_ → IVKr3AlRUVLKsw2fh7eXF}/_buildManifest.js +0 -0
- /package/apps/pwa/out/_next/static/{LSXfY0u9QtfDbejY0Ukf_ → IVKr3AlRUVLKsw2fh7eXF}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// FLEX-COORD-FU-PLACE — 후속 골 초안(FollowUpDraftRecord)에 판 배치·우선순위를 «제안»한다.
|
|
2
|
+
// FLEX-RFC §A-⑧ L-d 조율자: 후속 골 칸의 판 배치·우선순위를 정하고 작업 지시서에
|
|
3
|
+
// 자율 폭·조망 예산을 싣는다. 지금 초안은 판·우선순위 없이 shadow 로만 남는다.
|
|
4
|
+
// 이 모듈은 순수 계산만 한다 — 쓰기 없음(제안만). 제안을 초안 기록에 싣고 live 일 때
|
|
5
|
+
// placeCell 로 실제 판에 놓는 이어주기는 다음 조각이 recordFollowUpOnMerge·
|
|
6
|
+
// recordFollowUpOnBlocked(follow-up-goals.ts)에서 한다.
|
|
7
|
+
|
|
8
|
+
import type { PlacementPriority } from '../release-loop/placement.js';
|
|
9
|
+
|
|
10
|
+
/** 배치 제안에 필요한 초안 칸 — FollowUpDraftRecord 의 구조 부분집합이라 그대로 건넬 수 있다. */
|
|
11
|
+
export interface FollowUpPlacementRecord {
|
|
12
|
+
cellId: string;
|
|
13
|
+
depth: number;
|
|
14
|
+
ending?: 'blocked';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** 원래 칸(제안의 기준) — 판은 `x.y.z` 체크리스트 판, 우선순위는 배치 우선순위다. */
|
|
18
|
+
export interface FollowUpParentCell {
|
|
19
|
+
version: string;
|
|
20
|
+
priority: PlacementPriority;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** 배치 제안 — 초안 기록에 실렸다가 live 배치(placeCell)의 입력이 된다. */
|
|
24
|
+
export interface FollowUpPlacementProposal {
|
|
25
|
+
cellId: string;
|
|
26
|
+
version: string;
|
|
27
|
+
priority: PlacementPriority;
|
|
28
|
+
reason: string;
|
|
29
|
+
autonomyWidth: number;
|
|
30
|
+
groundingBudget: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** 깊이 1 후속의 자율 폭(단계). 링크가 깊어질수록 1씩 좁히고 1 미만으로는 내려가지 않는다. */
|
|
34
|
+
export const FOLLOW_UP_AUTONOMY_WIDTH_BASE = 3;
|
|
35
|
+
/** 깊이 1 후속의 조망(그라운딩) 예산(근거 수). 깊이마다 절반씩 좁히고 2 미만으로는 내려가지 않는다. */
|
|
36
|
+
export const FOLLOW_UP_GROUNDING_BUDGET_BASE = 8;
|
|
37
|
+
|
|
38
|
+
const PRIORITY_STEPS: readonly PlacementPriority[] = ['P0', 'P1', 'P2'];
|
|
39
|
+
|
|
40
|
+
/** 원래 칸의 다음 판 — 마지막 칸을 하나 올린다(release-tool 의 nextVersion 와 같은 계산). */
|
|
41
|
+
export function nextReleaseVersion(version: string): string {
|
|
42
|
+
const [major, minor, patch] = version.split('.').map(Number);
|
|
43
|
+
return `${major}.${minor}.${patch! + 1}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** 깊이 2 이상이면 원래 우선순위에서 한 단 낮춘다 — P2 밑으로는 내려가지 않는다. */
|
|
47
|
+
function priorityForDepth(parent: PlacementPriority, depth: number): PlacementPriority {
|
|
48
|
+
const index = PRIORITY_STEPS.indexOf(parent);
|
|
49
|
+
if (index < 0 || depth < 2) return parent;
|
|
50
|
+
return PRIORITY_STEPS[Math.min(index + 1, PRIORITY_STEPS.length - 1)]!;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** 후속이 반복될수록 자율은 줄고 조망은 좁힌다 — 같은 칸을 도는 사슬이 제자리 걸음으로 늙지 않게. */
|
|
54
|
+
function budgetsForDepth(depth: number): { autonomyWidth: number; groundingBudget: number } {
|
|
55
|
+
const d = Math.max(1, depth);
|
|
56
|
+
return {
|
|
57
|
+
autonomyWidth: Math.max(1, FOLLOW_UP_AUTONOMY_WIDTH_BASE - (d - 1)),
|
|
58
|
+
groundingBudget: Math.max(2, Math.floor(FOLLOW_UP_GROUNDING_BUDGET_BASE / d)),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 후속 초안 한 건의 판 배치 제안 — 순수 함수, 아무것도 쓰지 않는다.
|
|
64
|
+
* 기본 = 원래 칸의 다음 판 ⊕ 같은 우선순위 · 깊이 2 이상이면 한 단 낮춤(P2 하한) ·
|
|
65
|
+
* ending 이 blocked 면 같은 판. 자율 폭·조망 예산은 깊이로 좁힌다.
|
|
66
|
+
*/
|
|
67
|
+
export function proposeFollowUpPlacement(record: FollowUpPlacementRecord, parent: FollowUpParentCell): FollowUpPlacementProposal {
|
|
68
|
+
const depth = Math.max(1, record.depth);
|
|
69
|
+
const blocked = record.ending === 'blocked';
|
|
70
|
+
const version = blocked ? parent.version : nextReleaseVersion(parent.version);
|
|
71
|
+
const priority = priorityForDepth(parent.priority, depth);
|
|
72
|
+
const { autonomyWidth, groundingBudget } = budgetsForDepth(depth);
|
|
73
|
+
const reason = `후속 깊이 ${depth} — ${blocked
|
|
74
|
+
? `막힌 런의 후속은 원래 칸과 같은 판 ${version}`
|
|
75
|
+
: `원래 칸 ${parent.version} 의 다음 판 ${version}`} · ${depth >= 2
|
|
76
|
+
? `우선순위 한 단 낮춤 ${parent.priority}→${priority}(하한 P2)`
|
|
77
|
+
: `같은 우선순위 ${priority}`} · 자율 폭 ${autonomyWidth} · 조망 예산 ${groundingBudget}`;
|
|
78
|
+
return { cellId: record.cellId, version, priority, reason, autonomyWidth, groundingBudget };
|
|
79
|
+
}
|
|
@@ -55,6 +55,8 @@ export interface GateArtifactShard {
|
|
|
55
55
|
files: string[];
|
|
56
56
|
current: GateArtifactRun;
|
|
57
57
|
baseline: GateArtifactRun;
|
|
58
|
+
/** Latest incomplete attempt only; the verdict remains unmeasured. */
|
|
59
|
+
unmeasuredReason?: string;
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
/** SIDE-VERIFY ③ — 판정 «정수»(OPT 열차 영수증 재료). 이름은 게이트 집계(`SelfGateCliResult.baseline` ·
|
|
@@ -87,6 +89,8 @@ export interface GateArtifactSummary extends GateArtifactCounts {
|
|
|
87
89
|
/** 정수 칸 중 하나라도 null 일 때만 — 왜 못 셌나(한 줄). */
|
|
88
90
|
countsUnavailableReason?: string;
|
|
89
91
|
unmeasuredReason?: string;
|
|
92
|
+
/** Per-shard cause even when no process attempt was recorded. */
|
|
93
|
+
unmeasuredByShard?: Record<string, string>;
|
|
90
94
|
/** 분할 없는 경로의 지금 쪽 `bun test` 종료 코드(그 경로엔 JUnit 이 없어 shardsDetail 이 비다). */
|
|
91
95
|
unshardedRc?: number | null;
|
|
92
96
|
artifactsUnavailable?: GateArtifactsUnavailable;
|
|
@@ -198,7 +202,12 @@ export function writeGateArtifacts(input: GateArtifactInput): { summaryPath: str
|
|
|
198
202
|
writeFileSync(run.resultPath, runResultBody(run));
|
|
199
203
|
return run;
|
|
200
204
|
};
|
|
201
|
-
shardsDetail.push({
|
|
205
|
+
shardsDetail.push({
|
|
206
|
+
shardId: attempt.shardId, attempt: attempt.attempt, files, current: side('current'), baseline: side('baseline'),
|
|
207
|
+
...(input.sharded?.unmeasuredByShard?.[attempt.shardId] !== undefined
|
|
208
|
+
&& attempt === input.sharded.attempts.filter((item) => item.shardId === attempt.shardId).at(-1)
|
|
209
|
+
? { unmeasuredReason: input.sharded.unmeasuredByShard[attempt.shardId] } : {}),
|
|
210
|
+
});
|
|
202
211
|
}
|
|
203
212
|
const counts = input.counts ?? NULL_GATE_ARTIFACT_COUNTS;
|
|
204
213
|
const summary: GateArtifactSummary = {
|
|
@@ -219,6 +228,7 @@ export function writeGateArtifacts(input: GateArtifactInput): { summaryPath: str
|
|
|
219
228
|
...(Object.values(counts).some((value) => value === null)
|
|
220
229
|
? { countsUnavailableReason: input.countsUnavailableReason ?? 'not recorded' } : {}),
|
|
221
230
|
...(input.unmeasuredReason !== undefined ? { unmeasuredReason: input.unmeasuredReason } : {}),
|
|
231
|
+
...(input.sharded?.unmeasuredByShard !== undefined ? { unmeasuredByShard: input.sharded.unmeasuredByShard } : {}),
|
|
222
232
|
...(input.unshardedRc !== undefined ? { unshardedRc: input.unshardedRc } : {}),
|
|
223
233
|
...(input.artifactsUnavailable !== undefined ? { artifactsUnavailable: input.artifactsUnavailable } : {}),
|
|
224
234
|
};
|
|
@@ -252,16 +262,17 @@ export function writeRemoteUnavailableGateArtifacts(
|
|
|
252
262
|
|
|
253
263
|
// ── 원격 산출 회수 (SIDE-VERIFY-GATE-SHARDS ②) ──────────────────────────────
|
|
254
264
|
// 원격 run 은 `$W` 임시 트리에서 돌고 끝나면 trap 이 `$W` 를 지운다(gate-remote.ts remoteScript). 그래서 원격 쪽
|
|
255
|
-
// `--artifact-dir` 는 `$W` «밖»의
|
|
265
|
+
// `--artifact-dir` 는 `$W` «밖»의 호스트 디스크 경로(/var/tmp/elanous-gate-artifacts-<nonce>)로 준다 — run 이 끝나도 남고, 회수
|
|
256
266
|
// ssh 가 읽은 뒤 지운다. 회수 프로토콜은 tar 없이 «이름 줄 · base64 한 줄» 쌍 ⊕ 끝 표지(잘림 판별).
|
|
257
267
|
|
|
258
|
-
export const REMOTE_ARTIFACT_PREFIX = '/tmp/elanous-gate-artifacts-';
|
|
268
|
+
export const REMOTE_ARTIFACT_PREFIX = '/var/tmp/elanous-gate-artifacts-';
|
|
259
269
|
const FETCH_END = '__GATE_ARTIFACTS_END__';
|
|
260
270
|
/** 다 보냈지만 원격 dir 을 못 지웠다 — 회수는 성공, 잔여는 경고로 드러낸다(조용히 남기지 않는다). */
|
|
261
271
|
const FETCH_END_KEPT = '__GATE_ARTIFACTS_END_KEPT__';
|
|
262
272
|
const FETCH_MISSING = '__GATE_ARTIFACTS_MISSING__';
|
|
263
273
|
const FETCH_READ_FAILED = '__GATE_ARTIFACTS_READ_FAILED__';
|
|
264
|
-
|
|
274
|
+
// Legacy `/tmp` receipts remain readable for in-flight runs; new paths use host disk `/var/tmp`.
|
|
275
|
+
const remoteDirPattern = /^\/(?:var\/)?tmp\/elanous-gate-artifacts-[0-9a-f]{8,64}$/;
|
|
265
276
|
const artifactNamePattern = /^[A-Za-z0-9._-]+$/;
|
|
266
277
|
export const REMOTE_ARTIFACT_FETCH_TIMEOUT_MS = 5 * 60_000;
|
|
267
278
|
|
|
@@ -28,6 +28,8 @@ export interface GateTestFailure {
|
|
|
28
28
|
baselinePresence: GateBaselinePresence;
|
|
29
29
|
attributedBy?: 'name-across-files' | 'copied-across-files';
|
|
30
30
|
precondition?: GateFailurePrecondition;
|
|
31
|
+
/** 귀속 불일치 등으로 `unknown` 판정을 내린 이유. 판정 enum 은 바꾸지 않는다. */
|
|
32
|
+
unknownReason?: '귀속 불일치';
|
|
31
33
|
/** 같은 gate 실행 안에서 같은 시험의 통과 기록이 있으면 `may-vary`로 보존한다. 타임아웃은 재실행 결과 변동성도 함께 표현한다. */
|
|
32
34
|
timeoutVariability?: TimeoutVariability;
|
|
33
35
|
/** 시간 초과한 정확한 시험이 base 로그에서는 통과해 자식 회귀로 귀속됐는지 나타낸다. */
|
|
@@ -197,6 +199,8 @@ export function baselineTimeoutForFiles(fileCount: number): number {
|
|
|
197
199
|
export interface GatePassedTestEvidence {
|
|
198
200
|
status: 'available' | 'unavailable';
|
|
199
201
|
tests?: readonly Pick<GateTestFailure, 'file' | 'name'>[];
|
|
202
|
+
/** 기준선 JUnit 의 전체 testcase 목록(통과·실패·스킵 포함). 귀속 일치 확인에 쓴다. */
|
|
203
|
+
testCases?: readonly Pick<GateTestFailure, 'file' | 'name'>[];
|
|
200
204
|
reason?: string;
|
|
201
205
|
}
|
|
202
206
|
|
|
@@ -399,11 +403,15 @@ export function extractGateTestFailures(log: string): Array<Omit<GateTestFailure
|
|
|
399
403
|
|
|
400
404
|
type GateTestIdentitySource = Pick<GateTestFailure, 'file' | 'name'>;
|
|
401
405
|
|
|
402
|
-
function
|
|
406
|
+
function gateTestName({ file, name }: GateTestIdentitySource): string | undefined {
|
|
403
407
|
if (!file) return undefined;
|
|
404
408
|
const prefix = `${file} > `;
|
|
405
|
-
|
|
406
|
-
|
|
409
|
+
return name.startsWith(prefix) ? name.slice(prefix.length) : name;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function gateTestIdentity(source: GateTestIdentitySource): string | undefined {
|
|
413
|
+
const testName = gateTestName(source);
|
|
414
|
+
return testName === undefined ? undefined : `${source.file}\u0000${testName}`;
|
|
407
415
|
}
|
|
408
416
|
|
|
409
417
|
function extractGatePassedTests(log: string): Array<Pick<GateTestFailure, 'file' | 'name'>> {
|
|
@@ -438,6 +446,44 @@ function decodeXmlEntities(value: string): string {
|
|
|
438
446
|
})[entity]!);
|
|
439
447
|
}
|
|
440
448
|
|
|
449
|
+
/**
|
|
450
|
+
* 기준선 JUnit 의 전체 testcase(통과·실패·스킵 무관)를 gate identity 형태로 만든다.
|
|
451
|
+
* 귀속 일치 확인(#27844)에 쓴다 — 콘솔 실패 헤더의 파일 귀속이 JUnit 과 어긋나는지는 통과 목록만으로는 못 본다.
|
|
452
|
+
* 파일 = 감싼 testsuite 의 `file=`(바깥으로 상속) → 없으면 testcase `file=`(Bun 산출에선 둘이 같다). `classname` 은 describe 이름일 수 있어
|
|
453
|
+
* 파일로 쓰지 않는다 — 파일을 못 정하면 그 testcase 는 빼고(귀속 불일치 «양성 신호» 없음 = 기존 판정 유지).
|
|
454
|
+
* 이름 = 콘솔 `(fail)` 줄과 같은 꼴로 describe 경로를 앞에 붙인다 — 중첩 testsuite 이름들(파일 수준 제외),
|
|
455
|
+
* 중첩이 없는 평면 JUnit 이면 testcase `classname`(파일과 다를 때).
|
|
456
|
+
*/
|
|
457
|
+
export function extractJUnitTestCases(xml: string): Array<Pick<GateTestFailure, 'file' | 'name'>> {
|
|
458
|
+
const tests: Array<Pick<GateTestFailure, 'file' | 'name'>> = [];
|
|
459
|
+
const attribute = (attributes: string, key: string): string | undefined => {
|
|
460
|
+
const raw = attributes.match(new RegExp(`\\b${key}=(?:"([^"]*)"|'([^']*)')`))?.slice(1).find((value) => value !== undefined);
|
|
461
|
+
return raw === undefined ? undefined : decodeXmlEntities(raw);
|
|
462
|
+
};
|
|
463
|
+
const suites: Array<{ file: string | undefined; name: string | undefined }> = [];
|
|
464
|
+
const tokenRe = /<testsuite\b([^>]*?)(\/?)>|<\/testsuite\s*>|<testcase\b([^>]*?)(?:\/>|>[\s\S]*?<\/testcase>)/g;
|
|
465
|
+
for (const match of xml.matchAll(tokenRe)) {
|
|
466
|
+
const token = match[0];
|
|
467
|
+
if (token.startsWith('</testsuite')) { suites.pop(); continue; }
|
|
468
|
+
if (token.startsWith('<testsuite')) {
|
|
469
|
+
if (match[2] === '/') continue;
|
|
470
|
+
const attributes = match[1] ?? '';
|
|
471
|
+
suites.push({ file: attribute(attributes, 'file') ?? suites.at(-1)?.file, name: attribute(attributes, 'name') });
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
const attributes = match[3] ?? '';
|
|
475
|
+
const testName = attribute(attributes, 'name');
|
|
476
|
+
const file = suites.at(-1)?.file ?? attribute(attributes, 'file');
|
|
477
|
+
if (!testName || !file) continue;
|
|
478
|
+
const nested = suites.flatMap((suite) => suite.name && suite.name !== file && suite.name !== suite.file ? [suite.name] : []);
|
|
479
|
+
// 평면 JUnit(describe 가 testsuite 로 중첩되지 않음)은 classname 이 describe 이름이다 — 이름 복원에만 쓴다.
|
|
480
|
+
const classname = attribute(attributes, 'classname');
|
|
481
|
+
const describePath = nested.length > 0 ? nested : classname && classname !== file ? [classname] : [];
|
|
482
|
+
tests.push({ file, name: [file, ...describePath, testName].join(' > ') });
|
|
483
|
+
}
|
|
484
|
+
return tests;
|
|
485
|
+
}
|
|
486
|
+
|
|
441
487
|
export function extractJUnitPassedTests(xml: string): Array<Pick<GateTestFailure, 'file' | 'name'>> {
|
|
442
488
|
const tests: Array<Pick<GateTestFailure, 'file' | 'name'>> = [];
|
|
443
489
|
const testcaseRe = /<testcase\b([^>]*?)(?:\/>|>([\s\S]*?)<\/testcase>)/g;
|
|
@@ -461,6 +507,19 @@ function passedIdentities(tests: readonly Pick<GateTestFailure, 'file' | 'name'>
|
|
|
461
507
|
}));
|
|
462
508
|
}
|
|
463
509
|
|
|
510
|
+
/** 기준선 JUnit 전체 testcase 를 시험명 → 파일 집합으로 묶는다. 귀속 불일치 양성 신호 확인에 쓴다. */
|
|
511
|
+
function junitFilesByTestName(testCases: readonly Pick<GateTestFailure, 'file' | 'name'>[]): Map<string, Set<string>> {
|
|
512
|
+
const filesByTestName = new Map<string, Set<string>>();
|
|
513
|
+
for (const testCase of testCases) {
|
|
514
|
+
const testName = gateTestName(testCase);
|
|
515
|
+
if (testName === undefined || !testCase.file) continue;
|
|
516
|
+
const files = filesByTestName.get(testName) ?? new Set<string>();
|
|
517
|
+
files.add(testCase.file);
|
|
518
|
+
filesByTestName.set(testName, files);
|
|
519
|
+
}
|
|
520
|
+
return filesByTestName;
|
|
521
|
+
}
|
|
522
|
+
|
|
464
523
|
export function classifyGateTestFailures(
|
|
465
524
|
worktreeLog: string,
|
|
466
525
|
baselineLog: string | undefined,
|
|
@@ -505,6 +564,21 @@ export function classifyGateTestFailures(
|
|
|
505
564
|
const identity = gateTestIdentity(failure);
|
|
506
565
|
return identity ? [identity] : [];
|
|
507
566
|
}));
|
|
567
|
+
// #27844: Bun 콘솔의 실패 헤더 귀속은 신뢰할 수 없다 — introduced 로 단정하기 전에
|
|
568
|
+
// 기준선 JUnit(testsuite file= · classname)으로 귀속을 확인한다. 기준선은 base worktree 라
|
|
569
|
+
// worktree 에서 새로 추가한 시험명을 담을 수 없어서 «이름이 없다»만으로는 귀속 불일치라 못
|
|
570
|
+
// 한다. 시험명이 «다른 파일»에 존재한다는 양성 신호가 있을 때만 귀속 불일치로 판정하고,
|
|
571
|
+
// 시험명이 귀속 파일의 JUnit 에 있으면, 또는 어디에도 없으면(신규 시험) 기존 판정을 유지한다.
|
|
572
|
+
const baselineJUnitFilesByTestName = baselinePassedEvidence?.status === 'available' && baselinePassedEvidence.testCases !== undefined
|
|
573
|
+
? junitFilesByTestName(baselinePassedEvidence.testCases)
|
|
574
|
+
: undefined;
|
|
575
|
+
const junitAttributionMismatch = (failure: GateTestIdentitySource): boolean => {
|
|
576
|
+
if (!baselineJUnitFilesByTestName) return false;
|
|
577
|
+
const testName = gateTestName(failure);
|
|
578
|
+
if (testName === undefined || !failure.file) return false;
|
|
579
|
+
const files = baselineJUnitFilesByTestName.get(testName);
|
|
580
|
+
return files !== undefined && !files.has(failure.file);
|
|
581
|
+
};
|
|
508
582
|
const comparable = worktreeFailures.filter((failure) => !isTimeoutDiagnostic(failure.diagnostic) && !matchedGatePrecondition(failure.diagnostic ?? '') && !(failure.file && missing.has(failure.file)) && baselineLog !== undefined && Boolean(failure.file));
|
|
509
583
|
const comparison = classifyAgainstBaseline(baselineFailures, comparable, (failure) => failure.name);
|
|
510
584
|
const preexisting = new Set(comparison.preexisting);
|
|
@@ -585,10 +659,18 @@ export function classifyGateTestFailures(
|
|
|
585
659
|
debug.log('self-implement.gate', 'attributed-copy-across-files', { name: path, file: failure.file, baselineFile: original.file });
|
|
586
660
|
return { ...failure, attribution: 'preexisting' as const, attributedBy: 'copied-across-files' as const, baselinePresence };
|
|
587
661
|
}
|
|
662
|
+
if (junitAttributionMismatch(failure)) {
|
|
663
|
+
debug.log('self-implement.gate', 'attribution-mismatch', { name: failure.name, file: failure.file });
|
|
664
|
+
return { ...failure, attribution: 'unknown' as const, baselinePresence, unknownReason: '귀속 불일치' as const };
|
|
665
|
+
}
|
|
588
666
|
return { ...failure, attribution: 'introduced' as const, baselinePresence };
|
|
589
667
|
}
|
|
590
668
|
if (baselineLog === undefined || !failure.file) return { ...failure, attribution: 'unknown' as const, baselinePresence };
|
|
591
669
|
const attribution = preexisting.has(failure) ? 'preexisting' as const : 'introduced' as const;
|
|
670
|
+
if (attribution === 'introduced' && junitAttributionMismatch(failure)) {
|
|
671
|
+
debug.log('self-implement.gate', 'attribution-mismatch', { name: failure.name, file: failure.file });
|
|
672
|
+
return { ...failure, attribution: 'unknown' as const, baselinePresence, unknownReason: '귀속 불일치' as const };
|
|
673
|
+
}
|
|
592
674
|
// 재실행 관측이 `pass`일 때만 흔들림으로 강등한다. 관측이 없거나 실패면 원래 귀속을 유지한다.
|
|
593
675
|
if (attribution === 'introduced' && observationsFor(failure.name).includes('pass')) {
|
|
594
676
|
return { ...failure, attribution: 'flaky-rerun' as const, baselinePresence };
|
|
@@ -730,7 +812,15 @@ function runBaselineBunTest(cwd: string, args: string[], timeout: number): Proce
|
|
|
730
812
|
if (!existsSync(reportPath)) {
|
|
731
813
|
return { ...process, passedTestEvidence: { status: 'unavailable', reason: 'JUnit passed-test evidence unavailable: reporter outfile was not created' } };
|
|
732
814
|
}
|
|
733
|
-
|
|
815
|
+
const junitXml = readFileSync(reportPath, 'utf8');
|
|
816
|
+
return {
|
|
817
|
+
...process,
|
|
818
|
+
passedTestEvidence: {
|
|
819
|
+
status: 'available',
|
|
820
|
+
tests: extractJUnitPassedTests(junitXml),
|
|
821
|
+
testCases: extractJUnitTestCases(junitXml),
|
|
822
|
+
},
|
|
823
|
+
};
|
|
734
824
|
} catch (error) {
|
|
735
825
|
return { ...process, passedTestEvidence: { status: 'unavailable', reason: `JUnit passed-test evidence unreadable: ${String(error)}` } };
|
|
736
826
|
}
|
|
@@ -1343,7 +1433,8 @@ export function formatGateBaselineNote(report: GateBaselineReport, unrunImporter
|
|
|
1343
1433
|
+ (report.missingAtBase > 0 ? `, missing-at-base=${report.missingAtBase}` : '');
|
|
1344
1434
|
const lines = report.failures.map((failure) => `- ${failure.attribution}: ${failure.name}`
|
|
1345
1435
|
+ (includesBaselinePresence ? ` [baseline=${failure.baselinePresence}]` : '')
|
|
1346
|
-
+ (failure.precondition ? ` (${failure.precondition.name}; ${failure.precondition.remediation})` : '')
|
|
1436
|
+
+ (failure.precondition ? ` (${failure.precondition.name}; ${failure.precondition.remediation})` : '')
|
|
1437
|
+
+ (failure.unknownReason ? ` (${failure.unknownReason})` : ''));
|
|
1347
1438
|
const timedOutFailures = report.failures.filter((failure) => failure.attribution === 'flaky-timeout');
|
|
1348
1439
|
const { mayVary, same, unknown } = report.timeoutVariabilityCounts;
|
|
1349
1440
|
const timedOutLine = timedOutFailures.length > 0
|
|
@@ -859,7 +859,7 @@ function runSelfGateCliCore(cwd: string, options: SelfGateCliOptions, deps: Self
|
|
|
859
859
|
: runShards(cwd, testFiles, selection.baseRef, shardCount);
|
|
860
860
|
capture.sharded = sharded;
|
|
861
861
|
capture.verdict = sharded.aggregate.status;
|
|
862
|
-
lines.push(`shards: ${sharded.shards.length} (${sharded.shards.map((shard) => `${shard.id}=${shard.files.join(',')}`).join('; ')})${sharded.measurementFailures ? ` · 측정 실패 ${sharded.measurementFailures}` : ''}${options.fixedShardDistribution ? ' · 고정 분배(사전 측정 없음)' : ''}`);
|
|
862
|
+
lines.push(`shards: ${sharded.shards.length} (${sharded.shards.map((shard) => `${shard.id}=${shard.files.join(',')}`).join('; ')})${sharded.expandedFrom !== undefined ? ` · RSS 예산상 ${sharded.expandedFrom}→${sharded.shards.length} 자동 확장` : ''}${sharded.measurementFailures ? ` · 측정 실패 ${sharded.measurementFailures}` : ''}${options.fixedShardDistribution ? ' · 고정 분배(사전 측정 없음)' : ''}${sharded.distribution === 'fixed-cap-fallback' ? ` · 호스트 상한 자동 고정 분배(${sharded.fallbackReason})` : ''}`);
|
|
863
863
|
// 조각이 «못 쟀다»일 때 이유가 줄에 없으면 자리·시간 초과·죽음을 가를 수 없다(OP 10-09 light RC) — 종료 코드·신호를 같이 싣는다.
|
|
864
864
|
const side = (code: number | null, signal: string | null | undefined, junit: string | undefined) => signal ? `signal ${signal}` : code === null ? 'exit ?' : `exit ${code}${junit === undefined ? ' · junit 없음' : ''}`;
|
|
865
865
|
for (const attempt of sharded.attempts) {
|
|
@@ -886,7 +886,12 @@ function runSelfGateCliCore(cwd: string, options: SelfGateCliOptions, deps: Self
|
|
|
886
886
|
let shardBaseline: SelfGateCliResult['baseline'];
|
|
887
887
|
let unmeasuredReason: string | undefined;
|
|
888
888
|
if (aggregate.status === 'unmeasured') {
|
|
889
|
-
unmeasuredReason =
|
|
889
|
+
unmeasuredReason = sharded.reason
|
|
890
|
+
? redactSecretText(sharded.reason).replace(/[\r\n\t\x00-\x1f\x7f]+/g, ' ').slice(0, 500)
|
|
891
|
+
: aggregate.retryShardIds.join(', ') || 'no complete JUnit';
|
|
892
|
+
for (const shardId of aggregate.retryShardIds) {
|
|
893
|
+
lines.push(`shard unmeasured: ${shardId} · ${sharded.unmeasuredByShard?.[shardId] ?? sharded.reason ?? 'cause unavailable'}`);
|
|
894
|
+
}
|
|
890
895
|
lines.push(`shards: unmeasured (${unmeasuredReason})`);
|
|
891
896
|
capture.unmeasuredReason = unmeasuredReason;
|
|
892
897
|
}
|
|
@@ -88,6 +88,25 @@ export function extractGateRemoteFlags(argv: readonly string[]): GateRemoteFlags
|
|
|
88
88
|
return out;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
/** A remote importer sweep must not silently become one large test process. The measured planner may grow this
|
|
92
|
+
* starting count further when the observed RSS needs more memory-bounded bundles. */
|
|
93
|
+
export function remoteSelfGateArgv(input: {
|
|
94
|
+
changed?: boolean;
|
|
95
|
+
base?: string;
|
|
96
|
+
shards?: number;
|
|
97
|
+
fixedShardDistribution?: boolean;
|
|
98
|
+
includeImporters?: boolean;
|
|
99
|
+
artifactDir?: string;
|
|
100
|
+
}, sha: (ref: string) => string): string[] {
|
|
101
|
+
const shards = input.shards ?? (input.includeImporters ? 2 : undefined);
|
|
102
|
+
return ['bun', 'bin/elanous.mjs', 'self', 'gate', '--local', ...(input.changed ? ['--changed'] : []),
|
|
103
|
+
...(input.base ? ['--base', input.base === 'origin/main' ? input.base : sha(input.base)] : []),
|
|
104
|
+
...(shards !== undefined ? ['--shards', String(shards)] : []),
|
|
105
|
+
...(input.fixedShardDistribution ? ['--fixed-shard-distribution'] : []),
|
|
106
|
+
...(input.includeImporters ? ['--include-importers'] : []),
|
|
107
|
+
...(input.artifactDir ? ['--artifact-dir', input.artifactDir] : [])];
|
|
108
|
+
}
|
|
109
|
+
|
|
91
110
|
export interface GateDispatchDecision { mode: 'local' | 'remote'; host?: string; reason: string; load1: number }
|
|
92
111
|
|
|
93
112
|
export function decideGateDispatch(input: { flags: Pick<GateRemoteFlags, 'remote' | 'local'>; settings: GateRemoteSettings; load1: number; env?: NodeJS.ProcessEnv }): GateDispatchDecision {
|
|
@@ -249,7 +268,13 @@ export function remoteScript(input: { mirror: string; commit: string; fetchShas:
|
|
|
249
268
|
'umask 077',
|
|
250
269
|
'PATH="$HOME/.bun/bin:/opt/homebrew/bin:$PATH"; export PATH',
|
|
251
270
|
`infra() { echo "${INFRA}$1" >&2; exit 97; }`,
|
|
252
|
-
|
|
271
|
+
// `/tmp` is a 20 GiB tmpfs on WSL. One baseline checkout can exhaust it before any shard starts.
|
|
272
|
+
// Keep the entire child temp tree on the host disk; a low-space host yields infra fallback.
|
|
273
|
+
'TMPDIR=/var/tmp; export TMPDIR',
|
|
274
|
+
'free_kib=$(df -Pk "$TMPDIR" 2>/dev/null | awk \'NR==2 {print $4}\')',
|
|
275
|
+
'case "$free_kib" in ""|*[!0-9]*) infra disk-unmeasured;; esac',
|
|
276
|
+
'if [ "$free_kib" -lt 8388608 ]; then infra "disk-low:${free_kib}KiB"; fi',
|
|
277
|
+
'W=$(mktemp -d /var/tmp/elanous-gate-remote-XXXXXXXX) || infra mktemp',
|
|
253
278
|
'export W',
|
|
254
279
|
'trap \'rm -rf "$W"\' EXIT',
|
|
255
280
|
...payloadLines,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
|
-
import { existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, rmSync, rmdirSync, symlinkSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { dirname, join, resolve } from 'node:path';
|
|
6
6
|
import { debug } from '../debug/log.js';
|
|
@@ -26,9 +26,41 @@ export function needsPwaBuild(files: readonly string[], repoRoot: string, graph:
|
|
|
26
26
|
* land worktree (TA-LAND-WORKTREE).
|
|
27
27
|
*/
|
|
28
28
|
export function provideCheckoutDependencies(candidate: string, sourceRoot: string, dir: string, marker: string, install: (cwd: string) => void): void {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
const candidateModules = join(candidate, dir, 'node_modules');
|
|
30
|
+
const candidateMarker = join(candidateModules, marker, 'package.json');
|
|
31
|
+
const sourceModules = join(sourceRoot, dir, 'node_modules');
|
|
32
|
+
if (existsSync(candidateMarker)) return;
|
|
33
|
+
if (existsSync(join(sourceModules, marker, 'package.json'))) {
|
|
34
|
+
let stat: ReturnType<typeof lstatSync> | undefined;
|
|
35
|
+
try { stat = lstatSync(candidateModules); }
|
|
36
|
+
catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error; }
|
|
37
|
+
if (stat) {
|
|
38
|
+
if (stat.isSymbolicLink()) {
|
|
39
|
+
// A dangling or stale link can block symlinkSync with EEXIST; unlink only the link itself.
|
|
40
|
+
try { unlinkSync(candidateModules); }
|
|
41
|
+
catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error; }
|
|
42
|
+
} else if (stat.isDirectory() && readdirSync(candidateModules).length === 0) {
|
|
43
|
+
try { rmdirSync(candidateModules); }
|
|
44
|
+
catch (error) {
|
|
45
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOTEMPTY') {
|
|
46
|
+
install(join(candidate, dir));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
install(join(candidate, dir));
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
symlinkSync(sourceModules, candidateModules, 'dir');
|
|
59
|
+
} catch (error) {
|
|
60
|
+
// Concurrent host regates may provision the same checked-out worktree. The first link wins;
|
|
61
|
+
// accept EEXIST only after confirming its package marker is now visible.
|
|
62
|
+
if ((error as NodeJS.ErrnoException).code !== 'EEXIST' || !existsSync(candidateMarker)) throw error;
|
|
63
|
+
}
|
|
32
64
|
} else install(join(candidate, dir));
|
|
33
65
|
}
|
|
34
66
|
|
|
@@ -155,6 +155,22 @@ import { AUTO_REVIEW_LABEL, resolveAutoReviewLabels } from './context-capsule.js
|
|
|
155
155
|
import { GOAL_TYPES, extractVerbatimOriginalAsk, leadingGoalMetadata, parseAskFile, parseGoalType, type GoalType } from './goal-author.js';
|
|
156
156
|
import { goalDocumentForReleaseNote, goalChecklistIds, prTitle } from './pr-title.js';
|
|
157
157
|
export { goalChecklistIds, prTitle } from './pr-title.js';
|
|
158
|
+
|
|
159
|
+
/** TA's card slot is carried by the Pod, independent of goal-author prose. */
|
|
160
|
+
export function prChecklistLine(feature: string, goalFile?: string, slotId = process.env.ELANOUS_POD_SLOT_ID): string {
|
|
161
|
+
const slot = slotId?.trim();
|
|
162
|
+
if (slot && /^[A-Z][A-Z0-9]*(?:-[A-Z0-9]+)+$/.test(slot)) return `칸: ${slot}`;
|
|
163
|
+
const goalIds = goalChecklistIds(feature, goalFile);
|
|
164
|
+
return `칸: 미상\n골 ID: ${goalIds.join(' · ') || '없음'}`;
|
|
165
|
+
}
|
|
166
|
+
/** Preserve authored card lines as quoted prose; only the first line is machine readable. */
|
|
167
|
+
function onePrChecklistLine(body: string): string {
|
|
168
|
+
let first = true;
|
|
169
|
+
return body.replace(/^칸:[^\n]*$/gm, (line) => {
|
|
170
|
+
if (first) { first = false; return line; }
|
|
171
|
+
return `> ${line}`;
|
|
172
|
+
});
|
|
173
|
+
}
|
|
158
174
|
import { renderRoundSummaryCard, type RoundSummaryEntry } from './round-summary-card.js';
|
|
159
175
|
import { computeFlexSignals } from './flex-signals.js';
|
|
160
176
|
import { resolveAdaptiveMaxReworkDecision, resolveEscalateTier, resolveEscalateTarget, buildReworkFeature, failIndicator, isRepeatedUnverifiedOnlyGateFailure, unverifiedRepeatKey, appendReworkHistory, truncateReworkHistoryByItem, applyReworkBudgetDecision, parseContractConflictRelaxation, parseReworkBudgetDecision, resolveReworkBudgetCarry, stripReworkBudgetHeaders, countConsecutiveMustFixIds, mergeReworkNotes, resolveReworkKind, type ReworkPlanRevision, type ReworkNotePart } from './rework-policy.js';
|
|
@@ -2305,7 +2321,7 @@ export interface SelfImplementResult {
|
|
|
2305
2321
|
/** Additive hard-cap disposition; outcome remains the stable public terminal contract. */
|
|
2306
2322
|
salvage?: 'launched' | 'parked';
|
|
2307
2323
|
/** Rework stopped on exclusively baseline/flaky/precondition failures; do not retry this goal against the same base. */
|
|
2308
|
-
terminationReason?: 'blocked-by-baseline';
|
|
2324
|
+
terminationReason?: 'blocked-by-baseline' | 'goal-only-rework-exhausted';
|
|
2309
2325
|
sessionId?: string;
|
|
2310
2326
|
worktreePath?: string;
|
|
2311
2327
|
branch?: string;
|
|
@@ -2615,13 +2631,13 @@ export function gateNotRunSection(gateLog?: string): string[] {
|
|
|
2615
2631
|
return lines.length ? ['', '## Gate · 안 돈 소비자 시험(상한 초과)', ...lines.map((line) => `- ${line.replace(/^⚠️ caller test cap exceeded — not run: /, '')}`)] : [];
|
|
2616
2632
|
}
|
|
2617
2633
|
|
|
2618
|
-
function prBody(feature: string, implSummary: string, gateLog?: string, review?: SelfImplementReview, reviewIntent?: string, autoReviewDeclineReasons?: readonly string[], evidence?: string, goalFile?: string, planRevision?: ReworkPlanRevision, releaseNote?: ReturnType<typeof harnessReleaseNote>, revertGuard?: RevertGuardResult): string {
|
|
2634
|
+
export function prBody(feature: string, implSummary: string, gateLog?: string, review?: SelfImplementReview, reviewIntent?: string, autoReviewDeclineReasons?: readonly string[], evidence?: string, goalFile?: string, planRevision?: ReworkPlanRevision, releaseNote?: ReturnType<typeof harnessReleaseNote>, revertGuard?: RevertGuardResult): string {
|
|
2619
2635
|
const note = releaseNote ?? releaseNoteForRun(goalFile, feature);
|
|
2620
2636
|
const relaxation = planRevision?.relaxation
|
|
2621
2637
|
? ['', '## 감독 수용 기준 완화', `- 대상: ${planRevision.relaxation.target}`, `- 이전: ${planRevision.relaxation.expected}`, `- 완화: ${planRevision.relaxation.replacement}`, `- 이유: ${planRevision.reason}`, `- 적용: ${planRevision.application?.status ?? 'failed'}${planRevision.application?.detail ? ` (${planRevision.application.detail})` : ''}`, ...(planRevision.disposition ? [`- 충돌 처분: ${planRevision.disposition}`] : [])]
|
|
2622
2638
|
: [];
|
|
2623
|
-
return [
|
|
2624
|
-
|
|
2639
|
+
return onePrChecklistLine([
|
|
2640
|
+
prChecklistLine(feature, goalFile),
|
|
2625
2641
|
'## 요청',
|
|
2626
2642
|
...(goalFile ? [`- 골 파일: ${formatGoalFileForPrBody(goalFile)}`] : []),
|
|
2627
2643
|
feature.trim(),
|
|
@@ -2650,7 +2666,7 @@ function prBody(feature: string, implSummary: string, gateLog?: string, review?:
|
|
|
2650
2666
|
...relaxation,
|
|
2651
2667
|
'',
|
|
2652
2668
|
'🤖 self-implement 오케스트레이터 (elanous 자율 구현·리뷰-게이트 병합)',
|
|
2653
|
-
].join('\n');
|
|
2669
|
+
].join('\n'));
|
|
2654
2670
|
}
|
|
2655
2671
|
|
|
2656
2672
|
/**
|
|
@@ -2838,8 +2854,8 @@ export function formatBlockedDraftClassificationSection(
|
|
|
2838
2854
|
}
|
|
2839
2855
|
|
|
2840
2856
|
function blockedDraftPrBody(feature: string, implSummary: string, state: BlockedDraftPrState, goalFile?: string): string {
|
|
2841
|
-
return [
|
|
2842
|
-
|
|
2857
|
+
return onePrChecklistLine([
|
|
2858
|
+
prChecklistLine(feature, goalFile),
|
|
2843
2859
|
BLOCKED_DRAFT_PR_HUMAN_JUDGMENT_ANCHOR,
|
|
2844
2860
|
state.salvageStatusExpected
|
|
2845
2861
|
? BLOCKED_DRAFT_PR_SALVAGE_STATUS_INSTRUCTION
|
|
@@ -2886,8 +2902,10 @@ function blockedDraftPrBody(feature: string, implSummary: string, state: Blocked
|
|
|
2886
2902
|
: [`- passed: ${state.typecheck.passed}`, ...(state.typecheck.log ? ['```', state.typecheck.log.trim().slice(0, 3000), '```'] : [])]),
|
|
2887
2903
|
...(state.review?.mustFix.length ? ['', '## 마지막 리뷰 must-fix', ...state.review.mustFix.map((item) => `- ${item}`)] : []),
|
|
2888
2904
|
'',
|
|
2889
|
-
|
|
2890
|
-
|
|
2905
|
+
// The blocked body already carries the card line above; keep exactly one machine-readable line.
|
|
2906
|
+
prBody(feature, implSummary, undefined, state.review, undefined, undefined, undefined, goalFile, state.planRevision)
|
|
2907
|
+
.replace(/^칸: [^\n]*\n/, ''),
|
|
2908
|
+
].join('\n'));
|
|
2891
2909
|
}
|
|
2892
2910
|
|
|
2893
2911
|
type PreparedPrBody = {
|
|
@@ -5583,6 +5601,9 @@ async function runSelfImplementInner(
|
|
|
5583
5601
|
};
|
|
5584
5602
|
}
|
|
5585
5603
|
let round = 0;
|
|
5604
|
+
// A goal-only retry spends its one allowance for this run even if a later
|
|
5605
|
+
// gate/review round removes the target diff and reaches the same disposition.
|
|
5606
|
+
let goalOnlyReworkUsed = false;
|
|
5586
5607
|
// PR이 열리기 전에 프로세스가 죽으면 PR 자체가 없으므로 잃을 대화도 없다. 따라서 파일로 영속화하지 않는다.
|
|
5587
5608
|
const prCommentBuffer: string[] = [];
|
|
5588
5609
|
// GitHub PR comment bodies are bounded; retain the RFC header and disclose omitted source rather than losing the whole record.
|
|
@@ -6948,6 +6969,75 @@ async function runSelfImplementInner(
|
|
|
6948
6969
|
// ⭐ 성공 경로에서도 닫는다 — 이 호출은 끝났고, 이후 자식이 늦게 보내는 입력은
|
|
6949
6970
|
// 「이 라운드의 재투입」이 아니다(다음 라운드는 자기 콜백을 새로 만든다).
|
|
6950
6971
|
closeSupervisorInput();
|
|
6972
|
+
// A child that actually worked and declared completion may still leave only the
|
|
6973
|
+
// harness-seeded goal document. Give it one actionable implementation retry before
|
|
6974
|
+
// the blocked-artifact path reaches the goal-only PR guard.
|
|
6975
|
+
let goalOnlyReworkExhausted = false;
|
|
6976
|
+
if (!impl.ok && impl.completionDisposition === 'completed-without-changes'
|
|
6977
|
+
&& impl.terminalStatus?.reached === true && impl.terminalStatus.timedOut === false
|
|
6978
|
+
&& !goalOnlyReworkUsed && round < effectiveMax && !hasCurrentProviderError() && !hasCurrentCredentialFailure()
|
|
6979
|
+
&& opts.goalFile) {
|
|
6980
|
+
let targets: readonly string[] | undefined;
|
|
6981
|
+
let goalDocument: string | undefined;
|
|
6982
|
+
try {
|
|
6983
|
+
goalDocument = readFileSync(opts.goalFile, 'utf8');
|
|
6984
|
+
const declared = declaredPathsFromGoalDocumentText(goalDocument);
|
|
6985
|
+
if (declared.readable && declared.paths.length > 0) targets = declared.paths;
|
|
6986
|
+
} catch { /* unreadable scope is not grounds for an automatic retry */ }
|
|
6987
|
+
let before: readonly string[] | undefined;
|
|
6988
|
+
try { before = (s.changedFilesForGateRoute ?? changedFiles)(wt.path); } catch { /* unknown diff */ }
|
|
6989
|
+
if (targets && goalDocument && before !== undefined) {
|
|
6990
|
+
const eligible = (files: readonly string[]): string[] => files.filter(path =>
|
|
6991
|
+
!harnessSeededPaths.includes(path) && !isHarnessTempArtifact(path)
|
|
6992
|
+
&& targets!.some(target => changedFileIsInsideDeclaredTarget(path, target)));
|
|
6993
|
+
if (eligible(before).length === 0) {
|
|
6994
|
+
goalOnlyReworkUsed = true;
|
|
6995
|
+
const firstToolCalls = impl.toolCalls ?? null;
|
|
6996
|
+
const retryRound = round + 1;
|
|
6997
|
+
const retryNote = [
|
|
6998
|
+
'[구현 재작업 · completed-without-changes]',
|
|
6999
|
+
`대상 경로: ${targets.join(' · ')}`,
|
|
7000
|
+
'원래 골의 완료 경계를 위 대상 경로에 구현하라. 해당 파일에 실제 변경을 남기고 바꾼 경로와 검증 결과를 RESULT: 한 줄에 적어라.',
|
|
7001
|
+
'직전 자식은 완료를 선언했지만 대상 변경이 0개였다. 원래 골의 요구와 완료 경계를 다시 읽고 그 구현에 집중하라.',
|
|
7002
|
+
'원래 골 문서(완료 경계 포함):',
|
|
7003
|
+
goalDocument,
|
|
7004
|
+
].join('\n');
|
|
7005
|
+
observe('implementation-goal-only-rework', { status: 'started', round: retryRound, targets, firstToolCalls, targetDiffCount: 0 });
|
|
7006
|
+
progress('implementing', `대상 변경 0 — 구현 재작업 ${retryRound} 시작`);
|
|
7007
|
+
onNodeEntry(node('rework'), retryRound);
|
|
7008
|
+
onNodeEntry(node('implement'), retryRound);
|
|
7009
|
+
const retryAbort = new AbortController();
|
|
7010
|
+
const first = impl;
|
|
7011
|
+
try {
|
|
7012
|
+
impl = await stepTimeout(s.implement({
|
|
7013
|
+
sessionId, cwd: wt.path, feature: `${feature}\n\n${retryNote}`, runId, shardIdentity,
|
|
7014
|
+
signal: retryAbort.signal,
|
|
7015
|
+
...(harnessSeededPaths.length ? { harnessSeededPaths } : {}),
|
|
7016
|
+
onSurfaceProgress: line => progress('implementing', line.trimEnd()),
|
|
7017
|
+
roundContext: { round: retryRound, effectiveMax, previousRoundFailure: retryNote },
|
|
7018
|
+
...(opts.childLlm ? { childLlm: opts.childLlm } : {}),
|
|
7019
|
+
...(opts.documentReferences ? { documentReferences: opts.documentReferences } : {}),
|
|
7020
|
+
}), T.implement, 'implement');
|
|
7021
|
+
} catch (error) {
|
|
7022
|
+
retryAbort.abort();
|
|
7023
|
+
impl = { ok: false, summary: `goal-only-rework-exhausted: ${safeErrorDescription(error)}`, completionDisposition: first.completionDisposition };
|
|
7024
|
+
}
|
|
7025
|
+
let after: readonly string[] | undefined;
|
|
7026
|
+
try { after = (s.changedFilesForGateRoute ?? changedFiles)(wt.path); } catch { /* unknown diff */ }
|
|
7027
|
+
const targetDiff = after === undefined ? undefined : eligible(after);
|
|
7028
|
+
const resultLine = /(?:^|\n)\s*RESULT:\s*\S/m.test(impl.evidenceTranscript ?? impl.summary);
|
|
7029
|
+
const succeeded = impl.ok && !!targetDiff?.length && resultLine;
|
|
7030
|
+
goalOnlyReworkExhausted = !succeeded;
|
|
7031
|
+
observe('implementation-goal-only-rework', {
|
|
7032
|
+
status: succeeded ? 'succeeded' : 'exhausted', round: retryRound, targets,
|
|
7033
|
+
firstToolCalls, retryToolCalls: impl.toolCalls ?? null,
|
|
7034
|
+
targetDiffCount: targetDiff?.length ?? null, diffMeasured: after !== undefined, resultLine,
|
|
7035
|
+
}, succeeded ? {} : { level: 'warn' });
|
|
7036
|
+
if (!succeeded) impl = { ...impl, ok: false, summary: `goal-only-rework-exhausted: ${impl.summary}` };
|
|
7037
|
+
round = retryRound;
|
|
7038
|
+
}
|
|
7039
|
+
}
|
|
7040
|
+
}
|
|
6951
7041
|
// A finished child can leave edits without a commit. Measure them before any
|
|
6952
7042
|
// diff/no-changes classification, and commit only paths declared in the goal.
|
|
6953
7043
|
finishedChildCommitMissing = false;
|
|
@@ -7121,7 +7211,7 @@ async function runSelfImplementInner(
|
|
|
7121
7211
|
toolCalls: impl.toolCalls ?? null,
|
|
7122
7212
|
});
|
|
7123
7213
|
progress('aborted', progressLine);
|
|
7124
|
-
const pr = await preserveBlockedArtifacts({
|
|
7214
|
+
const pr = goalOnlyReworkExhausted ? undefined : await preserveBlockedArtifacts({
|
|
7125
7215
|
stage: 'aborted',
|
|
7126
7216
|
reason: abort.reason,
|
|
7127
7217
|
childSummary: abort.childSummary,
|
|
@@ -7132,7 +7222,7 @@ async function runSelfImplementInner(
|
|
|
7132
7222
|
salvageStatusExpected: false,
|
|
7133
7223
|
...(abort.terminalStatus ? { terminalStatus: abort.terminalStatus } : {}),
|
|
7134
7224
|
});
|
|
7135
|
-
return { ok: false, stage: 'aborted', node: 'implement', ...resolveRunOutcome({ termination: 'abandoned' }), sessionId, worktreePath: wt.path, branch: wt.branch, quotaExhaustionAssessment: quotaExhaustionAssessmentForRun(), ...(impl.completionDisposition ? { completionDisposition: impl.completionDisposition } : {}), ...(pr ? { prUrl: pr.url, prNumber: pr.number } : {}), detail: impl.summary };
|
|
7225
|
+
return { ok: false, stage: 'aborted', node: 'implement', ...resolveRunOutcome({ termination: 'abandoned' }), ...(goalOnlyReworkExhausted ? { terminationReason: 'goal-only-rework-exhausted' as const } : {}), sessionId, worktreePath: wt.path, branch: wt.branch, quotaExhaustionAssessment: quotaExhaustionAssessmentForRun(), ...(impl.completionDisposition ? { completionDisposition: impl.completionDisposition } : {}), ...(pr ? { prUrl: pr.url, prNumber: pr.number } : {}), detail: impl.summary };
|
|
7136
7226
|
}
|
|
7137
7227
|
|
|
7138
7228
|
if (finishedChildCommitMissing) {
|