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,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GREEN-FROM-LEDGER, first slice: decide from a bound card or an explicit
|
|
3
|
+
* checklist evidence line. This module never writes a checklist or card.
|
|
4
|
+
*/
|
|
5
|
+
import type { ChecklistItem } from '../release-loop/checklist.js';
|
|
6
|
+
import { debug } from '../debug/log.js';
|
|
7
|
+
import { cardPrNumber, type TaskCard } from './task-hand.js';
|
|
8
|
+
|
|
9
|
+
export type GreenSweepVerdict = 'green' | 'record-only' | 'skip';
|
|
10
|
+
export interface GreenSweepCandidate {
|
|
11
|
+
cell: string;
|
|
12
|
+
pr: number;
|
|
13
|
+
source: 'card' | 'evidence';
|
|
14
|
+
card?: TaskCard;
|
|
15
|
+
ambiguousAcrossCards?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface GreenSweepPr {
|
|
18
|
+
number: number;
|
|
19
|
+
state: string;
|
|
20
|
+
mergeSha?: string;
|
|
21
|
+
url?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface GreenSweepDecision {
|
|
24
|
+
cell: string;
|
|
25
|
+
pr: number;
|
|
26
|
+
mergeSha: string | null;
|
|
27
|
+
source: GreenSweepCandidate['source'];
|
|
28
|
+
verdict: GreenSweepVerdict;
|
|
29
|
+
reason: string;
|
|
30
|
+
evidence: string;
|
|
31
|
+
existingEvidence: string;
|
|
32
|
+
/** Preview only; a later live operation may persist this complete value. */
|
|
33
|
+
plannedEvidence: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** PR-body mentions are intentionally absent from this parser. */
|
|
37
|
+
export function explicitEvidencePrs(evidence: string | undefined): number[] {
|
|
38
|
+
const numbers = new Set<number>();
|
|
39
|
+
for (const line of (evidence ?? '').split(/\r?\n/)) {
|
|
40
|
+
// A previous refusal is diagnostic text, never new landing evidence.
|
|
41
|
+
if (/^GREEN-FROM-LEDGER:/i.test(line) && !/· green ·/.test(line)) continue;
|
|
42
|
+
// A negated landing claim cannot establish this cell's PR ownership.
|
|
43
|
+
if (/미착지|미병합|예정|계획|아님|않음|안\s*함|보류|대기|실패|불가|아직|미완료|누락|못\s*함|(?:착지|병합)\s*전|\b(?:not\s+(?:yet\s+)?(?:merged|landed)|unmerged|pending|planned)\b/i.test(line)) continue;
|
|
44
|
+
// A PR number only counts when this cell's own evidence calls it landed.
|
|
45
|
+
for (const match of line.matchAll(/(?:착지|병합|merged|landed)\s*(?:PR\s*)?#(\d+)\b|(?:PR\s*)?#(\d+)\s*(?:착지|병합|merged|landed)(?=$|[^A-Za-z가-힣])/gi)) {
|
|
46
|
+
const number = Number(match[1] ?? match[2]);
|
|
47
|
+
if (Number.isSafeInteger(number) && number > 0) numbers.add(number);
|
|
48
|
+
}
|
|
49
|
+
for (const match of line.matchAll(/https?:\/\/github\.com\/[^/\s]+\/[^/\s]+\/pull\/(\d+)\b\s*(?:착지|병합|merged|landed)|(?:착지|병합|merged|landed)\s*https?:\/\/github\.com\/[^/\s]+\/[^/\s]+\/pull\/(\d+)\b/gi)) {
|
|
50
|
+
const number = Number(match[1] ?? match[2]);
|
|
51
|
+
if (Number.isSafeInteger(number) && number > 0) numbers.add(number);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return [...numbers];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function greenSweepCandidates(item: ChecklistItem, version: string, cards: readonly TaskCard[]): GreenSweepCandidate[] {
|
|
58
|
+
if (item.status !== 'yellow' && item.status !== 'red') return [];
|
|
59
|
+
const candidates = new Map<number, GreenSweepCandidate>();
|
|
60
|
+
for (const number of explicitEvidencePrs(item.evidence)) candidates.set(number, { cell: item.id, pr: number, source: 'evidence' });
|
|
61
|
+
for (const card of cards) {
|
|
62
|
+
if (card.checklistId !== item.id || (card.checklistVersion && card.checklistVersion !== version)
|
|
63
|
+
|| (card.completion ?? 'code-pr') !== 'code-pr') continue;
|
|
64
|
+
const pr = cardPrNumber(card);
|
|
65
|
+
if (!pr) continue;
|
|
66
|
+
// Multiple cards claiming one PR are ambiguous, even if one has a complete marker.
|
|
67
|
+
if (candidates.get(pr)?.source === 'card') candidates.set(pr, { cell: item.id, pr, source: 'card' });
|
|
68
|
+
else candidates.set(pr, { cell: item.id, pr, source: 'card', card });
|
|
69
|
+
}
|
|
70
|
+
return [...candidates.values()].map((candidate) => ({ ...candidate,
|
|
71
|
+
ambiguousAcrossCards: cards.filter((card) => cardPrNumber(card) === candidate.pr).length > 1,
|
|
72
|
+
})).sort((a, b) => a.pr - b.pr);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function ambiguousGreenSweepPrs(items: readonly ChecklistItem[], version: string, cards: readonly TaskCard[]): Set<number> {
|
|
76
|
+
const claims = new Map<number, Set<string>>();
|
|
77
|
+
for (const item of items) {
|
|
78
|
+
const numbers = new Set(explicitEvidencePrs(item.evidence));
|
|
79
|
+
for (const card of cards) {
|
|
80
|
+
if (card.checklistId === item.id && (!card.checklistVersion || card.checklistVersion === version)
|
|
81
|
+
&& (card.completion ?? 'code-pr') === 'code-pr') {
|
|
82
|
+
const pr = cardPrNumber(card);
|
|
83
|
+
if (pr) numbers.add(pr);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
for (const pr of numbers) {
|
|
87
|
+
const cells = claims.get(pr) ?? new Set<string>();
|
|
88
|
+
cells.add(item.id);
|
|
89
|
+
claims.set(pr, cells);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return new Set([...claims].filter(([, cells]) => cells.size > 1).map(([pr]) => pr));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function appendGreenSweepEvidence(existing: string | undefined, line: string): string {
|
|
96
|
+
const before = existing ?? '';
|
|
97
|
+
if (before.split(/\r?\n/).includes(line)) return before;
|
|
98
|
+
return `${before}${before && !before.endsWith('\n') ? '\n' : ''}${line}`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** A merge establishes ownership of a PR, not completion of a whole cell. */
|
|
102
|
+
export function decideGreenSweep(input: {
|
|
103
|
+
item: ChecklistItem;
|
|
104
|
+
candidate: GreenSweepCandidate;
|
|
105
|
+
candidateCount: number;
|
|
106
|
+
pr: GreenSweepPr | null;
|
|
107
|
+
ancestor: boolean | null;
|
|
108
|
+
hold: boolean | null;
|
|
109
|
+
repositoryMatches?: boolean | null;
|
|
110
|
+
ambiguousAcrossCells?: boolean;
|
|
111
|
+
}): GreenSweepDecision {
|
|
112
|
+
const { item, candidate, pr } = input;
|
|
113
|
+
const mergeSha = pr?.mergeSha && /^[a-f0-9]{40}$/i.test(pr.mergeSha) ? pr.mergeSha : null;
|
|
114
|
+
const result = (verdict: GreenSweepVerdict, reason: string): GreenSweepDecision => {
|
|
115
|
+
const evidence = `GREEN-FROM-LEDGER: PR #${candidate.pr}${mergeSha ? ` merged ${mergeSha}` : ''} · ${verdict} · ${reason}`;
|
|
116
|
+
return { cell: item.id, pr: candidate.pr, mergeSha, source: candidate.source, verdict, reason,
|
|
117
|
+
evidence, existingEvidence: item.evidence ?? '', plannedEvidence: appendGreenSweepEvidence(item.evidence, evidence) };
|
|
118
|
+
};
|
|
119
|
+
if (item.status !== 'yellow' && item.status !== 'red') return result('skip', 'cell-not-yellow');
|
|
120
|
+
if (item.status === 'red') return result('skip', 'red-cell-requires-owner');
|
|
121
|
+
if (!pr || pr.number !== candidate.pr) return result('skip', 'pr-unreadable');
|
|
122
|
+
if (input.repositoryMatches === false) return result('skip', 'repository-mismatch');
|
|
123
|
+
if (input.repositoryMatches !== true) return result('record-only', 'repository-unreadable');
|
|
124
|
+
if (pr.state !== 'MERGED') return result('skip', `pr-${pr.state.toLowerCase()}`);
|
|
125
|
+
if (!mergeSha) return result('skip', 'merge-sha-unreadable');
|
|
126
|
+
if (input.ancestor === null) return result('record-only', 'origin-main-ref-unverified');
|
|
127
|
+
if (!input.ancestor) return result('skip', 'merge-sha-not-on-origin-main');
|
|
128
|
+
const explicitUrls = [...(item.evidence ?? '').matchAll(/https?:\/\/github\.com\/[^/\s]+\/[^/\s]+\/pull\/\d+\b/gi)]
|
|
129
|
+
.map((match) => match[0]);
|
|
130
|
+
if (explicitUrls.some((url) => /\/pull\/(\d+)\b/.exec(url)?.[1] === String(candidate.pr)
|
|
131
|
+
&& url.replace(/\/+$/, '').toLowerCase() !== (pr.url ?? '').replace(/\/+$/, '').toLowerCase()))
|
|
132
|
+
return result('skip', 'evidence-pr-url-mismatch');
|
|
133
|
+
if (/보류|중단|정지|\bhold\b/i.test(`${item.title}\n${item.evidence ?? ''}`))
|
|
134
|
+
return result('skip', 'cell-stop-evidence');
|
|
135
|
+
if (input.hold === null) return result('record-only', 'hold-state-unreadable');
|
|
136
|
+
if (input.hold) return result('skip', 'traffic-hold');
|
|
137
|
+
if (input.candidateCount !== 1) return result('record-only', 'multiple-piece-prs');
|
|
138
|
+
if (input.ambiguousAcrossCells) return result('record-only', 'ambiguous-pr-across-cells');
|
|
139
|
+
if (candidate.ambiguousAcrossCards) return result('record-only', 'ambiguous-pr-across-cards');
|
|
140
|
+
if (/남은|나머지|후속|조각|분할|미착지|미병합|예정|계획|(?:착지|병합)\s*(?:실패|불가|아직|미완료|누락|못\s*함)|PR\s*#\d+\s*누락|추가\s*(?:작업|실물|검증|배선|조각|PR|필요)|(?:착지|실물|판정|검증|승인|운영)\s*대기|대기\s*(?:중|필요)|[1-9]차|(?:작업|검증|확인|운영|수리|배선)\s*필요|(?:일부|부분)(?:\s*범위(?:만)?)?\s*완료|잔여|미충족|미달|미완료|대체\s*후보/i.test(`${item.title}\n${item.evidence ?? ''}`))
|
|
141
|
+
return result('record-only', 'remaining-or-multiple-piece-evidence');
|
|
142
|
+
const verdictLines = [...(item.evidence ?? '').matchAll(/(?:^|\n)\s*(?:판정|결론|상태)\s*:\s*([^\n]+)/gi)];
|
|
143
|
+
if (verdictLines.some((match) => !/^(?:green|완료|충족)(?:$|[\s·,.;])/i.test(match[1] ?? '')))
|
|
144
|
+
return result('record-only', 'conflicting-cell-verdict-evidence');
|
|
145
|
+
if (candidate.source === 'evidence') {
|
|
146
|
+
const fullCellProof = /(?:^|\n)\s*(?:판정|결론|상태)\s*:\s*(?:green|완료|충족)(?:$|[\s·,.;])/i;
|
|
147
|
+
return fullCellProof.test(item.evidence ?? '')
|
|
148
|
+
? result('green', 'explicit-cell-completion-evidence')
|
|
149
|
+
: result('record-only', 'cell-completion-not-explicit');
|
|
150
|
+
}
|
|
151
|
+
const card = candidate.card;
|
|
152
|
+
if (!card) return result('record-only', 'ambiguous-card-binding');
|
|
153
|
+
if (!card.checklistVersion) return result('record-only', 'card-version-unbound');
|
|
154
|
+
if (card.status !== 'done') return result('record-only', 'card-not-done');
|
|
155
|
+
if (card.postLand && (card.postLand.outcome !== 'complete' || card.postLand.remaining.length > 0))
|
|
156
|
+
return result('record-only', 'card-post-land-incomplete');
|
|
157
|
+
if (card.history.some((event) => event.event === 'closed-by-hand'))
|
|
158
|
+
return result('record-only', 'card-stop-evidence');
|
|
159
|
+
if (typeof card.pr === 'object' && pr.url && card.pr.url
|
|
160
|
+
&& card.pr.url.replace(/\/+$/, '') !== pr.url.replace(/\/+$/, ''))
|
|
161
|
+
return result('record-only', 'card-pr-url-mismatch');
|
|
162
|
+
if (card.prTerminal?.state === 'CLOSED') return result('record-only', 'card-terminal-conflict');
|
|
163
|
+
return result('green', card.postLand?.outcome === 'complete' ? 'bound-card-complete' : 'bound-card-merged');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Every candidate emits one structured decision, including refusals. */
|
|
167
|
+
export function observeGreenSweep(decision: GreenSweepDecision, log: typeof debug.log = debug.log.bind(debug)): void {
|
|
168
|
+
log('task-agent.green-sweep', decision.verdict,
|
|
169
|
+
{ cell: decision.cell, pr: decision.pr, mergeSha: decision.mergeSha, reason: decision.reason });
|
|
170
|
+
}
|
|
@@ -24,7 +24,7 @@ const defaultGit: GitRun = (cwd, args) => {
|
|
|
24
24
|
|
|
25
25
|
export interface LandWorktreeInput { card: string; pr: number; head: string; branch: string | undefined; repoCwd: string; statePath: string }
|
|
26
26
|
export type LandWorktreeRemoval = { removed: boolean; reason?: string; /** 이 호출이 만든 가지를 지웠나 — 만든 가지가 없으면 null. */ branchRemoved?: boolean | null };
|
|
27
|
-
export interface LandWorktree { cwd: string; cleanup: () => LandWorktreeRemoval }
|
|
27
|
+
export interface LandWorktree { cwd: string; cleanup: () => LandWorktreeRemoval; /** false means an existing owner's tree is borrowed, never removed. */ created?: boolean }
|
|
28
28
|
|
|
29
29
|
export interface LandWorktreeDeps {
|
|
30
30
|
git?: GitRun;
|
|
@@ -46,6 +46,14 @@ export function localHead(cwd: string, git: GitRun = defaultGit): string | null
|
|
|
46
46
|
} catch { return null; }
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
/** cwd 의 현재 가지 — detached·조회 실패는 null. */
|
|
50
|
+
export function localBranch(cwd: string, git: GitRun = defaultGit): string | null {
|
|
51
|
+
try {
|
|
52
|
+
const r = git(cwd, ['branch', '--show-current']);
|
|
53
|
+
return r.status === 0 && r.stdout.trim() ? r.stdout.trim() : null;
|
|
54
|
+
} catch { return null; }
|
|
55
|
+
}
|
|
56
|
+
|
|
49
57
|
async function defaultProvideDeps(worktree: string, sourceRoot: string): Promise<void> {
|
|
50
58
|
const { spawnSync } = await import('node:child_process');
|
|
51
59
|
const { provideCheckoutDependencies } = await import('../self-implement/host-regate.js');
|
|
@@ -71,6 +79,24 @@ function registered(git: GitRun, repoCwd: string, path: string): boolean | null
|
|
|
71
79
|
return list.stdout.split('\n').some((line) => line.startsWith('worktree ') && real(line.slice('worktree '.length).trim()) === want);
|
|
72
80
|
}
|
|
73
81
|
|
|
82
|
+
/** A registered checkout of the exact PR branch. An unreadable/malformed registry is never treated as vacant. */
|
|
83
|
+
function branchOwner(git: GitRun, repoCwd: string, branch: string): { path: string | null; reason?: string } {
|
|
84
|
+
let list: ReturnType<GitRun>;
|
|
85
|
+
try { list = git(repoCwd, ['worktree', 'list', '--porcelain']); }
|
|
86
|
+
catch { return { path: null, reason: 'git worktree registry unreadable (owner unknown)' }; }
|
|
87
|
+
if (list.status !== 0 || !list.stdout.trim()) return { path: null, reason: 'git worktree registry unreadable (owner unknown)' };
|
|
88
|
+
const entries = list.stdout.trim().split(/\n\s*\n/);
|
|
89
|
+
const owners: string[] = [];
|
|
90
|
+
for (const entry of entries) {
|
|
91
|
+
const lines = entry.split('\n');
|
|
92
|
+
const pathLine = lines.find(line => line.startsWith('worktree '));
|
|
93
|
+
if (!pathLine?.slice(9).trim()) return { path: null, reason: 'git worktree registry malformed (owner unknown)' };
|
|
94
|
+
if (lines.some(line => line === `branch refs/heads/${branch}`)) owners.push(pathLine.slice(9).trim());
|
|
95
|
+
}
|
|
96
|
+
if (owners.length > 1) return { path: null, reason: `PR branch ${branch} has multiple registered owners (owner unknown)` };
|
|
97
|
+
return { path: owners[0] ?? null };
|
|
98
|
+
}
|
|
99
|
+
|
|
74
100
|
/** 이 저장소의 git 공통 디렉터리(실경로) — 못 읽으면 null. */
|
|
75
101
|
function commonDir(git: GitRun, repoCwd: string): string | null {
|
|
76
102
|
const r = git(repoCwd, ['rev-parse', '--path-format=absolute', '--git-common-dir']);
|
|
@@ -110,7 +136,7 @@ function removeWorktree(git: GitRun, repoCwd: string, path: string): { removed:
|
|
|
110
136
|
}
|
|
111
137
|
|
|
112
138
|
/** `created` — 트리를 만들었었나(모르면 null) · `removed` — 만들었다면 걷었나(만든 적 없거나 모르면 null). */
|
|
113
|
-
export type LandWorktreeFailure = { reason: string; created: boolean | null; removed: boolean | null };
|
|
139
|
+
export type LandWorktreeFailure = { reason: string; created: boolean | null; removed: boolean | null; kind?: 'worktree-conflict'; ownerPath?: string };
|
|
114
140
|
|
|
115
141
|
/** PR 머리 트리를 만든다 — 못 만들면 이유 ⊕ 만들었다 걷었는지(관측용). 던지지 않는다. */
|
|
116
142
|
export async function createLandWorktree(input: LandWorktreeInput, deps: LandWorktreeDeps = {}): Promise<LandWorktree | LandWorktreeFailure> {
|
|
@@ -119,10 +145,15 @@ export async function createLandWorktree(input: LandWorktreeInput, deps: LandWor
|
|
|
119
145
|
const head = input.head.toLowerCase();
|
|
120
146
|
const branch = input.branch;
|
|
121
147
|
const fail = (reason: string, created: boolean | null = false, removed: boolean | null = null): LandWorktreeFailure => ({ reason, created, removed });
|
|
148
|
+
const conflict = (reason: string, ownerPath?: string): LandWorktreeFailure => ({ reason: `worktree-conflict: ${reason}`, created: false, removed: null, kind: 'worktree-conflict', ...(ownerPath ? { ownerPath } : {}) });
|
|
122
149
|
if (!branch) return fail('PR head branch name unknown');
|
|
123
150
|
if (!/^[0-9a-f]{40}$/.test(head)) return fail('PR head is not a full sha');
|
|
124
151
|
// 지난 시도가 남긴 같은 자리는 먼저 걷는다(같은 PR·머리 · 재사용하지 않는다 — 상태를 모르는 트리에서 착지하지 않게).
|
|
125
|
-
|
|
152
|
+
let atLandPath: boolean | null;
|
|
153
|
+
try { atLandPath = registered(git, input.repoCwd, path); }
|
|
154
|
+
catch { atLandPath = null; }
|
|
155
|
+
if (atLandPath === null) return conflict('git worktree list unreadable (owner unknown)');
|
|
156
|
+
if (existsSync(path) || atLandPath) {
|
|
126
157
|
const stale = removeWorktree(git, input.repoCwd, path);
|
|
127
158
|
if (!stale.removed) return fail(`stale land worktree could not be removed: ${stale.reason}`);
|
|
128
159
|
}
|
|
@@ -135,9 +166,25 @@ export async function createLandWorktree(input: LandWorktreeInput, deps: LandWor
|
|
|
135
166
|
return r.status === 0 ? r.stdout.trim().toLowerCase() : null;
|
|
136
167
|
};
|
|
137
168
|
const existingHead = branchHead();
|
|
138
|
-
if (existingHead && existingHead !== head) return fail(`local branch ${branch} exists at ${existingHead.slice(0, 12)} (not the PR head) — not overwriting it`);
|
|
139
169
|
const mine = commonDir(git, input.repoCwd);
|
|
140
170
|
if (!mine) return fail('git common directory unreadable');
|
|
171
|
+
const owner = branchOwner(git, input.repoCwd, branch);
|
|
172
|
+
if (owner.reason) return conflict(owner.reason);
|
|
173
|
+
if (owner.path) {
|
|
174
|
+
const path = owner.path;
|
|
175
|
+
// The registry is only a lead: validate the checkout itself immediately before handing it to pr land.
|
|
176
|
+
try {
|
|
177
|
+
if (commonDir(git, path) !== mine) return conflict(`owner ${path} repository identity unreadable or different`, path);
|
|
178
|
+
const current = git(path, ['branch', '--show-current']);
|
|
179
|
+
if (current.status !== 0 || current.stdout.trim() !== branch) return conflict(`owner ${path} branch is ${current.stdout.trim() || 'unknown'} (expected ${branch})`, path);
|
|
180
|
+
const actualHead = localHead(path, git);
|
|
181
|
+
if (actualHead?.toLowerCase() !== head) return conflict(`owner ${path} HEAD ${actualHead?.slice(0, 12) ?? 'unknown'} ≠ reviewed ${head.slice(0, 12)}`, path);
|
|
182
|
+
const status = git(path, ['status', '--porcelain']);
|
|
183
|
+
if (status.status !== 0 || status.stdout.trim()) return conflict(`owner ${path} is ${status.status !== 0 ? 'status unreadable' : `dirty: ${status.stdout.trim().split('\n').slice(0, 3).join(' · ').slice(0, 160)}`}`, path);
|
|
184
|
+
} catch { return conflict(`owner ${path} verification failed`, path); }
|
|
185
|
+
return { cwd: path, created: false, cleanup: () => ({ removed: false, reason: `borrowed owner ${path} — left untouched` }) };
|
|
186
|
+
}
|
|
187
|
+
if (existingHead && existingHead !== head) return fail(`local branch ${branch} exists at ${existingHead.slice(0, 12)} (not the PR head) — not overwriting it`);
|
|
141
188
|
try {
|
|
142
189
|
mkdirSync(dirname(path), { recursive: true });
|
|
143
190
|
// 표지를 먼저 — 등록 전에 죽어도 다음 시도가 «우리 잔여»임을 확인하고 걷을 수 있게.
|
|
@@ -192,7 +239,7 @@ export async function createLandWorktree(input: LandWorktreeInput, deps: LandWor
|
|
|
192
239
|
} catch (error) {
|
|
193
240
|
return abort(`land worktree check threw: ${(error instanceof Error ? error.message : String(error)).slice(-200)}`);
|
|
194
241
|
}
|
|
195
|
-
return { cwd: path, cleanup };
|
|
242
|
+
return { cwd: path, cleanup, created: true };
|
|
196
243
|
}
|
|
197
244
|
|
|
198
245
|
export function observeLandWorktree(data: { card: string; pr: number; head: string; created: boolean | null; removed: boolean | null; branchRemoved?: boolean | null; reason: string },
|
|
@@ -50,6 +50,7 @@ export function liveMoveEffect(input: Pick<LiveMoveHistoryInput, 'executorResult
|
|
|
50
50
|
const result = input.result;
|
|
51
51
|
if (!result) return LIVE_MOVE_EFFECT_UNKNOWN;
|
|
52
52
|
if (!result.executed) return 'none — not executed';
|
|
53
|
+
if (result.kind === 'decision-card') return result.ok ? 'decision card raised' : 'raise failed';
|
|
53
54
|
if (result.kind === 'review' && result.ok) return result.resultPath ? LIVE_REVIEW_EFFECT_PENDING : LIVE_REVIEW_EFFECT_REQUESTED;
|
|
54
55
|
if (result.kind === 'land' && result.ok) return 'land reported ok by the executor (merge state not re-checked here) · merge sha not reported';
|
|
55
56
|
if (result.kind === 'propose-green' && result.ok) return `green proposed · checklist ${checklistId ?? '-'} (checklist untouched)`;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* - `review`: 수확 가능 런의 PR 에 elanous 리뷰를 «한 번» 요청한다 — PR 머리(sha)마다 한 번(카드 `reviewRequests` 이력).
|
|
5
5
|
* 요청만 한다: 결과로 착지·재발사로 잇지 않는다(실행부 `executeNextAction` 의 review→land 사슬을 타지 않는다).
|
|
6
|
-
* TA-REVIEW-RESULT-CAPTURE — 자식 stdout(`--json`)은 결과 파일로 받고,
|
|
6
|
+
* TA-REVIEW-RESULT-CAPTURE — 자식 stdout(`--json`)은 결과 파일로 받고, 자식 완료 뒤 해당 카드만 회수·재판정한다.
|
|
7
7
|
* 회수한 pass 는 그 틱의 판단부·land 관문 입력(`selfReview`)이 될 뿐 land 판정 로직은 그대로다.
|
|
8
8
|
* - `propose-green`: 카드에 green «제안»(`greenProposal`)만 적는다 — 체크리스트는 손대지 않는다.
|
|
9
9
|
* - `propose-land`: 카드·현재 머리의 self review 통과(pass «또는» warn ⊕ must-fix 0 · TA-LAND-WARN-MUSTFIX0)·non-draft 를 확인하고 기존 `pr land` 를 머리 고정으로 한 번 부른다.
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* - 관측: 실행한 수마다 `debug.log('task-agent', 'live-move', {kind, card, ok, detail, live:true})`.
|
|
16
16
|
*/
|
|
17
17
|
import { spawn, spawnSync } from 'node:child_process';
|
|
18
|
-
import { createHash } from 'node:crypto';
|
|
18
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
19
19
|
import { closeSync, mkdirSync, openSync, readFileSync, statSync } from 'node:fs';
|
|
20
20
|
import { dirname, isAbsolute, resolve } from 'node:path';
|
|
21
21
|
import { debug } from '../debug/log.js';
|
|
@@ -25,9 +25,9 @@ import type { DetachedSpawn } from '../cli/tasks-cli.js';
|
|
|
25
25
|
import { LIVE_MOVE_HISTORY_EVENT, taskAgentStatePath, updateTaskCard, type TaskCard } from './task-hand.js';
|
|
26
26
|
import { LIVE_RETRY_MOVES } from './judge.js';
|
|
27
27
|
import { decisionCardGrounds, decisionRaiseArgs, type DecisionCardEvidence, type NextAction } from './actions.js';
|
|
28
|
-
import { reviewResultPath } from './review-result-capture.js';
|
|
28
|
+
import { pendingReviewResults, reviewResultPath, TA_REVIEW_COMPLETION_CARD_ENV, TA_REVIEW_COMPLETION_RESULT_ENV, TA_REVIEW_COMPLETION_STATE_ENV } from './review-result-capture.js';
|
|
29
29
|
import { buildPriorFixContext, PRIOR_FIX_SCOPE_LINE, priorFixBlock, priorFixObservation, type PriorDiffReader, type PriorFixContext } from './review-prior-fix.js';
|
|
30
|
-
import { createLandWorktree, localHead, observeLandWorktree, type LandWorktree, type LandWorktreeFailure, type LandWorktreeInput, type LandWorktreeRemoval } from './land-worktree.js';
|
|
30
|
+
import { createLandWorktree, localBranch, localHead, observeLandWorktree, type LandWorktree, type LandWorktreeFailure, type LandWorktreeInput, type LandWorktreeRemoval } from './land-worktree.js';
|
|
31
31
|
|
|
32
32
|
export const TASK_AGENT_LIVE_MOVES = ['review', 'propose-green', 'propose-land', 'decision-card', ...LIVE_RETRY_MOVES] as const;
|
|
33
33
|
export type TaskAgentLiveMove = typeof TASK_AGENT_LIVE_MOVES[number];
|
|
@@ -89,7 +89,7 @@ export interface LiveMoveDeps {
|
|
|
89
89
|
raiseDecision?: (args: string[]) => Promise<{ status: number; stdout: string; stderr?: string }>;
|
|
90
90
|
log?: (category: string, event: string, data: Record<string, unknown>) => void;
|
|
91
91
|
/** TA-LAND-WORKTREE — cwd HEAD 읽기 · PR 머리 임시 워크트리 만들기(시험 주입 · 기본 = git). */
|
|
92
|
-
landWorktree?: { localHead?: (cwd: string) => string | null; create?: (input: LandWorktreeInput) => Promise<LandWorktree | LandWorktreeFailure> };
|
|
92
|
+
landWorktree?: { localHead?: (cwd: string) => string | null; localBranch?: (cwd: string) => string | null; create?: (input: LandWorktreeInput) => Promise<LandWorktree | LandWorktreeFailure> };
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
/** `executed` = 부작용을 실제로 냈다(리뷰를 띄웠다 · 제안을 적었다). ok 이지만 이미 한 수(같은 머리·이미 제안)면 false. */
|
|
@@ -208,13 +208,18 @@ export function liveReviewArgs(entry: string, configDir: string, pr: number, int
|
|
|
208
208
|
* 파이프가 아니라 파일 — 부모가 먼저 끝나도 자식이 EPIPE 로 죽지 않는다). 없으면 종전과 같다(stdio ignore).
|
|
209
209
|
* `opts` 는 시험 몫(spawn·진입점·우주) — 기본은 실물.
|
|
210
210
|
*/
|
|
211
|
-
export async function defaultRequestReview(pr: number, intent: string, cwd?: string, opts: { spawn?: DetachedSpawn; entry?: string; configDir?: string; resultPath?: string } = {}): Promise<void> {
|
|
211
|
+
export async function defaultRequestReview(pr: number, intent: string, cwd?: string, opts: { spawn?: DetachedSpawn; entry?: string; configDir?: string; resultPath?: string; cardId?: string; statePath?: string } = {}): Promise<void> {
|
|
212
212
|
const { spawnDetachedConfirmed, ELANOUS_CLI_ENTRY } = await import('../cli/tasks-cli.js');
|
|
213
213
|
const { getElanousConfigDirOverride } = await import('../elanous-config-dir.js');
|
|
214
214
|
const { effectiveInstanceRoot } = await import('../instance/resolve.js');
|
|
215
215
|
const configDir = opts.configDir ?? getElanousConfigDirOverride() ?? effectiveInstanceRoot();
|
|
216
216
|
const base = opts.spawn ?? (spawn as unknown as DetachedSpawn);
|
|
217
217
|
const args = liveReviewArgs(opts.entry ?? ELANOUS_CLI_ENTRY, configDir, pr, intent);
|
|
218
|
+
const completionEnv = opts.resultPath && opts.cardId && opts.statePath ? {
|
|
219
|
+
[TA_REVIEW_COMPLETION_CARD_ENV]: opts.cardId,
|
|
220
|
+
[TA_REVIEW_COMPLETION_STATE_ENV]: opts.statePath,
|
|
221
|
+
[TA_REVIEW_COMPLETION_RESULT_ENV]: opts.resultPath,
|
|
222
|
+
} : undefined;
|
|
218
223
|
if (!opts.resultPath) { await spawnDetachedConfirmed(base, process.execPath, args, undefined, cwd); return; }
|
|
219
224
|
let fd: number;
|
|
220
225
|
try {
|
|
@@ -229,7 +234,7 @@ export async function defaultRequestReview(pr: number, intent: string, cwd?: str
|
|
|
229
234
|
const toFile: DetachedSpawn = (command, spawnArgs, options) =>
|
|
230
235
|
(base as unknown as (c: string, a: string[], o: unknown) => ReturnType<DetachedSpawn>)(command, spawnArgs, { ...options, stdio: ['ignore', fd, 'ignore'] });
|
|
231
236
|
try {
|
|
232
|
-
await spawnDetachedConfirmed(toFile, process.execPath, args, undefined, cwd);
|
|
237
|
+
await spawnDetachedConfirmed(toFile, process.execPath, args, undefined, cwd, completionEnv);
|
|
233
238
|
} finally {
|
|
234
239
|
try { closeSync(fd); } catch { /* the child keeps its own copy */ }
|
|
235
240
|
}
|
|
@@ -312,7 +317,7 @@ function observe(deps: LiveMoveDeps, result: LiveMoveResult): LiveMoveResult {
|
|
|
312
317
|
/** `review` — PR 머리마다 한 번. 카드에 먼저 적고(잠금 안) 그다음 띄운다 · 띄우기 실패는 카드에 오류로 남기고 같은 머리는 다시 안 띄운다. */
|
|
313
318
|
export async function executeLiveReview(
|
|
314
319
|
card: TaskCard | undefined, pr: number | undefined,
|
|
315
|
-
ctx: { runId: string | null; stopReason: string; cwd?: string; produced?: { headCommit?: string; branch?: string; prUrl?: string } },
|
|
320
|
+
ctx: { runId: string | null; stopReason: string; cwd?: string; forceNewReview?: boolean; produced?: { headCommit?: string; branch?: string; prUrl?: string } },
|
|
316
321
|
deps: LiveMoveDeps = {},
|
|
317
322
|
): Promise<LiveMoveResult> {
|
|
318
323
|
const kind = 'review' as const;
|
|
@@ -340,12 +345,16 @@ export async function executeLiveReview(
|
|
|
340
345
|
const path = deps.statePath ?? taskAgentStatePath();
|
|
341
346
|
const at = (deps.now ?? (() => new Date()))().toISOString();
|
|
342
347
|
// TA-REVIEW-RESULT-CAPTURE — 머리마다 한 번이라 경로도 머리마다 하나다. 요청 칸에 먼저 적는다(회수가 이 칸을 본다).
|
|
343
|
-
const resultPath =
|
|
348
|
+
const resultPath = ctx.forceNewReview
|
|
349
|
+
? reviewResultPath(path, card.id, pr, head.head).replace(/\.json$/, `-${randomUUID()}.json`)
|
|
350
|
+
: reviewResultPath(path, card.id, pr, head.head);
|
|
344
351
|
const claim: { claimed: boolean; missing: boolean; before?: TaskCard } = { claimed: false, missing: false };
|
|
345
352
|
updateTaskCard(path, card.id, (current) => {
|
|
346
353
|
if (!current) { claim.missing = true; return undefined; }
|
|
347
354
|
// 머리 «이력»으로 본다 — A→B→A 로 돌아와도 A 는 다시 요청하지 않는다.
|
|
348
|
-
if (
|
|
355
|
+
if (ctx.forceNewReview) {
|
|
356
|
+
if (pendingReviewResults(current).some((entry) => entry.pr === pr && entry.head === head!.head)) return undefined;
|
|
357
|
+
} else if ((current.reviewRequests ?? []).some((entry) => entry.pr === pr && entry.head === head!.head)) return undefined;
|
|
349
358
|
claim.claimed = true;
|
|
350
359
|
claim.before = current;
|
|
351
360
|
return { ...current, reviewRequests: [...(current.reviewRequests ?? []), { pr, head: head!.head, at, resultPath }] };
|
|
@@ -360,7 +369,7 @@ export async function executeLiveReview(
|
|
|
360
369
|
try { (deps.log ?? ((c, e, d) => debug.log(c, e, d)))('task-agent', 'review-prior-fix-filled', priorFixObservation(card.id, pr, prior)); } catch { /* fail-soft */ }
|
|
361
370
|
}
|
|
362
371
|
const intent = liveReviewIntent(card, ctx.runId, ctx.stopReason, prior);
|
|
363
|
-
await (deps.requestReview ? deps.requestReview(pr, intent, cwd, { resultPath }) : defaultRequestReview(pr, intent, cwd, { resultPath }));
|
|
372
|
+
await (deps.requestReview ? deps.requestReview(pr, intent, cwd, { resultPath }) : defaultRequestReview(pr, intent, cwd, { resultPath, cardId: card.id, statePath: path }));
|
|
364
373
|
} catch (error) {
|
|
365
374
|
const reason = error instanceof Error ? error.message : String(error);
|
|
366
375
|
updateTaskCard(path, card.id, (current) => current?.reviewRequests?.some((entry) => entry.pr === pr && entry.head === head!.head && entry.at === at)
|
|
@@ -411,6 +420,12 @@ const LAND_BASE_STALE_LINE = /^✗ overlap-evidence: (base-stale-overlap: .*)$/;
|
|
|
411
420
|
const LAND_BASE_STALE_CONSEQUENCE_LINES: readonly RegExp[] = [/^✗ expected-head: overlap merge approval unavailable for the reviewed head$/];
|
|
412
421
|
/** 실패가 아닌 ✗ 줄 — `pr land` 가 «계속합니다»로 넘어간다(판정에 쓰지 않는다). */
|
|
413
422
|
const LAND_NON_FATAL_LINES: readonly RegExp[] = [/^✗ active-run-files: .*계속합니다\.?$/];
|
|
423
|
+
const TSC_RUNTIME_MISSING = /(?:^|\n)(?:sh: )?(?:env: )?(?:[^\s]*\/)?(?:node|bun|tsc): (?:No such file or directory|command not found)/m;
|
|
424
|
+
const TSC_NOT_RUN = /(?:tsc 종료코드 127|tsc 실행 실패 \([^\n]*code=ENOENT)/;
|
|
425
|
+
const TSC_GATE_REFUSED = '✗ typecheck: scripts/ci-typecheck-changed.ts blocked pr land.';
|
|
426
|
+
function missingTscRuntime(text: string): boolean {
|
|
427
|
+
return TSC_NOT_RUN.test(text) && TSC_RUNTIME_MISSING.test(text);
|
|
428
|
+
}
|
|
414
429
|
/** executeLiveLand 쪽 인프라 사유(출력 줄이 아니라 실행 사실). */
|
|
415
430
|
export const LAND_INFRA_EXECUTOR_REASONS = ['land-process-error', 'land-no-result-line'] as const;
|
|
416
431
|
const [LAND_PROCESS_ERROR, LAND_NO_RESULT_LINE] = LAND_INFRA_EXECUTOR_REASONS;
|
|
@@ -430,6 +445,12 @@ export function classifyLandFailure(outcome: { status: number; stdout?: string;
|
|
|
430
445
|
// 결과 줄이 «하나도» 없이 0 아닌 코드로 끝났다 — 관문까지 못 간 채 죽었다(크래시 · 신호).
|
|
431
446
|
if (!marked.length) return outcome.status !== 0 ? { class: 'infra', reason: LAND_NO_RESULT_LINE } : { class: 'judgement', reason: 'no-failure-line' };
|
|
432
447
|
const failures = marked.map((line) => line.replace(/^\[dry-run\] /, '')).filter((line) => line.startsWith('✗') && !LAND_NON_FATAL_LINES.some((re) => re.test(line)));
|
|
448
|
+
// The gate did not measure TypeScript when its interpreter was absent. The
|
|
449
|
+
// exact typecheck refusal alone remains judgement; require rc127/ENOENT and
|
|
450
|
+
// the missing runtime line, with no second failing gate.
|
|
451
|
+
if (failures.length === 1 && failures[0] === TSC_GATE_REFUSED && missingTscRuntime(lines.join('\n'))) {
|
|
452
|
+
return { class: 'infra', reason: 'tsc-runtime-missing' };
|
|
453
|
+
}
|
|
433
454
|
const infra = new Set<string>();
|
|
434
455
|
const unlisted: string[] = [];
|
|
435
456
|
for (const line of failures) {
|
|
@@ -492,7 +513,15 @@ function observeLandWorktreeCreateCapped(deps: LiveMoveDeps, data: { card: strin
|
|
|
492
513
|
|
|
493
514
|
/** 같은 머리에 한 번 더 시도해도 되나 — 앞 시도가 «전부» 끝났고(ok:false) «전부» infra 이고 상한 밑일 때만. */
|
|
494
515
|
function landRetryAllowed(prior: readonly LandAttempt[]): boolean {
|
|
495
|
-
return prior.length > 0 && prior.length < LAND_ATTEMPTS_PER_HEAD_ON_INFRA
|
|
516
|
+
return prior.length > 0 && prior.length < LAND_ATTEMPTS_PER_HEAD_ON_INFRA
|
|
517
|
+
&& prior.every((entry) => entry.ok === false && (entry.class === 'infra' || legacyMissingTscRuntime(entry)));
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/** A previous release classified a missing node interpreter as judgement; only that exact recorded shape may be reopened. */
|
|
521
|
+
function legacyMissingTscRuntime(entry: LandAttempt): boolean {
|
|
522
|
+
return entry.ok === false && entry.class === 'judgement' && entry.reason === TSC_GATE_REFUSED
|
|
523
|
+
&& typeof entry.detail === 'string'
|
|
524
|
+
&& classifyLandFailure({ status: 1, stderr: entry.detail }).reason === 'tsc-runtime-missing';
|
|
496
525
|
}
|
|
497
526
|
|
|
498
527
|
/**
|
|
@@ -560,11 +589,15 @@ export async function executeLiveLand(
|
|
|
560
589
|
const path = deps.statePath ?? taskAgentStatePath();
|
|
561
590
|
const at = (deps.now ?? (() => new Date()))().toISOString();
|
|
562
591
|
const local = (deps.landWorktree?.localHead ?? localHead)(cwd);
|
|
563
|
-
const
|
|
592
|
+
const landKey = `land:${card.id}:${view.head.slice(0, 12)}`;
|
|
593
|
+
const branch = local?.toLowerCase() === view.head.toLowerCase() ? (deps.landWorktree?.localBranch ?? localBranch)(cwd) : null;
|
|
594
|
+
const needsLandWorktree = !!local && (local.toLowerCase() !== view.head.toLowerCase() || branch !== view.branch);
|
|
564
595
|
const createCap = landWorktreeCreateCap();
|
|
565
596
|
let claimed = false;
|
|
566
597
|
let missing = false;
|
|
567
598
|
let cappedReason: string | undefined;
|
|
599
|
+
// This state-file lock is the cross-process card/head land claim. Both completion workers
|
|
600
|
+
// must pass it; an unfinished or successful attempt makes the second worker skip.
|
|
568
601
|
// TA-LAND-RETRY-ON-INFRA — 같은 머리의 앞 시도들(잠금 안에서 읽은 «지금» 카드) · 재시도면 그 근거.
|
|
569
602
|
let prior: LandAttempt[] = [];
|
|
570
603
|
try {
|
|
@@ -590,12 +623,13 @@ export async function executeLiveLand(
|
|
|
590
623
|
if (!claimed) {
|
|
591
624
|
const last = prior.at(-1);
|
|
592
625
|
const why = last ? ` (attempts ${prior.length} · last ${last.ok === false ? `class ${last.class ?? 'unclassified'}${last.reason ? ` ${last.reason}` : ''}` : last.ok === true ? 'ok' : 'unfinished'})` : '';
|
|
593
|
-
return observe(deps, withRepo({ kind, card: card.id, ok: true, executed: false, detail: `already attempted land for #${pr} head ${view.head.slice(0, 12)}${why}` }));
|
|
626
|
+
return observe(deps, withRepo({ kind, card: card.id, ok: true, executed: false, detail: `already attempted land for #${pr} head ${view.head.slice(0, 12)} · ${landKey} claimed — skip${why}` }));
|
|
594
627
|
}
|
|
595
628
|
const attempt = prior.length + 1;
|
|
596
629
|
if (attempt > 1) {
|
|
597
630
|
const last = prior.at(-1)!;
|
|
598
|
-
|
|
631
|
+
const legacy = legacyMissingTscRuntime(last);
|
|
632
|
+
try { (deps.log ?? ((c, e, d) => debug.log(c, e, d)))('task-agent', LAND_RETRY_ON_INFRA_EVENT, { card: card.id, pr, head: view.head, attempt, reason: legacy ? 'tsc-runtime-missing-legacy' : last.reason ?? 'unknown', class: 'infra', decision: legacy ? 'legacy-reclassified-retrying' : 'retrying' }); } catch { /* fail-soft */ }
|
|
599
633
|
}
|
|
600
634
|
if (cycle) landedCycles.add(cycle);
|
|
601
635
|
// TA-LAND-WORKTREE — `pr land --expected-head` 는 «로컬 HEAD = 고정 머리»를 요구한다. cwd 가 다른 머리면(Pod 런 PR · 호스트 checkout)
|
|
@@ -610,6 +644,11 @@ export async function executeLiveLand(
|
|
|
610
644
|
if ('reason' in made) {
|
|
611
645
|
const reason = made.reason;
|
|
612
646
|
observeLandWorktree({ card: card.id, pr, head: view.head, created: made.created, removed: made.removed, reason }, deps.log);
|
|
647
|
+
if (made.kind === 'worktree-conflict') {
|
|
648
|
+
try { (deps.log ?? ((c, e, d) => debug.log(c, e, d)))('task-agent', 'worktree-conflict', {
|
|
649
|
+
card: card.id, pr, head: view.head, branch: view.branch ?? null, ownerPath: made.ownerPath ?? 'unknown', reason,
|
|
650
|
+
}); } catch { /* fail-soft */ }
|
|
651
|
+
}
|
|
613
652
|
let failures: LandWorktreeCreateFailure[] = [];
|
|
614
653
|
let recordFailureError: unknown;
|
|
615
654
|
try {
|
|
@@ -647,7 +686,7 @@ export async function executeLiveLand(
|
|
|
647
686
|
} catch (error) {
|
|
648
687
|
try { (deps.log ?? ((c, e, d) => debug.log(c, e, d, { level: 'warn' })))('task-agent', 'land-worktree-create-failures-clear-failed', { card: card.id, pr, head: view.head, error: String(error).slice(-200) }); } catch { /* fail-soft */ }
|
|
649
688
|
}
|
|
650
|
-
observeLandWorktree({ card: card.id, pr, head: view.head, created:
|
|
689
|
+
observeLandWorktree({ card: card.id, pr, head: view.head, created: made.created ?? null, removed: false, reason: `cwd HEAD ${local.slice(0, 12)} · branch ${branch ?? 'unknown'} differs from PR ${view.head.slice(0, 12)} · ${view.branch ?? 'unknown'}` }, deps.log);
|
|
651
690
|
}
|
|
652
691
|
// 증거 경로는 «원래» cwd 기준으로 고정한다 — 임시 트리로 cwd 를 바꿔도 상대경로가 다른 파일을 가리키지 않게(같은 cwd 면 그대로).
|
|
653
692
|
const overlapEvidence = ctx.overlapEvidence && worktree ? resolve(cwd, ctx.overlapEvidence) : ctx.overlapEvidence;
|
|
@@ -673,7 +712,7 @@ export async function executeLiveLand(
|
|
|
673
712
|
// 성공이든 실패든 걷는다.
|
|
674
713
|
let removal: LandWorktreeRemoval;
|
|
675
714
|
try { removal = worktree.cleanup(); } catch (error) { removal = { removed: false, reason: String(error).slice(-200) }; }
|
|
676
|
-
observeLandWorktree({ card: card.id, pr, head: view.head, created:
|
|
715
|
+
observeLandWorktree({ card: card.id, pr, head: view.head, created: worktree.created ?? null, removed: removal.removed, ...(removal.branchRemoved !== undefined ? { branchRemoved: removal.branchRemoved } : {}),
|
|
677
716
|
reason: removal.reason ?? `land ${ok ? 'ok' : 'failed'} — removed` }, deps.log);
|
|
678
717
|
}
|
|
679
718
|
const landAttempt: LiveMoveResult['landAttempt'] = failure
|
|
@@ -159,7 +159,7 @@ export function remoteLaunchScript(target: ParentHostConfig, args: readonly stri
|
|
|
159
159
|
const elanous = target.elanous ?? DEFAULT_REMOTE_ELANOUS;
|
|
160
160
|
const configDir = target.configDir ?? DEFAULT_REMOTE_CONFIG_DIR;
|
|
161
161
|
const log = remoteParentLogPath(target, env.ELANOUS_RUN_ID ?? 'unknown');
|
|
162
|
-
const envPart = Object.entries(env).filter(([key]) => /^[A-Z_][A-Z0-9_]*$/.test(key)).map(([key, value]) => `${key}=${shellQuote(value)}`).join(' ');
|
|
162
|
+
const envPart = [...Object.entries(env).filter(([key]) => key !== 'ELANOUS_STATE_DIR' && /^[A-Z_][A-Z0-9_]*$/.test(key)).map(([key, value]) => `${key}=${shellQuote(value)}`), `ELANOUS_STATE_DIR=${remotePath(configDir)}`].join(' ');
|
|
163
163
|
const argv = [remotePath(elanous), '--config-dir', remotePath(configDir), ...remoteParentArgs(args, target.podPool).map(shellQuote)].join(' ');
|
|
164
164
|
// 준비가 실패하면 «확실히 안 띄웠다»(not-launched=…)를 찍고 끝낸다 — 그때만 호출부가 로컬로 돌아간다.
|
|
165
165
|
// 띄운 뒤에는 짧게 살아 있는지 보고 pid 를 찍는다(조기 종료면 early-exit — 역시 «안 돈다»가 확실).
|