elanous 0.2.25 → 0.2.28
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/{1454-37fd16f479f5554a.js → 1454-29d1d773d5996a49.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{599-a914cc0468f56143.js → 599-56f2c0a5cc4a7295.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-5ab1e47978577c5e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-24453f6a302816b4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-6c131d30bb1cf871.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-b12149c51f7d04b1.js → page-fa46d4588e8442c2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-dc0772b11861af51.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-66ad46ed0279feb9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-5159dc8752c6cd29.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-c5a2478861c08b87.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-f722a9dec084e08b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/decisions/{page-305cd294e754906e.js → page-256ffd365d32e100.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/{page-dea4b795741126fd.js → page-e0de78ae7b99f01c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-cfbdc44b708d9801.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-45889e7070d11126.js → page-4c741eb760f2f77c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-16a2e893f83ae4d0.js → page-e9ba98f5f1440a21.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-dafc901bb1b72b5f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-56888bc744e08dac.js → page-2f6f303fa114ec24.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-1634081c5272006c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/{page-952c6ceec6d9de38.js → page-ced0bcfc6542b3fc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live-run/{page-6d0202af568f4c63.js → page-bb1f05cd51c38944.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-646b50f63b6fe732.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-db069b48a59c8c00.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-4fb6fc34e20c07b7.js → page-f736324afa2117c1.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-e9502921f4f01b1f.js → page-6f131b51193a36f9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{not-found-b6169ea2f09cf287.js → not-found-435f5e2faf02bc01.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/{page-6a14333d1b1b3967.js → page-497e0eedf20e7890.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-8c91a9a66c9ec90a.js → page-a8d28fdf995e16ea.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-8966452228753c5e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fd5d18656cb432b6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-5663779cb1a37609.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/{page-3d3fc73dd187ed52.js → page-66a67eb19ebd976b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-3b492aa78cf3c4a6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-9c04dac0831c2bbb.js → page-c71267e7681da982.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-a21d1e1797172692.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-04de9ca6d52a3893.js → page-cb249e81ed339318.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-62527677bbc2fdc4.js → page-781cfc5114e097da.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-9a2e09c4a311764c.js → page-478157214db6a023.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/{page-bfeb35d69e8b24bb.js → page-86141f12cf090b7f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/share/{page-89cb33bced824e4a.js → page-7256c73bb1c36751.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-5c738ca7adef3de5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-0138c8091ec17224.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-cc2f24f16d85e735.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/{page-6445ac4cbb8dd869.js → page-979d318a534e92da.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-182d394f7e6e096b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-21050928a697ed7a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-44b9d807005a9030.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/{page-20162174b77f96cf.js → page-601fb77340e63c9a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-75d3fb5d47af0edf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/{page-05a4b2c9b91034aa.js → page-11bf9e5725a72c22.js} +1 -1
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +13 -13
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +13 -13
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +13 -13
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +13 -13
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +13 -13
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +13 -13
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +13 -13
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +13 -13
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +13 -13
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +13 -13
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +13 -13
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +13 -13
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +13 -13
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +13 -13
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +13 -13
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +13 -13
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +13 -13
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +13 -13
- package/apps/pwa/out/live-run/index.html +1 -1
- package/apps/pwa/out/live-run/index.txt +13 -13
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +13 -13
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +13 -13
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +13 -13
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +13 -13
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +13 -13
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +13 -13
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +13 -13
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +13 -13
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +13 -13
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +14 -14
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +13 -13
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +13 -13
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +13 -13
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +13 -13
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +13 -13
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +13 -13
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +13 -13
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +13 -13
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +13 -13
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +13 -13
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +13 -13
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +13 -13
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +13 -13
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +13 -13
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +13 -13
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +13 -13
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +13 -13
- package/graphs/implement-loop.yaml +38 -1
- package/graphs/steward/recipes.yaml +6 -0
- package/graphs/steward/release-day.yaml +23 -0
- package/graphs/steward/steward.yaml +9 -1
- package/graphs/work/code-pr.yaml +23 -0
- package/package.json +1 -1
- package/scripts/browser-observe-cycle.ts +76 -2
- package/scripts/ci-typecheck-changed.ts +119 -7
- package/scripts/coord-msg-bridge.ts +209 -0
- package/scripts/coord-post.sh +40 -7
- package/scripts/coord-requests-bridge.ts +10 -5
- package/scripts/detect-test-interference.ts +31 -1
- package/scripts/expose-rubric-suggest.ts +1 -1
- package/scripts/measure-tsc-affected-roots.ts +10 -1
- package/scripts/release-loop/docs-node.ts +51 -22
- package/scripts/release-loop/export-check-node.ts +8 -5
- package/scripts/release-loop/gate-node.ts +37 -4
- package/scripts/release-loop/node-verdict.ts +10 -3
- package/scripts/release-loop/publish-node.ts +3 -0
- package/scripts/release-loop/tui-node-checks.ts +5 -2
- package/scripts/skills/public-check.ts +5 -2
- package/scripts/test-deterministic.ts +520 -37
- package/scripts/ux-sim.ts +7 -1
- package/scripts/verify-kpack6-live.ts +230 -0
- package/src/acp/codex-plugins.ts +17 -0
- package/src/agent/task-notification.ts +32 -2
- package/src/agent-mission/in-agent-plugin.ts +8 -3
- package/src/agent-substrate/review-observation.ts +9 -2
- package/src/agent-substrate/self-review-cli.ts +62 -16
- package/src/boot/daemon-tools/self-implement.ts +72 -4
- package/src/briefing/brief-items.ts +151 -35
- package/src/budget/llm-cost.ts +30 -3
- package/src/budget/usage-outcomes.ts +32 -0
- package/src/claims/claims-ledger.ts +23 -0
- package/src/claims/claims-render.ts +22 -2
- package/src/cli/brief-cli.ts +131 -10
- package/src/cli/claims-cli.ts +24 -4
- package/src/cli/control-cli.ts +29 -0
- package/src/cli/doctor-cli.ts +90 -8
- package/src/cli/msg-cli.ts +177 -11
- package/src/cli/plugin-cli.ts +11 -3
- package/src/cli/pr-cli.ts +133 -18
- package/src/cli/pr-land-overlap-evidence.ts +18 -10
- package/src/cli/pr-land-refusal.ts +20 -0
- package/src/cli/release-cli.ts +34 -5
- package/src/cli/rfc-cli.ts +24 -0
- package/src/cli/seat-cli.ts +121 -0
- package/src/cli/self-utterances-cli.ts +140 -0
- package/src/cli/tailscale-serve.ts +5 -8
- package/src/cli/tasks-cli.ts +528 -31
- package/src/cli/usage-cli.ts +9 -4
- package/src/context-card/cell.ts +40 -0
- package/src/dashboard/boot-timing.ts +35 -0
- package/src/dashboard/first-screen-band.ts +63 -13
- package/src/dashboard/index.ts +94 -27
- package/src/dashboard/input/chat-main-plain-turn-runtime.ts +26 -0
- package/src/dashboard/skill-detection-runtime.ts +24 -4
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +6 -0
- package/src/debug/log.ts +1 -1
- package/src/domains/morning-report.ts +3 -1
- package/src/domains/release-tool.ts +29 -12
- package/src/domains/surface-events.ts +16 -0
- package/src/elanous-config-dir.ts +2 -2
- package/src/fleet/host-doctor.ts +73 -2
- package/src/github/pr-labels.ts +10 -4
- package/src/graph-runner/runner.ts +104 -15
- package/src/harness/harness-clean.ts +21 -3
- package/src/harness/harness-cli-command.ts +68 -10
- package/src/harness/harness-pod-dispatch.ts +11 -4
- package/src/harness/harness-stop.ts +11 -7
- package/src/hq/seats.ts +17 -3
- package/src/index.ts +69 -9
- package/src/instance/current.ts +2 -6
- package/src/instance/resolve.ts +5 -6
- package/src/intelligence-map/model-bench-scores.ts +102 -0
- package/src/llm/openai-sampling-fields.ts +89 -0
- package/src/llm/runtime-model-family.ts +61 -0
- package/src/llm/zai-coding-plan.ts +27 -1
- package/src/llm.ts +13 -89
- package/src/loops/orchestrator/inbox.ts +73 -0
- package/src/loops/orchestrator/resync-shadow.ts +100 -12
- package/src/loops/orchestrator/tick.ts +73 -2
- package/src/machines/machine-ledger.ts +31 -3
- package/src/maturity/cli-maturity.ts +13 -3
- package/src/maturity/feature-maturity.ts +1 -1
- package/src/msg/msg-sse-server.ts +141 -0
- package/src/msg/msg-store.ts +149 -17
- package/src/ops/overview-collect.ts +3 -2
- package/src/ops/overview-owner.ts +29 -0
- package/src/patents/ledger.ts +128 -0
- package/src/plugin-hooks/shell-hook.ts +17 -2
- package/src/plugin-missions/evaluator-runner.ts +18 -2
- package/src/plugins/install/market-fetch.ts +32 -1
- package/src/plugins/install/plugin-install.ts +11 -1
- package/src/pod-lease/admission.ts +116 -41
- package/src/prompt-enhance/enhance.ts +11 -0
- package/src/release-loop/checklist.ts +52 -4
- package/src/release-loop/feature-store.ts +10 -9
- package/src/release-loop/merged-pr-checklist.ts +24 -5
- package/src/release-loop/rubric.ts +70 -0
- package/src/rfc-registry/registry.ts +88 -0
- package/src/seat-dispatch/seat-request-ledger.ts +4 -0
- package/src/seat-loop/knowhow-lint.ts +233 -0
- package/src/seat-loop/seat-loop.ts +5 -1
- package/src/seat-loop/seat-pack.ts +16 -4
- package/src/seat-loop/traffic-signal.ts +7 -0
- package/src/self-dev/ask-launch-flow.ts +48 -8
- package/src/self-dev/ask-path-candidates.ts +69 -14
- package/src/self-dev/decompose-proposal.ts +43 -1
- package/src/self-dev/draft-sweep.ts +23 -14
- package/src/self-dev/graph-journey-nodes.ts +2 -0
- package/src/self-dev/launch-preflight-text.ts +5 -1
- package/src/self-dev/launch-preflight.ts +143 -10
- package/src/self-dev/orchestrate.ts +6 -2
- package/src/self-dev/run-supervisor.ts +35 -1
- package/src/self-implement/gate-cli.ts +6 -2
- package/src/self-implement/goal-author.ts +5 -28
- package/src/self-implement/goal-command-execution.ts +26 -0
- package/src/self-implement/goal-new-paths.ts +4 -5
- package/src/self-implement/graph-authority.ts +17 -3
- package/src/self-implement/graph-yaml.ts +15 -7
- package/src/self-implement/headless-elanous-driver.ts +16 -1
- package/src/self-implement/host-regate.ts +44 -9
- package/src/self-implement/orchestrator.ts +5 -11
- package/src/self-implement/pwa-import-graph.ts +194 -7
- package/src/self-implement/rework-policy.ts +80 -2
- package/src/self-implement/self-implement-runtime.ts +126 -21
- package/src/self-implement/speed-claims.ts +145 -6
- package/src/session/index.ts +14 -4
- package/src/session-runtime/index.ts +55 -11
- package/src/skills/tools/persistent-grounding.ts +59 -4
- package/src/skills/tools/shell.ts +79 -1
- package/src/steward/rejudge-tick.ts +12 -1
- package/src/steward/release-day.ts +158 -0
- package/src/steward/resync-tick.ts +123 -0
- package/src/task-agent/actions.ts +1 -0
- package/src/task-agent/adopt-pr.ts +140 -0
- package/src/task-agent/board.ts +11 -1
- package/src/task-agent/card-evidence.ts +37 -5
- package/src/task-agent/cover.ts +105 -19
- package/src/task-agent/failure-notify.ts +61 -0
- package/src/task-agent/green-sweep.ts +170 -0
- package/src/task-agent/land-worktree.ts +52 -5
- package/src/task-agent/live-move-history.ts +1 -0
- package/src/task-agent/live-moves.ts +56 -17
- package/src/task-agent/parent-host.ts +1 -1
- package/src/task-agent/postland-card.ts +139 -0
- package/src/task-agent/rejudge-on-head.ts +321 -25
- package/src/task-agent/remote-rejudge-bind.ts +62 -0
- package/src/task-agent/repair-branch-cas.ts +182 -0
- package/src/task-agent/repair-cycle.ts +275 -0
- package/src/task-agent/repair-next-move.ts +31 -0
- package/src/task-agent/repair-review.ts +24 -0
- package/src/task-agent/repair-worker.ts +87 -0
- package/src/task-agent/review-result-capture.ts +66 -2
- package/src/task-agent/shadow.ts +150 -29
- package/src/task-agent/task-hand.ts +133 -8
- package/src/task-orchestrator/surfaces/pod-repair-commit.ts +96 -0
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +71 -12
- package/src/telegram.ts +30 -1
- package/src/tool-runtime/tool-search-runtime.ts +25 -0
- package/src/tui.ts +20 -1
- package/src/user-config.ts +11 -60
- package/src/ux-sim/fault-sqlite-busy.ts +50 -0
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-9360b1285b2a346e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f08f6529d09ffae1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-f000d54d258f9ef6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-22f674e7e4c2c9d5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-8888733e40cea6d1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-9717ec6964b92276.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-44eaad486bc98a45.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-97fa6cf3d34c7eb9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-35e05d7450461a67.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-b2638013c0bf7dfd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-c44f93a41167536f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-1b810474507f3702.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-74366ae2693a7d95.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-4e634c5b98fd4e93.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-ed3070d637850548.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-b9cc8a10bc6078b4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-128458c8d86fbb5f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-cf0c2b0183991d09.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-b008aff96be56654.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-814394dcbae789b1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-6a87325c87f09dd2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-852170303c6cd755.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-087ceeb975ef272b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-97c7361cc2a19319.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-8c6901833f897496.js +0 -1
- /package/apps/pwa/out/_next/static/{dRS6lEhSRRBr1abbzp7fJ → y1ltt7oCZxVpC6zVPhZ2O}/_buildManifest.js +0 -0
- /package/apps/pwa/out/_next/static/{dRS6lEhSRRBr1abbzp7fJ → y1ltt7oCZxVpC6zVPhZ2O}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { debug } from '../debug/log.js';
|
|
2
|
+
import { listChecklist, setItemIfSnapshot, type Checklist } from '../release-loop/checklist.js';
|
|
3
|
+
import { judgeLandedCell, parseJudgeSignals, type GreenVerdict } from '../release-loop/landed-to-green.js';
|
|
4
|
+
import { cardPrNumber, readTaskAgentState, taskAgentStatePath, updateTaskCard, type TaskCard } from './task-hand.js';
|
|
5
|
+
|
|
6
|
+
export interface LandedTaskPr { number: number; url: string; headRefOid: string; state: string; baseRefName: string; mergedAt: string; files?: Array<{ path: string }> }
|
|
7
|
+
export interface PostlandDeps {
|
|
8
|
+
statePath?: string;
|
|
9
|
+
versions: readonly string[];
|
|
10
|
+
checklist?: (version: string) => Checklist;
|
|
11
|
+
setStatus?: typeof setItemIfSnapshot;
|
|
12
|
+
updateCard?: typeof updateTaskCard;
|
|
13
|
+
judge?: typeof judgeLandedCell;
|
|
14
|
+
log?: typeof debug.log;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Read-only PR binding for the legacy completion guard, including when judgment or ledger I/O fails. */
|
|
18
|
+
export function boundMergedTaskCells(pr: Pick<LandedTaskPr, 'number' | 'url'>, statePath = taskAgentStatePath()): string[] {
|
|
19
|
+
const tasks = readTaskAgentState<{ tasks?: Record<string, TaskCard> }>(statePath).tasks ?? {};
|
|
20
|
+
const cards = Object.values(tasks).filter((card) => card.taLand && card.checklistId && cardPrNumber(card) === pr.number
|
|
21
|
+
&& typeof card.pr === 'object' && card.pr.url?.replace(/\/+$/, '') === pr.url.replace(/\/+$/, ''));
|
|
22
|
+
return [...new Set(cards.map((card) => card.checklistId!))];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** When the PR API omits URL/head, keep candidate TA cells out of the prose-only green path. */
|
|
26
|
+
export function candidateMergedTaskCells(number: number, statePath = taskAgentStatePath()): string[] {
|
|
27
|
+
const tasks = readTaskAgentState<{ tasks?: Record<string, TaskCard> }>(statePath).tasks ?? {};
|
|
28
|
+
return [...new Set(Object.values(tasks).filter((card) => card.taLand && card.checklistId && cardPrNumber(card) === number).map((card) => card.checklistId!))];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** A bound TA card closes only after its exact PR and a unique release cell are confirmed. */
|
|
32
|
+
export function closeMergedTaskCard(pr: LandedTaskPr, cwd: string, deps: PostlandDeps): string | null {
|
|
33
|
+
const log = deps.log ?? debug.log.bind(debug);
|
|
34
|
+
if (pr.state !== 'MERGED' || pr.baseRefName !== 'main' || !/^[a-f0-9]{40}$/i.test(pr.headRefOid) || !pr.url) return null;
|
|
35
|
+
const path = deps.statePath ?? taskAgentStatePath();
|
|
36
|
+
const tasks = readTaskAgentState<{ tasks?: Record<string, TaskCard> }>(path).tasks ?? {};
|
|
37
|
+
const cards = Object.values(tasks).filter((card) => card.taLand && card.checklistId && cardPrNumber(card) === pr.number
|
|
38
|
+
&& typeof card.pr === 'object' && card.pr.url?.replace(/\/+$/, '') === pr.url.replace(/\/+$/, ''));
|
|
39
|
+
if (cards.length !== 1) {
|
|
40
|
+
log('task-agent', 'postland-card-skipped', { pr: pr.number, reason: cards.length ? 'ambiguous-card' : 'no-bound-card' });
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const card = cards[0]!;
|
|
44
|
+
if (!(card.landAttempts ?? []).some((attempt) => attempt.pr === pr.number && attempt.head.toLowerCase() === pr.headRefOid.toLowerCase())) {
|
|
45
|
+
log('task-agent', 'postland-card-skipped', { pr: pr.number, card: card.id, reason: 'land-head-not-bound' });
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
const candidateVersions = card.checklistVersion ? [card.checklistVersion] : deps.versions;
|
|
49
|
+
const cells = candidateVersions.flatMap((version) => {
|
|
50
|
+
const snapshot = (deps.checklist ?? listChecklist)(version);
|
|
51
|
+
const item = snapshot.items.find((row) => row.id === card.checklistId);
|
|
52
|
+
return item ? [{ version, snapshot, item }] : [];
|
|
53
|
+
});
|
|
54
|
+
if (cells.length !== 1) {
|
|
55
|
+
log('task-agent', 'postland-card-skipped', { pr: pr.number, card: card.id, reason: cells.length ? 'ambiguous-cell-version' : 'cell-not-found' });
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const { version, item } = cells[0]!;
|
|
59
|
+
const expected = { title: item.title, status: item.status, evidence: item.evidence };
|
|
60
|
+
const expectedTitle = expected.title;
|
|
61
|
+
const prior = (card as TaskCard & { postLand?: { pr: number; head: string; outcome: 'complete' | 'partial'; evidence: string[] } }).postLand;
|
|
62
|
+
const marker = `TA postland #${pr.number}/${pr.headRefOid.slice(0, 12)}:`;
|
|
63
|
+
const currentEvidenceLine = item.evidence?.split('\n').find((line) => line.startsWith(marker));
|
|
64
|
+
const terminalBound = card.status === 'done' && card.prTerminal?.pr === pr.number
|
|
65
|
+
&& card.prTerminal.head.toLowerCase() === pr.headRefOid.toLowerCase()
|
|
66
|
+
&& card.prTerminal.url.replace(/\/+$/, '') === pr.url.replace(/\/+$/, '');
|
|
67
|
+
if (prior?.pr === pr.number && prior.head.toLowerCase() === pr.headRefOid.toLowerCase()
|
|
68
|
+
&& prior.outcome === 'complete' && terminalBound && item.status === 'green' && currentEvidenceLine
|
|
69
|
+
&& prior.evidence?.length && prior.evidence.every((part) => currentEvidenceLine.includes(part))) return item.id;
|
|
70
|
+
const alreadyGreen = item.status === 'green';
|
|
71
|
+
let verdict: GreenVerdict | undefined = alreadyGreen ? 'proposed' : undefined;
|
|
72
|
+
let judgeError: string | undefined;
|
|
73
|
+
if (!alreadyGreen) {
|
|
74
|
+
try { verdict = (deps.judge ?? judgeLandedCell)({ version, id: item.id, title: expectedTitle, pr: pr.number }, { cwd }); }
|
|
75
|
+
catch (error) { judgeError = String(error).slice(0, 240); }
|
|
76
|
+
}
|
|
77
|
+
const complete = verdict === 'proposed';
|
|
78
|
+
const criterion = expectedTitle.split(/\r?\n/).filter((line) => /^\s*판정(?: 신호)?:/.test(line)).map((line) => line.trim());
|
|
79
|
+
const remaining = complete ? [] : criterion.length ? criterion : ['판정 줄 없음 — 칸 주인의 판정 필요'];
|
|
80
|
+
const allPaths = (pr.files ?? []).map((file) => file.path).filter(Boolean);
|
|
81
|
+
const paths = allPaths.slice(0, 20);
|
|
82
|
+
const omitted = allPaths.length - paths.length;
|
|
83
|
+
const observations = parseJudgeSignals(expectedTitle).map((signal) => signal.observe).filter((value): value is string => !!value);
|
|
84
|
+
const judgeEvidence = alreadyGreen ? '기존 green 상태 확인(새 측정 0)' : judgeError
|
|
85
|
+
? `판정 실행 오류: ${judgeError}`
|
|
86
|
+
: `판정 ${verdict ?? 'error'}: ${observations.length ? observations.join(' · ') : '측정 명령 없음'}${verdict === 'proposed' ? ' → exit 0' : ''}`;
|
|
87
|
+
const changed = paths.length ? `changed: ${paths.join(' · ')}${omitted ? ` · 외 ${omitted}개(${pr.url}에서 전체 확인)` : ''}`
|
|
88
|
+
: `changed: 경로 목록 미제공(${pr.url} 참조)`;
|
|
89
|
+
const filled = [`PR #${pr.number} merged, head ${pr.headRefOid}`, `카드 land head 대조 일치`, changed, judgeEvidence];
|
|
90
|
+
const note = complete
|
|
91
|
+
? `${marker} ${filled.join(' | ')}; ${alreadyGreen ? '기존 green 유지' : '판정 신호 통과 → green'}.`
|
|
92
|
+
: `${marker} 이 조각이 채운 것: ${filled.join(' | ')}; 남은 것: ${remaining.join(' | ')}; 다음 조각 retitle 후보: ${remaining[0]}`;
|
|
93
|
+
const priorLines = item.evidence?.split('\n').filter((line) => !line.startsWith(marker)) ?? [];
|
|
94
|
+
const evidence = [...priorLines, note].join('\n');
|
|
95
|
+
const desiredStatus = complete ? 'green' : item.status === 'red' ? 'red' : 'yellow';
|
|
96
|
+
if (prior?.pr === pr.number && prior.head.toLowerCase() === pr.headRefOid.toLowerCase()
|
|
97
|
+
&& prior.outcome === (complete ? 'complete' : 'partial') && terminalBound
|
|
98
|
+
&& item.status === desiredStatus && item.evidence === evidence) return item.id;
|
|
99
|
+
const at = new Date().toISOString();
|
|
100
|
+
let wrote = false;
|
|
101
|
+
// The card lock protects the binding through the ledger write. A failed ledger write
|
|
102
|
+
// throws before the card changes, so the next pass can retry both records.
|
|
103
|
+
(deps.updateCard ?? updateTaskCard)(path, card.id, (current, currentTasks) => {
|
|
104
|
+
const matching = Object.values(currentTasks).filter((candidate) => candidate.taLand && cardPrNumber(candidate) === pr.number
|
|
105
|
+
&& typeof candidate.pr === 'object' && candidate.pr.url?.replace(/\/+$/, '') === pr.url.replace(/\/+$/, ''));
|
|
106
|
+
if (matching.length !== 1 || matching[0]?.id !== card.id) return undefined;
|
|
107
|
+
if (!current || cardPrNumber(current) !== pr.number || current.checklistId !== item.id
|
|
108
|
+
|| current.checklistVersion !== card.checklistVersion || typeof current.pr !== 'object'
|
|
109
|
+
|| current.pr.url?.replace(/\/+$/, '') !== pr.url.replace(/\/+$/, '')
|
|
110
|
+
|| !(current.landAttempts ?? []).some((attempt) => attempt.pr === pr.number && attempt.head.toLowerCase() === pr.headRefOid.toLowerCase())) return undefined;
|
|
111
|
+
const existing = (current as TaskCard & { postLand?: { pr: number; head: string; outcome: 'complete' | 'partial' } }).postLand;
|
|
112
|
+
const currentTerminalBound = current.status === 'done' && current.prTerminal?.pr === pr.number
|
|
113
|
+
&& current.prTerminal.head.toLowerCase() === pr.headRefOid.toLowerCase()
|
|
114
|
+
&& current.prTerminal.url.replace(/\/+$/, '') === pr.url.replace(/\/+$/, '');
|
|
115
|
+
if (existing?.pr === pr.number && existing.head.toLowerCase() === pr.headRefOid.toLowerCase()
|
|
116
|
+
&& existing.outcome === (complete ? 'complete' : 'partial') && currentTerminalBound
|
|
117
|
+
&& item.status === desiredStatus && item.evidence === evidence) return undefined;
|
|
118
|
+
const currentVersions = current.checklistVersion ? [current.checklistVersion] : deps.versions;
|
|
119
|
+
const currentCells = currentVersions.flatMap((candidateVersion) =>
|
|
120
|
+
(deps.checklist ?? listChecklist)(candidateVersion).items.some((row) => row.id === item.id) ? [candidateVersion] : []);
|
|
121
|
+
if (currentCells.length !== 1 || currentCells[0] !== version) return undefined;
|
|
122
|
+
(deps.setStatus ?? setItemIfSnapshot)(version, item.id, { evidence, status: desiredStatus }, 'task-agent', expected);
|
|
123
|
+
wrote = true;
|
|
124
|
+
const sameOutcome = existing?.pr === pr.number && existing.head.toLowerCase() === pr.headRefOid.toLowerCase()
|
|
125
|
+
&& existing.outcome === (complete ? 'complete' : 'partial');
|
|
126
|
+
return { ...current,
|
|
127
|
+
status: 'done' as const,
|
|
128
|
+
prTerminal: { pr: pr.number, url: pr.url, head: pr.headRefOid, state: 'MERGED' as const, at },
|
|
129
|
+
postLand: { pr: pr.number, head: pr.headRefOid, at, outcome: complete ? 'complete' as const : 'partial' as const, evidence: filled, remaining },
|
|
130
|
+
history: sameOutcome ? current.history : [...current.history, { at, event: 'postland-card', pr: pr.number, head: pr.headRefOid, detail: complete ? `green ${version}/${item.id}` : `${desiredStatus} ${version}/${item.id}; ${remaining.join(' | ')}` }],
|
|
131
|
+
};
|
|
132
|
+
});
|
|
133
|
+
if (!wrote) {
|
|
134
|
+
log('task-agent', 'postland-card-pending', { pr: pr.number, card: card.id, version, id: item.id, reason: 'card-binding-changed-before-write' });
|
|
135
|
+
return item.id;
|
|
136
|
+
}
|
|
137
|
+
log('task-agent', 'postland-card-closed', { pr: pr.number, card: card.id, version, id: item.id, outcome: complete ? 'complete' : 'partial', verdict });
|
|
138
|
+
return item.id;
|
|
139
|
+
}
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* fetch 한 뒤 PR 가지에 3-way merge(rerere 켬 · ⛔ rebase/force-push/reset/파일째 checkout) 하고 일반 push 로 새 머리를 만든 다음, 그 머리는 기존
|
|
24
24
|
* ①②③ 사슬을 탄다. 머리마다 1회 · 카드당 `CATCH_UP_CAP`(기본 2)회. 충돌이면 `merge --abort` ⊕ needs-owner(`main 따라잡기 충돌 · 파일 …`).
|
|
25
25
|
* 관측 `rejudge-on-head {step:'catch-up', decision: pushed|conflict|cap-reached, fromBase, toBase, newHead}`.
|
|
26
|
-
* - 대상: 카드에 묶인 PR(URL 로 저장소 확인) ⊕
|
|
26
|
+
* - 대상: 카드에 묶인 PR(URL 로 저장소 확인) ⊕ 기존 TA 리뷰 또는 원격 부모→자식 간선·같은 PR·자식 run-status 종결 증거.
|
|
27
27
|
* - 사람의 hold(draft PR)는 뒤집지 않는다 — 재게이트(`noMerge`)와 land 관문이 draft 를 거부한다.
|
|
28
28
|
* - 허용: 설정 `taskAgent.liveMoves` 그대로 — 재게이트·land 는 `propose-land`, 리뷰 재요청은 `review` 가 있을 때만(없으면 «했을 수»).
|
|
29
29
|
* - 관측: `task-agent` · `rejudge-on-head {card, pr, fromHead, toHead, step, decision, reason}` — 수가 난 판단은 카드 history 에도
|
|
@@ -34,12 +34,18 @@ import { spawn, spawnSync } from 'node:child_process';
|
|
|
34
34
|
import { tmpdir } from 'node:os';
|
|
35
35
|
import { dirname, join } from 'node:path';
|
|
36
36
|
import { debug } from '../debug/log.js';
|
|
37
|
+
import { prodInstanceRoot } from '../instance/resolve.js';
|
|
38
|
+
import { loadRunLedger, type RunLedgerEntry } from '../self-implement/run-ledger.js';
|
|
37
39
|
import type { DetachedSpawn } from '../cli/tasks-cli.js';
|
|
40
|
+
import { remoteLedgerMirrorDir } from './parent-host.js';
|
|
38
41
|
import { appendLandOrderRow, joinLandOrderRow } from './land-order-ledger.js';
|
|
39
42
|
import { recordLiveMoveHistory } from './live-move-history.js';
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
42
|
-
import {
|
|
43
|
+
import { runRepairCycle, type RepairLaunchRequest } from './repair-cycle.js';
|
|
44
|
+
import { repairMustFixFromReview, repairReviewDigest } from './repair-review.js';
|
|
45
|
+
import type { RepairCasOutcome, RepairCasRequest } from './repair-branch-cas.js';
|
|
46
|
+
import { defaultLandPrHead, defaultPrHead, defaultReviewRepoCandidates, executeLiveLand, executeLiveReview, landRetryPending, type LiveMoveDeps, type LiveMoveResult, type PrHeadView, type ReviewRepoCandidate, type TaskAgentLiveMove } from './live-moves.js';
|
|
47
|
+
import { capturedLandReview, captureReviewResults, parseReviewResultOutput, pendingReviewResults, reviewResultPath, type ParsedReviewResult } from './review-result-capture.js';
|
|
48
|
+
import { boundPrTerminal, cardPrNumber, LIVE_MOVE_RESULT_EVENT, readTaskCard, taskAgentStatePath, updateTaskCard, type TaskCard } from './task-hand.js';
|
|
43
49
|
|
|
44
50
|
export const REJUDGE_ON_HEAD_EVENT = 'rejudge-on-head';
|
|
45
51
|
/** 카드·PR 당 재게이트하는 머리 수 상한(머리가 바뀔 때마다 하나) — 넘으면 멈추고 사람에게 넘긴다. */
|
|
@@ -49,7 +55,7 @@ export const CATCH_UP_CAP = 2;
|
|
|
49
55
|
/** 떼어 띄운 재게이트 결과를 기다리는 상한 — 지나도 파일이 없으면 «결과 없음»으로 적는다(같은 머리는 다시 안 돌린다). */
|
|
50
56
|
export const REGATE_RESULT_WAIT_MS = 90 * 60_000;
|
|
51
57
|
|
|
52
|
-
export type RejudgeStep = 'regate' | 'review' | 'land' | 'target' | 'catch-up';
|
|
58
|
+
export type RejudgeStep = 'regate' | 'review' | 'ready' | 'land' | 'target' | 'catch-up' | 'repair';
|
|
53
59
|
export type CatchUpDecision = 'pushed' | 'conflict' | 'cap-reached' | 'failed';
|
|
54
60
|
export interface RejudgeOutcome {
|
|
55
61
|
card: string; pr: number | null; fromHead: string | null; toHead: string | null; step: RejudgeStep; decision: string; reason: string;
|
|
@@ -71,8 +77,18 @@ export type CatchUpGit = (cwd: string, args: string[]) => { status: number | nul
|
|
|
71
77
|
export interface RejudgeDeps {
|
|
72
78
|
live?: LiveMoveDeps;
|
|
73
79
|
liveMoves: ReadonlySet<TaskAgentLiveMove>;
|
|
80
|
+
/** Only phase ① shadow callbacks may inspect a previously judged head without a regate. */
|
|
81
|
+
shadowSameHead?: boolean;
|
|
82
|
+
/** HQ mirror proof that the bound remote child has stopped. Test seam; absence fails closed. */
|
|
83
|
+
remoteRunTerminal?: (card: TaskCard) => boolean;
|
|
84
|
+
/** Make a reviewed draft PR ready; tests inject this instead of calling gh. */
|
|
85
|
+
readyPr?: (pr: number, cwd: string) => Promise<{ ok: boolean; reason: string }>;
|
|
74
86
|
/** 재게이트를 떼어 띄운다 — 시험은 주입한다. 기본은 `tasks regate-run` 자식(stdio ignore · 결과는 파일). */
|
|
75
87
|
requestRegate?: (request: RegateRequest) => Promise<void>;
|
|
88
|
+
/** Source PR/head repair worker; absent uses the detached HQ local parent. */
|
|
89
|
+
requestRepair?: (request: RepairLaunchRequest) => Promise<void>;
|
|
90
|
+
applyRepair?: (request: RepairCasRequest) => RepairCasOutcome;
|
|
91
|
+
notifyRepair?: (seat: string, body: string) => void;
|
|
76
92
|
/** TA-REJUDGE-REBASE-LOOP — origin/main 을 PR 가지에 3-way merge 하고 일반 push. 시험은 주입한다. */
|
|
77
93
|
catchUpMain?: (request: CatchUpRequest) => Promise<CatchUpResult>;
|
|
78
94
|
log?: (category: string, event: string, data: Record<string, unknown>) => void;
|
|
@@ -80,12 +96,149 @@ export interface RejudgeDeps {
|
|
|
80
96
|
readFile?: (path: string) => string;
|
|
81
97
|
}
|
|
82
98
|
|
|
99
|
+
/** PR binding alone does not mean a remote parent has stopped. Require its child edge and terminal ledger row. */
|
|
100
|
+
export function remoteBoundRunTerminal(card: TaskCard, load: (runId: string, dir: string) => RunLedgerEntry[] | null = loadRunLedger): boolean {
|
|
101
|
+
const pr = cardPrNumber(card);
|
|
102
|
+
const url = typeof card.pr === 'object' ? card.pr.url : undefined;
|
|
103
|
+
if (card.status !== 'launched' || card.taLand !== true || !card.parentHost || !card.runId || !card.runChildId || !pr || !url) return false;
|
|
104
|
+
try {
|
|
105
|
+
const dir = remoteLedgerMirrorDir(prodInstanceRoot(), card.parentHost.host);
|
|
106
|
+
const parent = load(card.runId, dir);
|
|
107
|
+
if (!parent?.some((entry) => entry.event === 'pod-child-run' && entry.data?.childRunId === card.runChildId)) return false;
|
|
108
|
+
const child = load(card.runChildId, dir);
|
|
109
|
+
return !!child?.some((entry) => entry.event === 'pr-opened' && entry.data?.number === pr && entry.data.url === url)
|
|
110
|
+
&& child.some((entry) => entry.event === 'run-status' && ['completed', 'failed', 'cancelled'].includes(String(entry.data?.runStatus)));
|
|
111
|
+
} catch { return false; }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Shared completion callback for detached review and regate workers. */
|
|
115
|
+
type CompletedWorker = { pr: number; head: string; kind?: 'review' | 'regate'; resultPath?: string };
|
|
116
|
+
|
|
117
|
+
export async function rejudgeAsyncResultCard(
|
|
118
|
+
cardId: string, statePath: string,
|
|
119
|
+
external: Pick<LiveMoveDeps, 'repoCandidates' | 'prHead' | 'landPrHead' | 'land' | 'landWorktree' | 'requestReview'>
|
|
120
|
+
& Pick<RejudgeDeps, 'requestRegate'> = {},
|
|
121
|
+
completed?: CompletedWorker,
|
|
122
|
+
): Promise<RejudgeOutcome[]> {
|
|
123
|
+
const { requestRegate, ...live } = external;
|
|
124
|
+
return rejudgeShadowCard(cardId, statePath, { live, requestRegate }, completed);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export async function rejudgeShadowCard(cardId: string, statePath: string, deps: Partial<RejudgeDeps> = {}, completed?: CompletedWorker): Promise<RejudgeOutcome[]> {
|
|
128
|
+
// TA-LAND-AFTER-REVIEW phase ①: completion paths observe the decision but execute no
|
|
129
|
+
// regate/review/land move. A post-landing canary will gate the separate live switch.
|
|
130
|
+
const outcomes = await rejudgeCardOnHead(cardId, { ...deps, liveMoves: new Set(), shadowSameHead: true, live: { ...deps.live, statePath } });
|
|
131
|
+
requireSuccessfulRejudge(outcomes);
|
|
132
|
+
if (completed) requireCompletedHeadRejudge(outcomes, completed.pr, completed.head);
|
|
133
|
+
const last = outcomes.at(-1);
|
|
134
|
+
if (last) {
|
|
135
|
+
const decision = last.decision === 'would-land' ? 'land' : last.decision;
|
|
136
|
+
let claimed = false;
|
|
137
|
+
try {
|
|
138
|
+
updateTaskCard(statePath, cardId, (current) => {
|
|
139
|
+
if (!current || current.status !== 'launched' || current.taLand !== true || last.pr === null
|
|
140
|
+
|| cardPrNumber(current) !== last.pr || !last.toHead || !/^[0-9a-f]{40}$/i.test(last.toHead)
|
|
141
|
+
|| (completed?.kind && (!completed.resultPath || !(completed.kind === 'review' ? current.reviewRequests : current.regates)
|
|
142
|
+
?.some((entry) => entry.pr === completed.pr && sameHead(entry.head, completed.head) && entry.resultPath === completed.resultPath)))
|
|
143
|
+
|| (decision === 'land' && (!capturedLandReview(current, last.pr, last.toHead)
|
|
144
|
+
|| !(current.regates ?? []).some((entry) => entry.pr === last.pr && sameHead(entry.head, last.toHead!) && entry.passed === true && entry.resultPath)))
|
|
145
|
+
|| (current.history ?? []).some((item) => item.event === 'rejudge-shadow' && item.pr === last.pr && item.head === last.toHead && item.effect === decision)) return undefined;
|
|
146
|
+
claimed = true;
|
|
147
|
+
return { ...current, history: [...(current.history ?? []), { at: new Date().toISOString(), event: 'rejudge-shadow', pr: last.pr, head: last.toHead ?? undefined, effect: decision, detail: last.reason }] };
|
|
148
|
+
});
|
|
149
|
+
if (claimed) debug.log('task-agent', 'rejudge-shadow', {
|
|
150
|
+
card: cardId, head12: last.toHead?.slice(0, 12) ?? null, decision, reason: last.reason,
|
|
151
|
+
});
|
|
152
|
+
} catch { /* observation must not change completion */ }
|
|
153
|
+
}
|
|
154
|
+
return outcomes;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** A completed callback must not claim success when the rejudge returned a failed move. */
|
|
158
|
+
export function requireSuccessfulRejudge(result: unknown): void {
|
|
159
|
+
if (result == null || (Array.isArray(result) && result.length === 0)) throw new Error('task-agent rejudge returned no decision');
|
|
160
|
+
if (result === false || (result && typeof result === 'object' && !Array.isArray(result)
|
|
161
|
+
&& ('ok' in result && result.ok === false || 'success' in result && result.success === false))) {
|
|
162
|
+
throw new Error('task-agent rejudge returned failure');
|
|
163
|
+
}
|
|
164
|
+
if (!Array.isArray(result)) throw new Error('task-agent rejudge returned no decision array');
|
|
165
|
+
const failed = result.find((item) => {
|
|
166
|
+
if (item && typeof item === 'object' && 'ok' in item && item.ok === false) return true;
|
|
167
|
+
const decision = item && typeof item === 'object' && 'decision' in item ? item.decision : undefined;
|
|
168
|
+
return typeof decision === 'string' && (decision.endsWith('-failed') || decision.endsWith('-rejected')
|
|
169
|
+
|| ['no-pr', 'card-unreadable', 'conflict', 'cap-reached'].includes(decision));
|
|
170
|
+
});
|
|
171
|
+
if (failed) throw new Error(`task-agent rejudge returned failure: ${String(failed.decision ?? 'ok:false')}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** A completion marker belongs to the exact PR head whose worker result triggered it. */
|
|
175
|
+
export function requireCompletedHeadRejudge(result: unknown, pr: number, head: string): void {
|
|
176
|
+
requireSuccessfulRejudge(result);
|
|
177
|
+
const last = Array.isArray(result) ? result.at(-1) : undefined;
|
|
178
|
+
if (!last || typeof last !== 'object' || last.pr !== pr || typeof last.toHead !== 'string'
|
|
179
|
+
|| !/^[0-9a-f]{40}$/i.test(last.toHead) || !sameHead(last.toHead, head)) {
|
|
180
|
+
throw new Error('task-agent rejudge result head does not match completed worker head');
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const regateRejudgeInFlight = new Set<string>();
|
|
185
|
+
|
|
186
|
+
/** Regate worker has written the exact result file; claim one callback for its card/head/result. */
|
|
187
|
+
export async function rejudgeCompletedTaskRegate(
|
|
188
|
+
cardId: string, pr: number, head: string, resultPath: string, statePath: string,
|
|
189
|
+
rejudge: (cardId: string, statePath: string) => Promise<unknown> = rejudgeAsyncResultCard,
|
|
190
|
+
rejudgeDeps?: Partial<RejudgeDeps>,
|
|
191
|
+
): Promise<boolean> {
|
|
192
|
+
if (!/^[0-9a-f]{40}$/i.test(head)) return false;
|
|
193
|
+
const key = `${statePath}\0${cardId}\0${pr}\0${head}\0${resultPath}`;
|
|
194
|
+
if (regateRejudgeInFlight.has(key)) return false;
|
|
195
|
+
const card = readTaskCard(cardId, statePath);
|
|
196
|
+
if (card?.status !== 'launched' || card.taLand !== true || cardPrNumber(card) !== pr
|
|
197
|
+
|| !(card.regates ?? []).some((entry) => entry.pr === pr && sameHead(entry.head, head) && entry.resultPath === resultPath && entry.passed === undefined)
|
|
198
|
+
|| (card.history ?? []).some((entry) => entry.event === 'async-result-rejudge' && entry.pr === pr && sameHead(entry.head ?? '', head) && entry.resultPath === resultPath)) return false;
|
|
199
|
+
let text: string;
|
|
200
|
+
try { text = readFileSync(resultPath, 'utf8'); } catch { return false; }
|
|
201
|
+
const parsed = parseRegateResult(text, pr, head);
|
|
202
|
+
if (!parsed) return false;
|
|
203
|
+
regateRejudgeInFlight.add(key);
|
|
204
|
+
try {
|
|
205
|
+
const run = () => rejudgeDeps
|
|
206
|
+
? rejudgeShadowCard(cardId, statePath, rejudgeDeps, { pr, head, kind: 'regate', resultPath })
|
|
207
|
+
: rejudge === rejudgeAsyncResultCard ? rejudgeAsyncResultCard(cardId, statePath, {}, { pr, head, kind: 'regate', resultPath }) : rejudge(cardId, statePath);
|
|
208
|
+
try { requireCompletedHeadRejudge(await run(), pr, head); } catch { requireCompletedHeadRejudge(await run(), pr, head); } // Bounded retry; no marker on failed outcomes.
|
|
209
|
+
updateTaskCard(statePath, cardId, (current) => parsed.passed === true && current?.status === 'launched' && current.taLand === true && cardPrNumber(current) === pr
|
|
210
|
+
&& (current.regates ?? []).some((entry) => entry.pr === pr && sameHead(entry.head, head) && entry.resultPath === resultPath
|
|
211
|
+
&& (entry.passed === undefined || entry.passed === parsed.passed))
|
|
212
|
+
&& !(current.history ?? []).some((entry) => entry.event === 'async-result-rejudge' && entry.pr === pr && sameHead(entry.head ?? '', head) && entry.resultPath === resultPath)
|
|
213
|
+
? { ...current, history: [...(current.history ?? []), { at: new Date().toISOString(), event: 'async-result-rejudge', kind: 'regate', pr, head, resultPath, effect: 'completion callback succeeded' }] }
|
|
214
|
+
: undefined);
|
|
215
|
+
} finally {
|
|
216
|
+
regateRejudgeInFlight.delete(key);
|
|
217
|
+
}
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
|
|
83
221
|
const safe = (value: string) => value.replace(/[^A-Za-z0-9._-]/g, '_');
|
|
84
222
|
export function regateResultPath(statePath: string, cardId: string, pr: number, head: string, staleBase?: string): string {
|
|
85
223
|
const suffix = staleBase ? `-from-${safe(staleBase.slice(0, 12))}` : '';
|
|
86
224
|
return join(dirname(statePath), 'regate-results', `${safe(cardId)}-${pr}-${safe(head.slice(0, 12))}${suffix}.json`);
|
|
87
225
|
}
|
|
88
226
|
|
|
227
|
+
function readyRegateResultPath(statePath: string, cardId: string, pr: number, head: string): string {
|
|
228
|
+
return regateResultPath(statePath, cardId, pr, head).replace(/\.json$/, '-after-ready.json');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export async function defaultReadyPr(pr: number, cwd: string): Promise<{ ok: boolean; reason: string }> {
|
|
232
|
+
const run = (env: NodeJS.ProcessEnv) => spawnSync('gh', ['pr', 'ready', String(pr)], { cwd, env, encoding: 'utf8', timeout: 30_000 });
|
|
233
|
+
let result = run(process.env);
|
|
234
|
+
if (result.status !== 0) {
|
|
235
|
+
const noProxy = { ...process.env };
|
|
236
|
+
for (const key of ['HTTPS_PROXY', 'https_proxy', 'HTTP_PROXY', 'http_proxy']) delete noProxy[key];
|
|
237
|
+
result = run(noProxy);
|
|
238
|
+
}
|
|
239
|
+
return { ok: result.status === 0, reason: (result.stderr || result.stdout || `gh pr ready exit ${result.status ?? 'unknown'}`).trim().slice(-200) };
|
|
240
|
+
}
|
|
241
|
+
|
|
89
242
|
/** REJUDGE base-stale — 이 재게이트(요청 시각 `since`) «뒤»에 같은 머리 land 가 base-stale 로 거절된 마지막 시도. */
|
|
90
243
|
export function baseStaleAttemptAfter(card: Pick<TaskCard, 'landAttempts'>, pr: number, head: string, since: string): NonNullable<TaskCard['landAttempts']>[number] | undefined {
|
|
91
244
|
const from = Date.parse(since);
|
|
@@ -258,8 +411,11 @@ export async function rejudgeCardOnHead(cardId: string, deps: RejudgeDeps): Prom
|
|
|
258
411
|
const pr = card ? cardPrNumber(card) : undefined;
|
|
259
412
|
if (!card || pr === undefined) return target('no-pr', 'card has no bound PR');
|
|
260
413
|
const reviews = (card.reviewRequests ?? []).filter((entry) => entry.pr === pr);
|
|
261
|
-
//
|
|
262
|
-
|
|
414
|
+
// 원격 부모는 node-b 의 liveMoves 가 꺼져 리뷰 이력이 0일 수 있다. HQ 거울의 종결 증거가 있을 때만 시작한다.
|
|
415
|
+
const adopted = card.adoptedPr?.pr === pr && typeof card.pr === 'object'
|
|
416
|
+
&& !!card.pr.url && sameUrl(card.adoptedPr.url, card.pr.url) && /^[a-f0-9]{40}$/i.test(card.adoptedPr.head);
|
|
417
|
+
if (!reviews.length && !adopted && (!card.parentHost || !(deps.remoteRunTerminal ?? remoteBoundRunTerminal)(card)))
|
|
418
|
+
return target('not-started', 'no task-agent review on this PR yet (remote terminal unproven or run not stopped/judged)', pr);
|
|
263
419
|
// 떼어 띄운 재게이트 결과를 «머리와 무관하게» 먼저 회수한다 — 결과가 나오기 전에 머리가 또 바뀌어도 그 머리의 결과는 카드에 남는다.
|
|
264
420
|
for (const pending of (card.regates ?? []).filter((entry) => entry.pr === pr && entry.passed === undefined && entry.resultPath)) {
|
|
265
421
|
let text: string | null = null;
|
|
@@ -295,10 +451,137 @@ export async function rejudgeCardOnHead(cardId: string, deps: RejudgeDeps): Prom
|
|
|
295
451
|
if ('reason' in found) return target('pr-unreadable', found.reason, pr, fromHead);
|
|
296
452
|
const { cwd, view } = found;
|
|
297
453
|
const toHead = view.head;
|
|
454
|
+
if (view.state === 'MERGED' || view.state === 'CLOSED') {
|
|
455
|
+
const at = now().toISOString();
|
|
456
|
+
updateTaskCard(statePath, card.id, (current) => {
|
|
457
|
+
if (!current || cardPrNumber(current) !== pr || typeof current.pr !== 'object' || current.pr.url !== url) return undefined;
|
|
458
|
+
const previous = boundPrTerminal(current);
|
|
459
|
+
if (previous?.state === view.state && previous.head === toHead) return undefined;
|
|
460
|
+
return { ...current, prTerminal: { pr, url, head: toHead, state: view.state as 'MERGED' | 'CLOSED', at } };
|
|
461
|
+
});
|
|
462
|
+
return target('pr-not-open', `PR #${pr} is ${view.state}`, pr, fromHead, toHead);
|
|
463
|
+
}
|
|
298
464
|
if (view.state !== 'OPEN') return target('pr-not-open', `PR #${pr} is ${view.state}`, pr, fromHead, toHead);
|
|
465
|
+
// An explicitly rejudged PR can be reopened. Clear the old marker before normal head judgement.
|
|
466
|
+
if (boundPrTerminal(card)) {
|
|
467
|
+
updateTaskCard(statePath, card.id, (current) => current && cardPrNumber(current) === pr && typeof current.pr === 'object' && current.pr.url === url
|
|
468
|
+
? { ...current, prTerminal: undefined } : undefined);
|
|
469
|
+
card = readTaskCard(card.id, statePath) ?? card;
|
|
470
|
+
}
|
|
299
471
|
const step = (s: RejudgeStep, decision: string, reason: string, record: boolean, extra: Partial<RejudgeOutcome> = {}) =>
|
|
300
472
|
emit({ card: card!.id, pr, fromHead, toHead, step: s, decision, reason, ...extra }, record);
|
|
301
473
|
|
|
474
|
+
// Adoption is a first judgement for the current head, even when the implementation run
|
|
475
|
+
// already asked for a review. A captured failure needs repair; an accepted result must
|
|
476
|
+
// reach the host regate instead of stopping at the old same-head shortcut below.
|
|
477
|
+
const adoptedReview = adopted ? (card.history ?? []).filter((item) => item.event === LIVE_MOVE_RESULT_EVENT
|
|
478
|
+
&& item.kind === 'review' && item.pr === pr && item.head && sameHead(item.head, toHead)
|
|
479
|
+
&& item.reviewed === true && ['pass', 'warn', 'fail'].includes(item.verdict ?? '')).at(-1) : undefined;
|
|
480
|
+
if (adopted && !adoptedReview) {
|
|
481
|
+
if (reviews.some((entry) => sameHead(entry.head, toHead))) {
|
|
482
|
+
step('review', 'review-pending', `PR #${pr} head ${toHead.slice(0, 12)} already has a review request; await its result`, false);
|
|
483
|
+
} else if (!deps.liveMoves.has('review')) {
|
|
484
|
+
step('review', 'would-review', 'adopted PR first judgement · review not enabled in HQ taskAgent.liveMoves — shadow', false);
|
|
485
|
+
} else {
|
|
486
|
+
const before = { reviewRequests: card.reviewRequests, landAttempts: card.landAttempts };
|
|
487
|
+
const result = await executeLiveReview(card, pr, { runId: card.runId ?? null, stopReason: 'adopted-pr-first-judgement', cwd }, { ...live, ...(deps.log ? { log: deps.log } : {}) });
|
|
488
|
+
recordMove(statePath, card.id, card.runId, 'review', result, pr, before, now, deps.log);
|
|
489
|
+
step('review', result.executed ? 'review-requested' : 'review-request-failed', result.detail, true);
|
|
490
|
+
}
|
|
491
|
+
return outcomes;
|
|
492
|
+
}
|
|
493
|
+
const capturedForHead = (card.history ?? []).filter((item) => item.event === LIVE_MOVE_RESULT_EVENT
|
|
494
|
+
&& item.kind === 'review' && item.pr === pr && item.head && sameHead(item.head, toHead)
|
|
495
|
+
&& item.reviewed === true && ['pass', 'warn', 'fail'].includes(item.verdict ?? '')).at(-1);
|
|
496
|
+
if (capturedForHead && (capturedForHead.verdict === 'fail' || (capturedForHead.mustFix ?? 0) > 0)) {
|
|
497
|
+
const capturedPath = capturedForHead.resultPath;
|
|
498
|
+
const requestMatches = !!capturedPath && (card.reviewRequests ?? []).some((entry) => entry.pr === pr
|
|
499
|
+
&& sameHead(entry.head, toHead) && entry.resultPath === capturedPath);
|
|
500
|
+
let mustFix: string[] | null = null;
|
|
501
|
+
try {
|
|
502
|
+
if (requestMatches && /^[0-9a-f]{64}$/.test(capturedForHead.reviewSha256 ?? '')) {
|
|
503
|
+
const text = read(capturedPath!);
|
|
504
|
+
if (repairReviewDigest(text) === capturedForHead.reviewSha256) mustFix = repairMustFixFromReview(text, pr, toHead);
|
|
505
|
+
}
|
|
506
|
+
} catch { /* fail closed below */ }
|
|
507
|
+
if (!mustFix && (deps.liveMoves.has('narrow-relaunch') || deps.liveMoves.has('salvage-relaunch') || deps.liveMoves.has('alternative'))
|
|
508
|
+
&& !(card.repairs ?? []).some((entry) => entry.pr === pr && (entry.status === 'requested' || entry.status === 'unconfirmed'))) {
|
|
509
|
+
if (pendingReviewResults(card).some((entry) => entry.pr === pr && sameHead(entry.head, toHead))) {
|
|
510
|
+
step('review', 'review-pending', 'replacement exact-head review pending before repair', false);
|
|
511
|
+
return outcomes;
|
|
512
|
+
}
|
|
513
|
+
const originalPath = reviewResultPath(statePath, card.id, pr, toHead);
|
|
514
|
+
const retried = (card.reviewRequests ?? []).some((entry) => entry.pr === pr && sameHead(entry.head, toHead)
|
|
515
|
+
&& entry.resultPath && entry.resultPath !== originalPath);
|
|
516
|
+
if (deps.liveMoves.has('review') && !retried) {
|
|
517
|
+
const before = { reviewRequests: card.reviewRequests, landAttempts: card.landAttempts };
|
|
518
|
+
const result = await executeLiveReview(card, pr, { runId: card.runId ?? null, stopReason: 'captured-review-identity-unavailable', cwd, forceNewReview: true },
|
|
519
|
+
{ ...live, ...(deps.log ? { log: deps.log } : {}) });
|
|
520
|
+
recordMove(statePath, card.id, card.runId, 'review', result, pr, before, now, deps.log);
|
|
521
|
+
step('review', result.executed ? 'review-requested' : 'review-request-failed', result.detail, true);
|
|
522
|
+
return outcomes;
|
|
523
|
+
}
|
|
524
|
+
step('repair', 'repair-stopped', 'captured review result identity missing or changed; request a new review', true);
|
|
525
|
+
return outcomes;
|
|
526
|
+
}
|
|
527
|
+
if (mustFix?.length || (card.repairs ?? []).some((entry) => entry.pr === pr && (entry.status === 'requested' || entry.status === 'unconfirmed'))) {
|
|
528
|
+
if (!view.branch) {
|
|
529
|
+
step('repair', 'repair-stopped', 'PR branch name unavailable', true);
|
|
530
|
+
return outcomes;
|
|
531
|
+
}
|
|
532
|
+
const result = await runRepairCycle({ card, statePath, repo: cwd, pr, head: toHead, branch: view.branch,
|
|
533
|
+
mustFix: mustFix ?? [], liveMoves: deps.liveMoves, now: now(), readFile: read,
|
|
534
|
+
...(deps.requestRepair ? { request: deps.requestRepair } : {}),
|
|
535
|
+
...(deps.applyRepair ? { apply: deps.applyRepair } : {}),
|
|
536
|
+
...(deps.notifyRepair ? { notify: deps.notifyRepair } : {}) });
|
|
537
|
+
step('repair', result.decision, result.reason, true);
|
|
538
|
+
return outcomes;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
if (adopted && (adoptedReview?.verdict === 'fail' || !capturedLandReview(card, pr, toHead)
|
|
542
|
+
|| capturedLandReview(card, pr, toHead)?.mustFixCount !== 0)) {
|
|
543
|
+
step('review', 'repair-needed', `PR #${pr} head ${toHead.slice(0, 12)} review ${adoptedReview?.verdict ?? 'unknown'} · must-fix ${adoptedReview?.mustFix ?? 'unknown'} — owner repair`, true);
|
|
544
|
+
return outcomes;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// A remote --ta-land parent leaves its PR draft. Only a captured review for this exact
|
|
548
|
+
// head may let HQ make it ready; the regate runner deliberately rejects draft PRs.
|
|
549
|
+
let readyConfirmed = false;
|
|
550
|
+
const readyReview = card.taLand ? capturedLandReview(card, pr, toHead) : undefined;
|
|
551
|
+
if (readyReview?.mustFixCount === 0 && deps.liveMoves.has('propose-land')) {
|
|
552
|
+
let fresh: Awaited<ReturnType<NonNullable<LiveMoveDeps['landPrHead']>>>;
|
|
553
|
+
try { fresh = await (live.landPrHead ?? defaultLandPrHead)(pr, cwd); } catch { fresh = null; }
|
|
554
|
+
if (!fresh || fresh.state !== 'OPEN' || !sameHead(fresh.head, toHead) || !fresh.url || !sameUrl(fresh.url, url)) {
|
|
555
|
+
step('ready', 'ready-unconfirmed', 'fresh PR head, URL or draft state unavailable — no regate', false);
|
|
556
|
+
return outcomes;
|
|
557
|
+
}
|
|
558
|
+
if (fresh.isDraft) {
|
|
559
|
+
const result = await (deps.readyPr ?? defaultReadyPr)(pr, cwd);
|
|
560
|
+
if (!result.ok) {
|
|
561
|
+
step('ready', 'ready-failed', result.reason, true);
|
|
562
|
+
return outcomes;
|
|
563
|
+
}
|
|
564
|
+
try { fresh = await (live.landPrHead ?? defaultLandPrHead)(pr, cwd); } catch { fresh = null; }
|
|
565
|
+
if (!fresh || fresh.isDraft || fresh.state !== 'OPEN' || !sameHead(fresh.head, toHead) || !fresh.url || !sameUrl(fresh.url, url)) {
|
|
566
|
+
step('ready', 'ready-unconfirmed', 'gh pr ready returned success but the same PR/head is not confirmed ready', true);
|
|
567
|
+
return outcomes;
|
|
568
|
+
}
|
|
569
|
+
step('ready', 'ready-confirmed', `PR #${pr} head ${toHead.slice(0, 12)} is ready after captured review`, true);
|
|
570
|
+
}
|
|
571
|
+
readyConfirmed = true;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
if (!reviews.length) {
|
|
575
|
+
if (!deps.liveMoves.has('review')) step('review', 'would-review', 'remote run terminal proven · review not enabled in HQ taskAgent.liveMoves — shadow', false);
|
|
576
|
+
else {
|
|
577
|
+
const before = { reviewRequests: card.reviewRequests, landAttempts: card.landAttempts };
|
|
578
|
+
const result = await executeLiveReview(card, pr, { runId: card.runId ?? null, stopReason: 'remote-run-terminal', cwd }, { ...live, ...(deps.log ? { log: deps.log } : {}) });
|
|
579
|
+
recordMove(statePath, card.id, card.runId, 'review', result, pr, before, now, deps.log);
|
|
580
|
+
step('review', result.executed ? 'review-requested' : 'review-request-failed', result.detail, true);
|
|
581
|
+
}
|
|
582
|
+
return outcomes;
|
|
583
|
+
}
|
|
584
|
+
|
|
302
585
|
const tryCatchUp = async (fromBase: string | undefined, why: string): Promise<boolean> => {
|
|
303
586
|
const prior = catchUpHistory(card!, pr);
|
|
304
587
|
// 상한은 «실제 push» 만 센다 — 충돌·실패 뒤 사람이 새 머리를 올리면 다시 시도할 수 있어야 한다.
|
|
@@ -341,13 +624,22 @@ export async function rejudgeCardOnHead(cardId: string, deps: RejudgeDeps): Prom
|
|
|
341
624
|
return true;
|
|
342
625
|
};
|
|
343
626
|
|
|
344
|
-
//
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
627
|
+
// 기존 live 경로의 같은 머리 조기 반환은 유지한다. 완료 훅의 그림자 판단만 통과 리뷰를 다시 본다.
|
|
628
|
+
if (!adopted && !regates.length && judgedHead && sameHead(toHead, judgedHead) && !landRetryPending(card, pr, toHead)) {
|
|
629
|
+
const unchangedReview = capturedLandReview(card, pr, toHead);
|
|
630
|
+
if (!deps.shadowSameHead || deps.liveMoves.size !== 0 || !unchangedReview || !['pass', 'warn'].includes(unchangedReview.verdict) || unchangedReview.mustFixCount !== 0) {
|
|
631
|
+
return target('head-unchanged', `PR #${pr} head is still the judged head`, pr, fromHead, toHead);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
// `regates` 는 PR 전체 기록이다. 현재 머리의 결과만 재사용하고, 없으면 재게이트한다.
|
|
635
|
+
const currentHeadRegate = regates.filter((entry) => sameHead(entry.head, toHead)).at(-1);
|
|
348
636
|
|
|
349
637
|
// ① 재게이트 — 지금 머리의 «마지막» 기록(REJUDGE base-stale 로 같은 머리를 다시 잰 기록이 뒤에 붙는다).
|
|
350
|
-
const regate =
|
|
638
|
+
const regate = currentHeadRegate;
|
|
639
|
+
// A draft rejection is a stale input after the same PR becomes ready, not a permanent
|
|
640
|
+
// judgement about the code. Retry this exact head once with a separate result path.
|
|
641
|
+
const retryDraft = readyConfirmed && regate?.passed === false && /^pr-head:.*\bisDraft=true\b/.test(regate.detail ?? '')
|
|
642
|
+
&& !regates.some((entry) => sameHead(entry.head, toHead) && entry.readyRetry === true);
|
|
351
643
|
// REJUDGE base-stale — 이 머리의 통과한 재게이트로 land 했더니 base-stale-overlap 으로 거절됐다 → 새 base 위에서 한 번 더 잰다.
|
|
352
644
|
const stale = regate?.passed === true ? baseStaleAttemptAfter(card, pr, toHead, regate.at) : undefined;
|
|
353
645
|
const staleBase = stale ? regate?.baseCommit : undefined;
|
|
@@ -355,8 +647,9 @@ export async function rejudgeCardOnHead(cardId: string, deps: RejudgeDeps): Prom
|
|
|
355
647
|
await tryCatchUp(undefined, `base-stale-overlap on head ${toHead.slice(0, 12)} but the stale regate has no baseCommit`);
|
|
356
648
|
return outcomes;
|
|
357
649
|
}
|
|
358
|
-
if (!regate || staleBase) {
|
|
359
|
-
const why = staleBase ? `base-stale-overlap — gate base ${staleBase.slice(0, 12)} went stale (${(stale!.reason ?? '').replace(/^base-stale-overlap:\s*/, '').slice(0, 160)})`
|
|
650
|
+
if (!regate || staleBase || retryDraft) {
|
|
651
|
+
const why = staleBase ? `base-stale-overlap — gate base ${staleBase.slice(0, 12)} went stale (${(stale!.reason ?? '').replace(/^base-stale-overlap:\s*/, '').slice(0, 160)})`
|
|
652
|
+
: retryDraft ? 'draft rejection became stale after PR ready' : '';
|
|
360
653
|
if (regates.length >= REJUDGE_HEAD_CAP) {
|
|
361
654
|
step('regate', 'cap-reached', `${why ? `${why} · ` : ''}re-gated ${regates.length} times already (cap ${REJUDGE_HEAD_CAP}) — no more rejudge; needs owner`, true);
|
|
362
655
|
return outcomes;
|
|
@@ -364,17 +657,19 @@ export async function rejudgeCardOnHead(cardId: string, deps: RejudgeDeps): Prom
|
|
|
364
657
|
if (!deps.liveMoves.has('propose-land')) step('regate', 'would-regate', `${why ? `${why} · ` : ''}propose-land not enabled in taskAgent.liveMoves — shadow`, false);
|
|
365
658
|
else {
|
|
366
659
|
const at = now().toISOString();
|
|
367
|
-
const resultPath =
|
|
660
|
+
const resultPath = retryDraft ? readyRegateResultPath(statePath, card.id, pr, toHead)
|
|
661
|
+
: regateResultPath(statePath, card.id, pr, toHead, staleBase);
|
|
368
662
|
let claimed = false;
|
|
369
663
|
let capped = false;
|
|
370
664
|
updateTaskCard(statePath, card.id, (current) => {
|
|
371
665
|
const held = (current?.regates ?? []).filter((entry) => entry.pr === pr);
|
|
372
666
|
// 멱등 — 처음 재게이트는 «머리», base-stale 재게이트는 «머리 ⊕ 낡은 base» 가 키다(같은 머리·같은 base 로 두 번 안 잰다).
|
|
373
|
-
if (!current || held.some((entry) => sameHead(entry.head, toHead) && (staleBase ? entry.staleBase !== undefined && sameHead(entry.staleBase, staleBase)
|
|
667
|
+
if (!current || held.some((entry) => sameHead(entry.head, toHead) && (staleBase ? entry.staleBase !== undefined && sameHead(entry.staleBase, staleBase)
|
|
668
|
+
: retryDraft ? entry.readyRetry === true : true))) return undefined;
|
|
374
669
|
// 상한은 잠금 안의 «지금» 카드로 다시 잰다 — 다른 머리를 본 두 틱이 겹쳐도 상한을 넘지 않게.
|
|
375
670
|
if (held.length >= REJUDGE_HEAD_CAP) { capped = true; return undefined; }
|
|
376
671
|
claimed = true;
|
|
377
|
-
return { ...current, regates: [...(current.regates ?? []), { pr, head: toHead, at, resultPath, ...(staleBase ? { staleBase } : {}) }] };
|
|
672
|
+
return { ...current, regates: [...(current.regates ?? []), { pr, head: toHead, at, resultPath, ...(staleBase ? { staleBase } : {}), ...(retryDraft ? { readyRetry: true as const } : {}) }] };
|
|
378
673
|
});
|
|
379
674
|
if (capped) {
|
|
380
675
|
step('regate', 'cap-reached', `${why ? `${why} · ` : ''}re-gated ${REJUDGE_HEAD_CAP} times already (cap ${REJUDGE_HEAD_CAP} · claim-time recheck) — no more rejudge; needs owner`, true);
|
|
@@ -395,7 +690,7 @@ export async function rejudgeCardOnHead(cardId: string, deps: RejudgeDeps): Prom
|
|
|
395
690
|
}
|
|
396
691
|
}
|
|
397
692
|
// base-stale 재게이트는 그 결과가 나와야 land 한다 — 낡은 base 의 증거로 이번 틱에 다시 land 하지 않는다.
|
|
398
|
-
if (staleBase) return outcomes;
|
|
693
|
+
if (staleBase || retryDraft) return outcomes;
|
|
399
694
|
} else if (regate.passed === undefined) {
|
|
400
695
|
step('regate', 'awaiting-regate-result', 'host regate still running', false);
|
|
401
696
|
}
|
|
@@ -463,10 +758,7 @@ export async function rejudgeCardOnHead(cardId: string, deps: RejudgeDeps): Prom
|
|
|
463
758
|
// review-results 파일을 못 읽으면 reviewVerdict 'missing' — 0 으로 쓰지 않는다(land 판정의 회수 결과와는 별개 파일).
|
|
464
759
|
let reviewForLedger: ParsedReviewResult | null = null;
|
|
465
760
|
try { reviewForLedger = parseReviewResultOutput(read(reviewResult)); } catch { reviewForLedger = null; }
|
|
466
|
-
appendLandOrderRow(statePath, joinLandOrderRow({
|
|
467
|
-
overlapEvidence: { ...overlapEvidenceContent, cardId: card.id },
|
|
468
|
-
reviewResult: reviewForLedger, landed, at: now().toISOString(),
|
|
469
|
-
}));
|
|
761
|
+
appendLandOrderRow(statePath, joinLandOrderRow({ overlapEvidence: { ...overlapEvidenceContent, cardId: card.id }, reviewResult: reviewForLedger, landed, at: now().toISOString() }));
|
|
470
762
|
landOrderLog('appended');
|
|
471
763
|
} catch {
|
|
472
764
|
landOrderLog('append-failed');
|
|
@@ -476,7 +768,9 @@ export async function rejudgeCardOnHead(cardId: string, deps: RejudgeDeps): Prom
|
|
|
476
768
|
const attempt = landed.landAttempt?.class
|
|
477
769
|
? ` · land-class ${landed.landAttempt.class} (${landed.landAttempt.reason}) · attempt ${landed.landAttempt.attempt} · ${landed.landAttempt.class === 'base-stale' ? 'base-stale → re-gate on a new base next tick (cap counts)' : landed.landAttempt.retryAllowed ? 'retry allowed on this head' : 'no retry on this head'}`
|
|
478
770
|
: '';
|
|
479
|
-
step('land', landed.executed && landed.ok ? 'landed'
|
|
771
|
+
step('land', landed.executed && landed.ok ? 'landed'
|
|
772
|
+
: !landed.executed && landed.ok ? 'land-skipped' : 'land-rejected',
|
|
773
|
+
`${landed.detail}${attempt} · overlap-evidence ${overlapEvidence}`, true);
|
|
480
774
|
return outcomes;
|
|
481
775
|
}
|
|
482
776
|
|
|
@@ -487,10 +781,12 @@ function recordMove(statePath: string, cardId: string, runId: string | undefined
|
|
|
487
781
|
} catch { /* recordLiveMoveHistory 는 스스로 관측한다 */ }
|
|
488
782
|
}
|
|
489
783
|
|
|
490
|
-
/** 재판정 대상 카드 —
|
|
784
|
+
/** 재판정 대상 카드 — 기존 리뷰 또는 HQ 가 종결 원장을 검증할 원격 부모 카드. */
|
|
491
785
|
export function rejudgeCandidates(tasks: Readonly<Record<string, TaskCard>>): string[] {
|
|
492
786
|
return Object.values(tasks).filter((card) => {
|
|
493
787
|
const pr = cardPrNumber(card);
|
|
494
|
-
return pr !== undefined && (card.reviewRequests ?? []).some((entry) => entry.pr === pr)
|
|
788
|
+
return pr !== undefined && !boundPrTerminal(card) && ((card.reviewRequests ?? []).some((entry) => entry.pr === pr)
|
|
789
|
+
|| (card.status === 'launched' && card.taLand === true && ((!!card.parentHost && !!card.runId && !!card.runChildId)
|
|
790
|
+
|| (card.adoptedPr?.pr === pr && typeof card.pr === 'object' && !!card.pr.url && sameUrl(card.adoptedPr.url, card.pr.url)))));
|
|
495
791
|
}).map((card) => card.id);
|
|
496
792
|
}
|