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
|
@@ -22,6 +22,8 @@ export interface MorningReportOpts {
|
|
|
22
22
|
heatmapImageUrl?: string;
|
|
23
23
|
/** S3 상세 리포트 업로드 여부(기본 true). 테스트는 false 로 부작용 차단. */
|
|
24
24
|
upload?: boolean;
|
|
25
|
+
/** 시장서사 LLM model 명시(A/B). 미지정이면 지금과 같은 기본 provider 경로(providerFor(cfg)). */
|
|
26
|
+
model?: string;
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
/** 밤사이 소스를 종합한 아침 에센셜(텔레그램) 조립. 상세는 S3 링크로. */
|
|
@@ -38,7 +40,7 @@ export async function composeMorningReport(now: Date = new Date(), opts: Morning
|
|
|
38
40
|
catch { uploaded = null; }
|
|
39
41
|
}
|
|
40
42
|
|
|
41
|
-
const narrative = opts.narrate ? await synthesizeEssentialBrief(md) : '';
|
|
43
|
+
const narrative = opts.narrate ? await synthesizeEssentialBrief(md, { model: opts.model }) : '';
|
|
42
44
|
|
|
43
45
|
// 결론-우선 상단 블록(매력도 Δ · 섹터순환 · 캡스톤) — PWA 대시보드 데이터 재사용. fail-soft.
|
|
44
46
|
let heatmap = null, capstone = null;
|
|
@@ -21,12 +21,15 @@ export const RELEASE_STATUS_SPEC: LLMToolSpec = {
|
|
|
21
21
|
+ 'topic=cell: id 칸 하나의 상태·근거와 살핀 판.\n'
|
|
22
22
|
+ 'topic=schedule: 발행 이후 판의 컷·착지·발행·동결 일정.\n'
|
|
23
23
|
+ 'topic=ops: 도는 런·발행·늦은 스케줄·자리·열린 결정의 출처 있는 개관.\n'
|
|
24
|
-
+ 'topic 과 action 을 동시에
|
|
24
|
+
+ 'topic 과 action 을 동시에 쓰면 topic 을 우선하고 action 은 무시한다. topic 은 읽을 대상, action 은 단독 조회의 짧은 요약.\n'
|
|
25
|
+
+ 'topic=features 에서는 owner·status 로 칸을 필터링할 수 있다.\n'
|
|
25
26
|
+ '발행·버전·피처·체크리스트·칸·컷·일정·운영 상태 질문은 Grep·ListDir·파일 검색보다 이 도구를 먼저 부른다 — 그 답은 파일이 아니라 원장에 있다.',
|
|
26
27
|
parameters: { type: 'object', properties: {
|
|
27
28
|
version: { type: 'string', description: '조회할 판. 생략하면 개발 판과 다음 판.' },
|
|
28
29
|
topic: { type: 'string', enum: ['status', 'features', 'cell', 'schedule', 'ops'], description: '읽을 대상(기본 status).' },
|
|
29
30
|
action: { type: 'string', enum: ['schedule', 'blocked', 'landed'], description: '짧은 읽기 요약: 다음 컷 일정·막힌 칸·최근 24시간 병합 PR.' },
|
|
31
|
+
owner: { type: 'string', description: 'topic=features 때 담당 읽기 필터.' },
|
|
32
|
+
status: { type: 'string', description: 'topic=features 때 상태 읽기 필터.' },
|
|
30
33
|
id: { type: 'string', description: 'topic=cell 때 찾을 칸 id(필수).' },
|
|
31
34
|
}, required: [] },
|
|
32
35
|
};
|
|
@@ -122,10 +125,23 @@ function cellView(item: ChecklistItem) {
|
|
|
122
125
|
export function dispatchReleaseStatus(args: Record<string, unknown>, deps: ReleaseToolDeps = {}): { text: string; structured: Record<string, unknown> } {
|
|
123
126
|
const topic = args.topic === undefined ? 'status' : args.topic;
|
|
124
127
|
if (typeof topic !== 'string' || !['status', 'features', 'cell', 'schedule', 'ops'].includes(topic)) throw new Error('topic 은 status, features, cell, schedule, ops 중 하나');
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
+
const ignoredAction = args.topic !== undefined && args.action !== undefined ? args.action : undefined;
|
|
129
|
+
const withIgnoredAction = (result: { text: string; structured: Record<string, unknown> }) => ignoredAction === undefined
|
|
130
|
+
? result
|
|
131
|
+
: { ...result, text: `action=${String(ignoredAction)} 은 무시됨 — topic 과 같이 못 쓴다\n${result.text}` };
|
|
128
132
|
const requested = args.version;
|
|
133
|
+
if (topic === 'features') {
|
|
134
|
+
for (const key of ['owner', 'status'] as const) {
|
|
135
|
+
if (args[key] !== undefined && (typeof args[key] !== 'string' || !args[key].trim())) throw new Error(`${key} 값이 필요합니다`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const filters = topic === 'features' ? {
|
|
139
|
+
...(typeof args.owner === 'string' ? { owner: args.owner } : {}),
|
|
140
|
+
...(typeof args.status === 'string' ? { status: args.status } : {}),
|
|
141
|
+
} : {};
|
|
142
|
+
const hasFeatureFilters = Object.keys(filters).length > 0;
|
|
143
|
+
const featureItemMatches = (item: ChecklistItem) =>
|
|
144
|
+
(!('owner' in filters) || item.owner === filters.owner) && (!('status' in filters) || item.status === filters.status);
|
|
129
145
|
if (requested !== undefined && (typeof requested !== 'string' || !requested.trim())) throw new Error('판 이름이 필요합니다');
|
|
130
146
|
const checklist = deps.checklist ?? listChecklist;
|
|
131
147
|
if (topic === 'ops') {
|
|
@@ -133,10 +149,10 @@ export function dispatchReleaseStatus(args: Record<string, unknown>, deps: Relea
|
|
|
133
149
|
const kinds: ContextFact['kind'][] = ['run', 'release', 'schedule-late', 'seat', 'decision'];
|
|
134
150
|
const facts = answer.facts.filter(fact => kinds.includes(fact.kind));
|
|
135
151
|
debug.log('release.tool', 'status', { action: 'status', topic, version: requested ?? null });
|
|
136
|
-
return { text: facts.length ? facts.map(fact => JSON.stringify(fact)).join('\n') : '운영 개관: 관측된 사실 없음', structured: { facts } };
|
|
152
|
+
return withIgnoredAction({ text: facts.length ? facts.map(fact => JSON.stringify(fact)).join('\n') : '운영 개관: 관측된 사실 없음', structured: { facts } });
|
|
137
153
|
}
|
|
138
154
|
const dev = checklistDevVersion(deps.devVersion?.() ?? devVersion());
|
|
139
|
-
if (args.action !== undefined) {
|
|
155
|
+
if (ignoredAction === undefined && args.action !== undefined) {
|
|
140
156
|
const action = args.action;
|
|
141
157
|
if (typeof action !== 'string' || !['schedule', 'blocked', 'landed'].includes(action)) {
|
|
142
158
|
throw new Error('action 은 schedule, blocked, landed 중 하나여야 합니다');
|
|
@@ -178,12 +194,13 @@ export function dispatchReleaseStatus(args: Record<string, unknown>, deps: Relea
|
|
|
178
194
|
const target = (requested as string | undefined) ?? dev;
|
|
179
195
|
validateVersion(target);
|
|
180
196
|
const data = checklist(target);
|
|
181
|
-
const
|
|
182
|
-
const
|
|
197
|
+
const matchingItems = data.items.filter(featureItemMatches);
|
|
198
|
+
const counts = hasFeatureFilters ? summarizeChecklist({ ...data, items: matchingItems }) : summarizeChecklist(data);
|
|
199
|
+
const items = matchingItems.slice(0, 60).map(({ id, title, status, owner, priority }) =>
|
|
183
200
|
({ id, title: firstLine(title), status, owner: owner ?? null, priority: priority ?? null }));
|
|
184
|
-
const total =
|
|
201
|
+
const total = matchingItems.length;
|
|
185
202
|
result = { text: `${target} 판 피처 ${total}칸 · 초록 ${counts.green} · 노랑 ${counts.yellow} · 빨강 ${counts.red} · 끝 ${counts.done}\n${items.map(item => `${item.id} ${item.title} · ${item.status} · ${item.owner ?? '미배정'}`).join('\n')}${total > 60 ? `\n앞 60칸만 표시(전체 ${total}칸)` : ''}`,
|
|
186
|
-
structured: { version: target, items, counts: { green: counts.green, yellow: counts.yellow, red: counts.red, done: counts.done }, total, truncated: total > 60 } };
|
|
203
|
+
structured: { version: target, items, counts: { green: counts.green, yellow: counts.yellow, red: counts.red, done: counts.done }, total, truncated: total > 60, ...(hasFeatureFilters ? { filters } : {}) } };
|
|
187
204
|
} else if (topic === 'cell') {
|
|
188
205
|
const id = requiredString(args, 'id');
|
|
189
206
|
const searched = requested ? [requested as string] : [...new Set([dev, nextVersion(dev), checklist(dev).released].filter(Boolean))];
|
|
@@ -208,7 +225,7 @@ export function dispatchReleaseStatus(args: Record<string, unknown>, deps: Relea
|
|
|
208
225
|
publishAt: row.publishAt ?? null, freezeFrom: row.freezeFrom ?? null, freezeUntil: row.freezeUntil ?? null })) } };
|
|
209
226
|
}
|
|
210
227
|
debug.log('release.tool', 'status', { action: 'status', topic, version: requested ?? dev });
|
|
211
|
-
return result;
|
|
228
|
+
return withIgnoredAction(result);
|
|
212
229
|
}
|
|
213
230
|
const version = requested ? null : dev;
|
|
214
231
|
const versions = requested ? [requested as string] : [version!, nextVersion(version!)];
|
|
@@ -253,7 +270,7 @@ export function dispatchReleaseStatus(args: Record<string, unknown>, deps: Relea
|
|
|
253
270
|
: published.state === 'unreadable' ? `발행 판 ${published.version} · 발행 기록 못 읽음` : '발행 판 모름';
|
|
254
271
|
const structured = { releases: entries, latestRun: latest, published };
|
|
255
272
|
debug.log('release.tool', 'status', { action: 'status', topic, version: versions.join(',') });
|
|
256
|
-
return { text: `${lines.join('\n\n')}\n${cards.map(formatElanousCard).join('\n')}\n${publishedLine}`, structured };
|
|
273
|
+
return withIgnoredAction({ text: `${lines.join('\n\n')}\n${cards.map(formatElanousCard).join('\n')}\n${publishedLine}`, structured });
|
|
257
274
|
}
|
|
258
275
|
|
|
259
276
|
function requiredString(args: Record<string, unknown>, key: string): string {
|
|
@@ -169,6 +169,22 @@ export function recordInboundTurn(
|
|
|
169
169
|
finally { if (ownDb) db.close(); }
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
+
export interface SurfaceUtteranceQuery {
|
|
173
|
+
from: string;
|
|
174
|
+
to: string;
|
|
175
|
+
surface?: string;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Read every event in a half-open UTC interval without touching recall state. */
|
|
179
|
+
export function querySurfaceUtterances(db: Database, opts: SurfaceUtteranceQuery): SurfaceEventRow[] {
|
|
180
|
+
const where = ["julianday(e.ts) >= julianday(?)", "julianday(e.ts) < julianday(?)", "e.direction = 'inbound'", "e.kind IN ('utterance', 'qna')"];
|
|
181
|
+
const params: string[] = [opts.from, opts.to];
|
|
182
|
+
if (opts.surface) { where.push('e.surface = ?'); params.push(opts.surface); }
|
|
183
|
+
return db.prepare(
|
|
184
|
+
`SELECT e.* FROM events e WHERE ${where.join(' AND ')} ORDER BY julianday(e.ts) ASC, e.id ASC`,
|
|
185
|
+
).all(...params) as SurfaceEventRow[];
|
|
186
|
+
}
|
|
187
|
+
|
|
172
188
|
export interface QueryEventsOpts {
|
|
173
189
|
query?: string; // FTS MATCH (없으면 최근순 전체)
|
|
174
190
|
surface?: string;
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
// Resolution order:
|
|
16
16
|
// 1. Programmatic override set via `setElanousConfigDir(dir)` (incl.
|
|
17
17
|
// from the `--config-dir` CLI flag).
|
|
18
|
-
// 2. State-dir via `effectiveInstanceRoot()` (
|
|
19
|
-
// 3. `~/.elanous` for
|
|
18
|
+
// 2. State-dir via `effectiveInstanceRoot()` (explicit --test or parent stamp).
|
|
19
|
+
// 3. `~/.elanous` for both installed and source CLIs without an explicit test root.
|
|
20
20
|
|
|
21
21
|
import { homedir } from 'node:os';
|
|
22
22
|
import { resolve } from 'node:path';
|
package/src/fleet/host-doctor.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
2
|
import { debug } from '../debug/log.js';
|
|
3
|
+
import type { ResourceView } from '../control-plane/ledger.js';
|
|
4
|
+
import type { MachineLedger, MachineFleetIntent } from '../machines/machine-ledger.js';
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* 호스트 닥터 — 첫 조각(FLEET-DOCTOR-1 · 읽기 전용).
|
|
@@ -295,6 +297,66 @@ export interface HostDoctorOptions {
|
|
|
295
297
|
remote?: HostDoctorRemote;
|
|
296
298
|
expected?: HostDoctorExpected;
|
|
297
299
|
now?: () => Date;
|
|
300
|
+
/** HQ observations: null means the source could not be read; [] means a successful empty reading. */
|
|
301
|
+
membership?: {
|
|
302
|
+
intent: MachineLedger | null;
|
|
303
|
+
machines: readonly ResourceView[] | null;
|
|
304
|
+
parentHosts: readonly { host?: string; podPool?: string; approvalCapacity?: number }[] | null;
|
|
305
|
+
intentError?: string;
|
|
306
|
+
machinesError?: string;
|
|
307
|
+
parentHostsError?: string;
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function membershipCheck(id: string, status: HostDoctorCheckStatus, observed: string, expected: string): HostDoctorCheckResult {
|
|
312
|
+
return { id, status, observed, expected, fixKind: 'human', fix: 'compare Nexus machine registration, machines.yaml intent, and taskAgent.parentHost' };
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** Pure HQ comparison. A failed query is null (unmeasured); a successful empty result is a real missing host. */
|
|
316
|
+
export function checkHostMembership(host: string, platform: HostDoctorPlatform | null, membership: NonNullable<HostDoctorOptions['membership']>): HostDoctorCheckResult[] {
|
|
317
|
+
const intent: MachineFleetIntent | undefined = membership.intent?.machines.find((row) => row.fleet?.sshName === host)?.fleet;
|
|
318
|
+
const registryExpected = intent ? `live machine ${host} · role ${intent.role}` : `machines.yaml fleet intent for ${host}`;
|
|
319
|
+
let registry: HostDoctorCheckResult;
|
|
320
|
+
if (membership.intent === null || membership.machines === null) {
|
|
321
|
+
registry = membershipCheck('registry.member', 'unmeasured', membership.intent === null
|
|
322
|
+
? `intent ledger unavailable (repository checkout required)${membership.intentError ? `: ${membership.intentError}` : ''}`
|
|
323
|
+
: `Nexus machine query unavailable${membership.machinesError ? `: ${membership.machinesError}` : ''}`, registryExpected);
|
|
324
|
+
} else if (!intent) {
|
|
325
|
+
registry = membershipCheck('registry.member', 'fail', 'host absent from fleet intent', registryExpected);
|
|
326
|
+
} else {
|
|
327
|
+
// `machine` is the Nexus identity; `name` is a display label. A missing/expired record
|
|
328
|
+
// is a measured failure even when the remote platform probe did not run.
|
|
329
|
+
const rows = membership.machines.filter((row) => row.kind === 'machine' && row.machine === host && !row.expired);
|
|
330
|
+
const row = rows[0];
|
|
331
|
+
const role = row?.attrs.role;
|
|
332
|
+
if (!row) registry = membershipCheck('registry.member', 'fail', 'no live Nexus machine record', registryExpected);
|
|
333
|
+
else if (rows.length !== 1) registry = membershipCheck('registry.member', 'fail', `${rows.length} live machine records`, registryExpected);
|
|
334
|
+
else if (typeof role !== 'string') registry = membershipCheck('registry.member', 'unmeasured', 'machine attrs.role unavailable', registryExpected);
|
|
335
|
+
else if (role !== intent.role) registry = membershipCheck('registry.member', 'fail', `machine role ${role}`, registryExpected);
|
|
336
|
+
// Existence and role have been measured above; a pass still needs the platform probe.
|
|
337
|
+
else if (platform === null) registry = membershipCheck('registry.member', 'unmeasured', 'host platform unavailable', `intent platform ${intent.platform}`);
|
|
338
|
+
else if (platform !== intent.platform) registry = membershipCheck('registry.member', 'fail', `host platform ${platform}`, `intent platform ${intent.platform}`);
|
|
339
|
+
else registry = membershipCheck('registry.member', 'pass', `live machine ${host} · role ${role}`, registryExpected);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const parentExpected = intent ? `taskAgent.parentHost ${host} · ${intent.podPool} · cap ${intent.cap}` : `machines.yaml fleet intent for ${host}`;
|
|
343
|
+
let parent: HostDoctorCheckResult;
|
|
344
|
+
if (membership.intent === null || membership.parentHosts === null) {
|
|
345
|
+
parent = membershipCheck('parentHost.member', 'unmeasured', membership.intent === null
|
|
346
|
+
? `intent ledger unavailable (repository checkout required)${membership.intentError ? `: ${membership.intentError}` : ''}`
|
|
347
|
+
: `taskAgent.parentHost unavailable${membership.parentHostsError ? `: ${membership.parentHostsError}` : ''}`, parentExpected);
|
|
348
|
+
} else if (!intent) {
|
|
349
|
+
parent = membershipCheck('parentHost.member', 'fail', 'host absent from fleet intent', parentExpected);
|
|
350
|
+
} else {
|
|
351
|
+
const rows = membership.parentHosts.filter((row) => row.host === host);
|
|
352
|
+
const row = rows[0];
|
|
353
|
+
if (intent.cap === 0) parent = membershipCheck('parentHost.member', rows.length === 0 ? 'pass' : 'fail', rows.length === 0 ? 'disabled in both intent and parent config' : 'disabled host still in parent config', 'host absent from taskAgent.parentHost (cap 0)');
|
|
354
|
+
else if (!row) parent = membershipCheck('parentHost.member', 'fail', 'host absent from taskAgent.parentHost', parentExpected);
|
|
355
|
+
else if (rows.length !== 1 || row.podPool !== intent.podPool || row.approvalCapacity !== intent.cap) {
|
|
356
|
+
parent = membershipCheck('parentHost.member', 'fail', `host entries ${rows.length} · pool ${row.podPool ?? '(missing)'} · cap ${row.approvalCapacity ?? '(missing)'}`, parentExpected);
|
|
357
|
+
} else parent = membershipCheck('parentHost.member', 'pass', `host ${host} · pool ${row.podPool} · cap ${row.approvalCapacity}`, parentExpected);
|
|
358
|
+
}
|
|
359
|
+
return [registry, parent];
|
|
298
360
|
}
|
|
299
361
|
|
|
300
362
|
/** 원격 `uname -s` ⊕ `/proc/version` 의 microsoft 로 darwin|linux|wsl. 못 재면 null. */
|
|
@@ -344,9 +406,14 @@ export function runHostDoctor(options: HostDoctorOptions): HostDoctorReport {
|
|
|
344
406
|
const platform = reached ? detectHostDoctorPlatform(remote, host) : null;
|
|
345
407
|
const arch = reached ? detectHostDoctorArch(remote, host) : null;
|
|
346
408
|
const specs = HOST_DOCTOR_CHECKS.filter((spec) => spec.roles.includes(role));
|
|
347
|
-
const
|
|
409
|
+
const remoteChecks = reached
|
|
348
410
|
? specs.map((spec) => runCheck(spec, { host, remote, role, platform, arch, expected }))
|
|
349
411
|
: specs.map((spec) => unmeasuredBecauseUnreached(spec, { platform, arch, expected }));
|
|
412
|
+
const checks = [...remoteChecks, ...(role === 'worker' && options.membership ? checkHostMembership(host, platform, options.membership) : [])];
|
|
413
|
+
const mismatch = checks.filter((check) => (check.id === 'registry.member' || check.id === 'parentHost.member') && check.status === 'fail');
|
|
414
|
+
if (mismatch.length) debug.log('fleet.doctor', 'membership-mismatch', { host, checks: mismatch.map((check) => ({ id: check.id, observed: check.observed, expected: check.expected })) });
|
|
415
|
+
const membershipUnmeasured = checks.filter((check) => (check.id === 'registry.member' || check.id === 'parentHost.member') && check.status === 'unmeasured');
|
|
416
|
+
if (membershipUnmeasured.length) debug.log('fleet.doctor', 'membership-unmeasured', { host, checks: membershipUnmeasured.map((check) => ({ id: check.id, observed: check.observed })) });
|
|
350
417
|
const fail = checks.filter((check) => check.status === 'fail').map((check) => check.id);
|
|
351
418
|
const unmeasured = checks.filter((check) => check.status === 'unmeasured').map((check) => check.id);
|
|
352
419
|
const measuredAt = (options.now ?? (() => new Date()))().toISOString();
|
|
@@ -365,9 +432,13 @@ export function runHostDoctor(options: HostDoctorOptions): HostDoctorReport {
|
|
|
365
432
|
};
|
|
366
433
|
}
|
|
367
434
|
|
|
368
|
-
/** 사람 출력 한 줄씩 — «✓|✗|→ <id> <한 줄>»
|
|
435
|
+
/** 사람 출력 한 줄씩 — 기본은 «✓|✗|→ <id> <한 줄>»(node-setup.sh --check),
|
|
436
|
+
* membership 미측정은 눈에 보이는 «⚠ <id> unmeasured: <원인>»을 쓴다. */
|
|
369
437
|
export function formatHostDoctorLines(report: HostDoctorReport): string[] {
|
|
370
438
|
return report.checks.map((check) => {
|
|
439
|
+
if ((check.id === 'registry.member' || check.id === 'parentHost.member') && check.status === 'unmeasured') {
|
|
440
|
+
return `⚠ ${check.id} unmeasured: ${check.observed}`;
|
|
441
|
+
}
|
|
371
442
|
const mark = check.status === 'pass' ? '✓' : check.status === 'fail' ? '✗' : '→';
|
|
372
443
|
const detail = check.status === 'fail' ? `${check.observed} (expected: ${check.expected})` : check.observed;
|
|
373
444
|
return `${mark} ${check.id} ${detail}`;
|
package/src/github/pr-labels.ts
CHANGED
|
@@ -3,6 +3,12 @@ export const IDEA_APPROVAL_LIMIT = 10;
|
|
|
3
3
|
export const IDEA_STALE_DAYS = 7;
|
|
4
4
|
export const STALLED_DRAFT_HOURS = 24;
|
|
5
5
|
export const CLAIM_IDLE_HOURS = 6;
|
|
6
|
+
export const PR_LABEL_NAMES = {
|
|
7
|
+
running: 'elanous:running',
|
|
8
|
+
stalled: 'elanous:stalled',
|
|
9
|
+
superseded: 'elanous:superseded',
|
|
10
|
+
needsRebase: 'elanous:needs-rebase',
|
|
11
|
+
} as const;
|
|
6
12
|
|
|
7
13
|
export type PrLabelAxis = 'state' | 'addon' | 'origin' | 'risk';
|
|
8
14
|
export type PrLabelApplier = 'harness' | 'launcher' | 'sweeper' | 'session' | 'human';
|
|
@@ -24,14 +30,14 @@ export interface PrLabelDefinition {
|
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
export const PR_LABELS = [
|
|
27
|
-
{ name:
|
|
28
|
-
{ name:
|
|
33
|
+
{ name: PR_LABEL_NAMES.running, axis: 'state', color: '1D76DB', description: 'Harness run or session is actively handling a draft PR', appliedBy: ['harness', 'session'], sweep: { action: 'mark-stalled', when: 'run-ended' } },
|
|
34
|
+
{ name: PR_LABEL_NAMES.stalled, axis: 'state', color: 'D93F0B', description: 'Incomplete draft requiring human attention', appliedBy: ['harness', 'sweeper'], sweep: { action: 'close', afterHours: STALLED_DRAFT_HOURS } },
|
|
29
35
|
{ name: 'elanous:idea-approval', axis: 'state', color: '0E8A16', description: 'Completed, ready PR awaiting human approval', appliedBy: ['launcher'], sweep: { action: 'none' } },
|
|
30
|
-
{ name:
|
|
36
|
+
{ name: PR_LABEL_NAMES.superseded, axis: 'state', color: '6A737D', description: 'Another PR has already merged the same work', appliedBy: ['sweeper', 'session'], sweep: { action: 'close' } },
|
|
31
37
|
{ name: 'elanous:keep', axis: 'addon', color: 'BFDADC', description: 'Human requested that the PR be kept open', appliedBy: ['human'], sweep: { action: 'exclude' } },
|
|
32
38
|
{ name: 'elanous:release-hold', axis: 'addon', color: 'D4C5F9', description: 'Exclude from release notes until verified in production', appliedBy: ['launcher', 'session', 'human'], sweep: { action: 'none' } },
|
|
33
39
|
{ name: 'elanous:idea-stale', axis: 'addon', color: 'FBCA04', description: 'Approval pending beyond the notification threshold', appliedBy: ['sweeper'], sweep: { action: 'notify', afterDays: IDEA_STALE_DAYS } },
|
|
34
|
-
{ name:
|
|
40
|
+
{ name: PR_LABEL_NAMES.needsRebase, axis: 'addon', color: 'E99695', description: 'Approval blocked by conflicts or failing checks', appliedBy: ['sweeper', 'session'], sweep: { action: 'block-approval' } },
|
|
35
41
|
{ name: 'elanous:bench', axis: 'addon', color: 'C5DEF5', description: 'Bench arm output: never landed or swept; the owner harvests the winner onto main', appliedBy: ['harness'], sweep: { action: 'none' } },
|
|
36
42
|
{ name: 'elanous:harvestable', axis: 'addon', color: 'FBCA04', description: 'Pod terminal marked this PR for harvest', appliedBy: ['harness'], sweep: { action: 'close', afterHours: STALLED_DRAFT_HOURS } },
|
|
37
43
|
{ name: 'elanous:from-harness', axis: 'origin', color: 'C5DEF5', description: 'Launched from a harness goal', appliedBy: ['harness'], sweep: { action: 'none' } },
|
|
@@ -26,6 +26,44 @@ import type { BashNode, NodeExecContext, WorkflowDeps } from '../workflow-runtim
|
|
|
26
26
|
import { releaseResumeAcceptedRegressions, releaseResumeGateExecutor, releaseResumeWaiver, type AcceptedRegression, type GateExecutorOverride } from '../../scripts/release-loop/resume.js';
|
|
27
27
|
|
|
28
28
|
type BashRun = WorkflowDeps['runBash'];
|
|
29
|
+
const DEFAULT_BASH_TIMEOUT_MS = 5 * 60 * 1_000;
|
|
30
|
+
// Independent of graph YAML and recipe snapshots: an already-wrong saved binding must fail closed.
|
|
31
|
+
const RELEASE_NODE_COMMANDS: Readonly<Record<string, { recipe: string; command?: string }>> = {
|
|
32
|
+
'version-release': { recipe: 'cmd:version-release', command: 'bun scripts/release-loop/version-node.ts release --json' },
|
|
33
|
+
cutoff: { recipe: 'cmd:cutoff', command: 'bun scripts/release-loop/node-verdict.ts cutoff' },
|
|
34
|
+
'checklist-gate': { recipe: 'cmd:checklist-gate', command: 'bun scripts/release-loop/checklist-gate-node.ts' },
|
|
35
|
+
prefetch: { recipe: 'cmd:docs', command: 'bun scripts/release-loop/docs-node.ts --json' },
|
|
36
|
+
gate: { recipe: 'cmd:gate', command: 'bun scripts/release-loop/gate-node.ts --json' },
|
|
37
|
+
'mac-smoke': { recipe: 'cmd:mac-smoke', command: 'bun scripts/release-loop/mac-smoke-node.ts' },
|
|
38
|
+
'export-check': { recipe: 'cmd:export-check', command: 'bun scripts/release-loop/export-check-node.ts' },
|
|
39
|
+
pwa: { recipe: 'cmd:pwa', command: 'bun scripts/release-loop/node-verdict.ts pwa' },
|
|
40
|
+
prepare: { recipe: 'cmd:prepare', command: 'bun scripts/release-loop/prepare-node.ts' },
|
|
41
|
+
upgrade: { recipe: 'cmd:upgrade', command: 'bun scripts/release-loop/upgrade-node.ts' },
|
|
42
|
+
tui: { recipe: 'cmd:docs', command: 'bun scripts/release-loop/docs-node.ts --json' },
|
|
43
|
+
docs: { recipe: 'cmd:docs', command: 'bun scripts/release-loop/docs-node.ts --json' },
|
|
44
|
+
'known-issues': { recipe: 'cmd:known-issues', command: 'bun scripts/release-loop/known-issues-node.ts' },
|
|
45
|
+
'notes-check': { recipe: 'cmd:notes-check', command: 'bun scripts/announce-loop/check-node.ts --json' },
|
|
46
|
+
'auto-approve': { recipe: 'cmd:auto-approve', command: 'bun scripts/release-loop/auto-approve-node.ts' },
|
|
47
|
+
'approve-publish': { recipe: 'approval:publish' },
|
|
48
|
+
publish: { recipe: 'cmd:publish-command', command: 'bun scripts/release-loop/publish-node.ts' },
|
|
49
|
+
'npm-publish': { recipe: 'cmd:npm-publish', command: 'bun scripts/release-loop/npm-publish-node.ts' },
|
|
50
|
+
'docs-land': { recipe: 'cmd:docs-land', command: 'bun scripts/release-loop/docs-land-node.ts' },
|
|
51
|
+
verify: { recipe: 'cmd:verify', command: 'bun scripts/release-loop/verify-node.ts' },
|
|
52
|
+
'vault-note': { recipe: 'cmd:vault-note', command: 'bun scripts/release-loop/vault-note-node.ts' },
|
|
53
|
+
'release-story': { recipe: 'cmd:release-story', command: 'bun scripts/release-story/draft.ts --graph' },
|
|
54
|
+
'ops-upgrade': { recipe: 'cmd:ops-upgrade', command: 'bun scripts/release-loop/ops-upgrade-node.ts' },
|
|
55
|
+
'version-dev-bump': { recipe: 'cmd:version-dev-bump', command: 'bun scripts/release-loop/version-node.ts dev-bump --json' },
|
|
56
|
+
done: { recipe: 'none' },
|
|
57
|
+
failed: { recipe: 'none' },
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
function assertReleaseCommandBinding(node: GraphNodeSpec, command: string | undefined, terminal: boolean): void {
|
|
61
|
+
const expected = RELEASE_NODE_COMMANDS[node.nodeId];
|
|
62
|
+
if (!expected || node.recipe !== expected.recipe || command !== expected.command ||
|
|
63
|
+
terminal !== (node.nodeId === 'done' || node.nodeId === 'failed')) {
|
|
64
|
+
throw new Error(`release node dispatch mismatch before execution: ${node.nodeId}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
29
67
|
|
|
30
68
|
export interface GraphRunState {
|
|
31
69
|
graphId: string;
|
|
@@ -53,7 +91,9 @@ export interface GraphRunState {
|
|
|
53
91
|
graphPath?: string;
|
|
54
92
|
variant?: { overlayId: string; goal: string; plan: GraphVariantPlan };
|
|
55
93
|
parentRunId?: string;
|
|
56
|
-
|
|
94
|
+
/** The entry chosen by a parent call; absent for existing single-entry runs. */
|
|
95
|
+
entryNodeId?: string;
|
|
96
|
+
unitCalls?: Array<{ node: string; unit: string; version: number; hash: string; childRunId: string; exit: string; steps: number; entry?: string }>;
|
|
57
97
|
growth?: GraphGrowth[];
|
|
58
98
|
growthRejections?: Array<{ from: string; outcome: string; reason: string }>;
|
|
59
99
|
growthPark?: { from: string; outcome: string; reason: string; decisionId: string; decisionRef: string; growth: GraphGrowth; command?: GrowthCommand; approval?: string };
|
|
@@ -83,7 +123,7 @@ export interface GraphRunOptions {
|
|
|
83
123
|
releaseGateExecutor?: { executor: string; host?: string };
|
|
84
124
|
useCurrentGraph?: boolean;
|
|
85
125
|
/** Internal unit invocation context, propagated only by runGraph. */
|
|
86
|
-
unitContext?: { parentRunId: string; ancestors: readonly string[]; remainingSteps: number };
|
|
126
|
+
unitContext?: { parentRunId: string; ancestors: readonly string[]; remainingSteps: number; entryNodeId?: string };
|
|
87
127
|
/**
|
|
88
128
|
* RELEASE-LEDGER-UNIVERSE — stamp every cmd: child with this run's ledger root (`ELANOUS_STATE_DIR`).
|
|
89
129
|
* Without it a child launched from a non-leader tree (`bun scripts/...` with cwd = that tree) re-resolves
|
|
@@ -403,7 +443,14 @@ function nextNode(edges: readonly GraphEdgeSpec[], nodeId: string, exitOutcome:
|
|
|
403
443
|
|
|
404
444
|
type ResolvedUnit = { path: string; template: GraphTemplateSpec; hash: string };
|
|
405
445
|
|
|
406
|
-
|
|
446
|
+
export class UnitNotFoundError extends Error {
|
|
447
|
+
constructor(call: string) {
|
|
448
|
+
super(`unit not found: ${call}`);
|
|
449
|
+
this.name = 'UnitNotFoundError';
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
export function resolveUnit(call: string, callerPath: string): ResolvedUnit {
|
|
407
454
|
const [id, requested] = call.split('@');
|
|
408
455
|
const repo = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
409
456
|
const directories = [join(dirname(callerPath), 'units'), dirname(callerPath), join(repo, 'graphs', 'units'), join(getElanousConfigDir(), 'graphs', 'units')];
|
|
@@ -423,16 +470,24 @@ function resolveUnit(call: string, callerPath: string): ResolvedUnit {
|
|
|
423
470
|
if (matches.length > 1) throw new Error(`ambiguous unit: ${call}`);
|
|
424
471
|
if (matches.length) return matches[0]!;
|
|
425
472
|
}
|
|
426
|
-
throw new
|
|
473
|
+
throw new UnitNotFoundError(call);
|
|
427
474
|
}
|
|
428
475
|
|
|
429
|
-
function validateUnitCalls(graph: GraphTemplateSpec, path: string, ancestors: readonly string[] = []): Map<string, ResolvedUnit> {
|
|
476
|
+
export function validateUnitCalls(graph: GraphTemplateSpec, path: string, ancestors: readonly string[] = [], onMissing?: (call: string) => void): Map<string, ResolvedUnit> {
|
|
430
477
|
if (ancestors.includes(graph.graphId)) throw new Error(`unit cycle: ${[...ancestors, graph.graphId].join(' -> ')}`);
|
|
431
478
|
if (ancestors.length > 3) throw new Error('unit nesting exceeds depth 3');
|
|
432
479
|
const result = new Map<string, ResolvedUnit>();
|
|
433
480
|
for (const node of graph.nodes) {
|
|
434
481
|
if (node.kind !== 'subgraph' || node.call === undefined) continue;
|
|
435
|
-
|
|
482
|
+
let unit: ResolvedUnit;
|
|
483
|
+
try { unit = resolveUnit(node.call!, path); } catch (error) {
|
|
484
|
+
if (error instanceof UnitNotFoundError && onMissing) { onMissing(node.call!); continue; }
|
|
485
|
+
throw error;
|
|
486
|
+
}
|
|
487
|
+
const selectedEntry = node.callEntry ?? unit.template.entryNode;
|
|
488
|
+
if (!(unit.template.unit?.entries ?? [unit.template.entryNode]).includes(selectedEntry)) {
|
|
489
|
+
throw new Error(`unit entry mismatch: ${node.nodeId} -> ${selectedEntry}`);
|
|
490
|
+
}
|
|
436
491
|
const contract = node.contract!;
|
|
437
492
|
const edge = graph.edges.find(e => e.from === node.nodeId);
|
|
438
493
|
if (!unit.template.unit || contract.inputs.length !== unit.template.unit.inputs.length ||
|
|
@@ -444,7 +499,7 @@ function validateUnitCalls(graph: GraphTemplateSpec, path: string, ancestors: re
|
|
|
444
499
|
debug.log('graph.unit', 'contract-mismatch', { graphId: graph.graphId, nodeId: node.nodeId, call: node.call });
|
|
445
500
|
throw new Error(`unit contract mismatch: ${node.nodeId} -> ${node.call}`);
|
|
446
501
|
}
|
|
447
|
-
validateUnitCalls(unit.template, unit.path, [...ancestors, graph.graphId]);
|
|
502
|
+
validateUnitCalls(unit.template, unit.path, [...ancestors, graph.graphId], onMissing);
|
|
448
503
|
result.set(node.nodeId, unit);
|
|
449
504
|
}
|
|
450
505
|
return result;
|
|
@@ -491,6 +546,13 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
491
546
|
if (variant && !variant.ok) throw new Error(`graph variant rejected: ${JSON.stringify(variant.rejections)}`);
|
|
492
547
|
if (variant?.ok) graph = variant.template;
|
|
493
548
|
const units = validateUnitCalls(graph, path, options.unitContext?.ancestors);
|
|
549
|
+
const unitEntryNode = options.unitContext?.entryNodeId ?? saved?.entryNodeId ?? graph.entryNode;
|
|
550
|
+
if (saved?.entryNodeId !== undefined && options.unitContext?.entryNodeId !== undefined && saved.entryNodeId !== options.unitContext.entryNodeId) {
|
|
551
|
+
throw new Error('unit entry changed on resume');
|
|
552
|
+
}
|
|
553
|
+
if ((options.unitContext || saved?.entryNodeId !== undefined) && !(graph.unit?.entries ?? [graph.entryNode]).includes(unitEntryNode)) {
|
|
554
|
+
throw new Error(`unit entry mismatch: ${unitEntryNode}`);
|
|
555
|
+
}
|
|
494
556
|
const variantSource = variant?.ok ? stringifyYaml({ ...document,
|
|
495
557
|
nodes: graph.nodes.map((node, index) => ({
|
|
496
558
|
...((document.nodes as Record<string, unknown>[])[index] ?? {}), node_id: node.nodeId, kind: node.kind,
|
|
@@ -536,6 +598,14 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
536
598
|
if (!resolved.command && !resolved.prompt && !resolved.approval) throw new Error(`unknown command recipe for ${node.nodeId}: ${node.recipe}`);
|
|
537
599
|
}
|
|
538
600
|
const graphId = safeSegment(graph.graphId);
|
|
601
|
+
const isReleaseLoop = graphId === 'release-loop';
|
|
602
|
+
// A copied or saved release graph is still a release graph. Its path cannot grant or revoke
|
|
603
|
+
// permission to dispatch a command; only the graph identity and this independent table can.
|
|
604
|
+
const enforceReleaseCommandBindings = isReleaseLoop;
|
|
605
|
+
if (enforceReleaseCommandBindings) {
|
|
606
|
+
for (const node of graph.nodes) assertReleaseCommandBinding(node,
|
|
607
|
+
resolveNodeRecipe(node, recipes, catalogRoles).command?.command, graph.terminalNodes.includes(node.nodeId));
|
|
608
|
+
}
|
|
539
609
|
if (saved && graphId !== saved.graphId) {
|
|
540
610
|
if (options.fromNodeId && graphMode === 'current') throw new Error('graph or recipes changed since failed run');
|
|
541
611
|
throw new Error(`run identity mismatch: ${saved.graphId}/${saved.runId}`);
|
|
@@ -561,7 +631,9 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
561
631
|
: { graphId, runId, startedAt: new Date().toISOString(), ...runnerOwner, status: 'running', path: [], nodes: [], sourceHash: approvalSourceHash,
|
|
562
632
|
graphSnapshot: { graphSha: createHash('sha256').update(variantSource).digest('hex'), recipesSha: createHash('sha256').update(recipeSource).digest('hex') },
|
|
563
633
|
graphPath: resolve(path), ...(variant?.ok ? { variant: { overlayId: variant.overlay.overlayId, ...options.variant! } } : {}),
|
|
564
|
-
...(options.input === undefined ? {} : { input: options.input }),
|
|
634
|
+
...(options.input === undefined ? {} : { input: options.input }),
|
|
635
|
+
...(options.unitContext?.entryNodeId === undefined ? {} : { entryNodeId: options.unitContext.entryNodeId }),
|
|
636
|
+
executed: 0, dryRun: options.dryRun === true, statePath };
|
|
565
637
|
if (options.resumeRunId && graphMode === 'snapshot' && state.sourceHash && state.sourceHash !== approvalSourceHash) {
|
|
566
638
|
throw new Error('graph snapshot changed since run started');
|
|
567
639
|
}
|
|
@@ -593,7 +665,7 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
593
665
|
const reported = lastJsonObject(prior.output)?.outcome;
|
|
594
666
|
const next = nextNode(graph.edges, prior.nodeId, prior.ok ? 'ok' : 'fail', typeof reported === 'string' ? reported : undefined);
|
|
595
667
|
if (next !== recorded.nodeId) throw new Error('saved run path does not follow graph edges');
|
|
596
|
-
} else if (recorded.nodeId !==
|
|
668
|
+
} else if (recorded.nodeId !== unitEntryNode) throw new Error('saved run path does not start at entry node');
|
|
597
669
|
if (i < from && !recorded.ok) throw new Error('cannot preserve a failed predecessor when resuming');
|
|
598
670
|
const approved = resolveNodeRecipe(spec, recipes, catalogRoles).approval;
|
|
599
671
|
if (approved && i >= from) throw new Error('cannot restart across an approval decision');
|
|
@@ -735,7 +807,7 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
735
807
|
if (options.resumeRunId && !options.fromNodeId) {
|
|
736
808
|
const previousStatus = state.status;
|
|
737
809
|
Object.assign(state, runnerOwner);
|
|
738
|
-
state.resume = { from: state.pending?.nodeId ?? state.path.at(-1) ??
|
|
810
|
+
state.resume = { from: state.pending?.nodeId ?? state.path.at(-1) ?? unitEntryNode, at: new Date().toISOString(), previousStatus, graph: graphMode };
|
|
739
811
|
persistOwnedRun(state, saved!);
|
|
740
812
|
}
|
|
741
813
|
if (options.resumeRunId) debug.log('graph.runs', 'resume', { graphId, runId, from: state.resume?.from, graph: graphMode });
|
|
@@ -750,7 +822,7 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
750
822
|
: baseLog;
|
|
751
823
|
const visits = new Map<string, number>();
|
|
752
824
|
for (const nodeId of state.path) visits.set(nodeId, (visits.get(nodeId) ?? 0) + 1);
|
|
753
|
-
let current: string | undefined = options.fromNodeId ?? state.pending?.nodeId ?? state.path.at(-1) ??
|
|
825
|
+
let current: string | undefined = options.fromNodeId ?? state.pending?.nodeId ?? state.path.at(-1) ?? unitEntryNode;
|
|
754
826
|
if (!options.resumeRunId) {
|
|
755
827
|
mkdirSync(dirname(statePath), { recursive: true });
|
|
756
828
|
if (readdirSync(dirname(statePath)).some((file) => file.startsWith(`${runId}.json.`) && file.endsWith('.decision.json'))) {
|
|
@@ -787,6 +859,7 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
787
859
|
}
|
|
788
860
|
const resolved = resolveNodeRecipe(node, recipes, catalogRoles);
|
|
789
861
|
const command = resolved.command;
|
|
862
|
+
if (enforceReleaseCommandBindings) assertReleaseCommandBinding(node, command?.command, graph.terminalNodes.includes(current));
|
|
790
863
|
const prompt = resolved.prompt;
|
|
791
864
|
const approval = resolved.approval ? { approval: approvalMessage(resolved.approval.approval, state.input) } : undefined;
|
|
792
865
|
if (resumingPending && (!approval || approval.approval !== state.pending?.message)) {
|
|
@@ -836,7 +909,13 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
836
909
|
const redactCredentials = (text: string) => Object.values(credentials).filter(value => value.length > 0)
|
|
837
910
|
.reduce((safe, value) => safe.replaceAll(value, '[REDACTED]'), text);
|
|
838
911
|
const runBash: BashRun = async (body, opts) => {
|
|
912
|
+
// Hash the actual /bin/bash argv after interpolation. Keep the command text (which may contain
|
|
913
|
+
// credentials) out of logs, but leave enough to distinguish a replayed publish from npm/docs.
|
|
914
|
+
const argvSha256 = createHash('sha256').update(JSON.stringify(['/bin/bash', '-c', body])).digest('hex');
|
|
915
|
+
const fingerprint = { graphId, runId, nodeId: current!, recipe: node.recipe, argvSha256 };
|
|
916
|
+
debug.log('graph.run', 'command-dispatch', { ...fingerprint, pid: null });
|
|
839
917
|
const execute = options.deps?.runBash ?? realRunBash((pid) => {
|
|
918
|
+
debug.log('graph.run', 'command-spawn', { ...fingerprint, pid });
|
|
840
919
|
// Publish the child before its start time is known, so a concurrent stop refuses instead of failing
|
|
841
920
|
// the ledger while an untracked node keeps working (REL8b review round 3).
|
|
842
921
|
state.activeNode = { nodeId: current!, pid, pidStartedAt: UNVERIFIED_NODE_START };
|
|
@@ -878,7 +957,8 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
878
957
|
env.ELANOUS_GRAPH_DRY_RUN = state.dryRun ? '1' : '0';
|
|
879
958
|
env.ELANOUS_GRAPH_DIR = dirname(resolve(path));
|
|
880
959
|
const ctx = { arguments: '', artifactsDir: dirname(statePath), outputs: {}, resolvedProvider: undefined, resolvedModel: undefined, toolPolicy: {}, env } as NodeExecContext;
|
|
881
|
-
const result = await executeBashNode({ id: current, type: 'bash', bash: state.dryRun ? command.dry_run_command! : command.command,
|
|
960
|
+
const result = await executeBashNode({ id: current, type: 'bash', bash: state.dryRun ? command.dry_run_command! : command.command,
|
|
961
|
+
idle_timeout: command.timeout_ms ?? DEFAULT_BASH_TIMEOUT_MS } as BashNode, ctx, { runBash } as WorkflowDeps);
|
|
882
962
|
output = result.output;
|
|
883
963
|
ok = result.ok;
|
|
884
964
|
exit = code;
|
|
@@ -904,7 +984,10 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
904
984
|
const alreadySpent = state.path.length + (state.unitCalls ?? []).reduce((sum, call) => sum + call.steps, 0);
|
|
905
985
|
const remaining = options.unitContext ? options.unitContext.remainingSteps - alreadySpent : Infinity;
|
|
906
986
|
const childRunId = randomUUID();
|
|
907
|
-
const
|
|
987
|
+
const selectedEntry = node.callEntry ?? unit.template.entryNode;
|
|
988
|
+
const recordEntry = node.callEntry !== undefined || unit.template.unit?.entries !== undefined;
|
|
989
|
+
const info = { graphId, runId, nodeId: current, unit: unit.template.graphId, childRunId, parentRunId: runId,
|
|
990
|
+
...(recordEntry ? { entry: selectedEntry } : {}) };
|
|
908
991
|
let entered = false;
|
|
909
992
|
try {
|
|
910
993
|
for (const key of contract.inputs) if (!Object.hasOwn(inputs, key)) throw new Error(`unit input missing: ${current}.${key}`);
|
|
@@ -916,7 +999,8 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
916
999
|
const child = await runGraph(unit.path, { runId: childRunId, input: inputs, dryRun: false,
|
|
917
1000
|
pinChildUniverse: options.pinChildUniverse,
|
|
918
1001
|
unitContext: { parentRunId: runId, ancestors: [...(options.unitContext?.ancestors ?? []), graphId],
|
|
919
|
-
remainingSteps: Math.min(node.budget!.innerSteps, remaining)
|
|
1002
|
+
remainingSteps: Math.min(node.budget!.innerSteps, remaining),
|
|
1003
|
+
...(recordEntry ? { entryNodeId: selectedEntry } : {}) }, deps: options.deps });
|
|
920
1004
|
const steps = child.path.length + (child.unitCalls ?? []).reduce((sum, call) => sum + call.steps, 0);
|
|
921
1005
|
const childExit = child.status === 'done' || child.status === 'failed' ? child.path.at(-1) : undefined;
|
|
922
1006
|
// The last successful record carrying every contracted output is the source;
|
|
@@ -930,7 +1014,8 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
930
1014
|
}, undefined) ?? {};
|
|
931
1015
|
state.unitCalls ??= [];
|
|
932
1016
|
state.unitCalls.push({ node: current, unit: unit.template.graphId, version: unit.template.version,
|
|
933
|
-
hash: unit.hash, childRunId, exit: childExit ?? child.status, steps
|
|
1017
|
+
hash: unit.hash, childRunId, exit: childExit ?? child.status, steps,
|
|
1018
|
+
...(recordEntry ? { entry: selectedEntry } : {}) });
|
|
934
1019
|
if (child.status === 'awaiting-approval') throw new Error(`unit cannot pause for approval: ${current}`);
|
|
935
1020
|
if (child.status === 'budget-exceeded' || steps > node.budget!.innerSteps || steps > remaining) {
|
|
936
1021
|
debug.log('graph.unit', 'budget-exhausted', { ...info, steps, innerSteps: node.budget!.innerSteps });
|
|
@@ -1003,6 +1088,10 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
1003
1088
|
...(approval && state.pending ? { ...(state.pending.decidedBy === undefined ? {} : { decidedBy: state.pending.decidedBy }), decidedAt: state.pending.decidedAt } : {}),
|
|
1004
1089
|
startedAt: nodeStartedAt, endedAt: nodeEndedAt, seconds });
|
|
1005
1090
|
}
|
|
1091
|
+
if (isReleaseLoop && graph.terminalNodes.includes(current) && node.recipe === 'none' &&
|
|
1092
|
+
(exit !== null || output !== undefined || error !== undefined || state.nodes.at(-1)?.executed)) {
|
|
1093
|
+
throw new Error(`release terminal received command output: ${current}`);
|
|
1094
|
+
}
|
|
1006
1095
|
if (approval) {
|
|
1007
1096
|
delete state.pending;
|
|
1008
1097
|
delete state.approvalSourceHash;
|
|
@@ -201,6 +201,8 @@ export function planHarnessClean(input: {
|
|
|
201
201
|
readWorktreeProvenance?: WorktreeProvenanceReader;
|
|
202
202
|
/** provenance reader가 쓰는 git runner. planner 단독 테스트에서는 반드시 명시한다. */
|
|
203
203
|
run?: GitRunner;
|
|
204
|
+
/** Allow reclaiming unowned worktrees only for merged-clean branches; default remains preservation. */
|
|
205
|
+
includeUnownedMergedClean?: boolean;
|
|
204
206
|
/** 조회별 소요 시간(밀리초). 키는 `queryStatus` 조회 칸과 같다. 못 잰 칸은 생략한다. */
|
|
205
207
|
queryTimings?: HarnessCleanQueryTimings;
|
|
206
208
|
}): HarnessCleanPlan {
|
|
@@ -353,7 +355,15 @@ export function planHarnessClean(input: {
|
|
|
353
355
|
continue;
|
|
354
356
|
}
|
|
355
357
|
const ladderReason = `assessment=${assessment.disposition}:${assessment.reason}`;
|
|
356
|
-
|
|
358
|
+
const includeUnownedMergedClean = input.includeUnownedMergedClean === true
|
|
359
|
+
&& mode === 'merged'
|
|
360
|
+
&& mergedPr.has(branch)
|
|
361
|
+
&& !openPr.has(branch)
|
|
362
|
+
&& assessment.disposition === 'reclaim-safe'
|
|
363
|
+
&& assessment.reason === 'merged-clean'
|
|
364
|
+
&& !ownership.recorded
|
|
365
|
+
&& ownership.status.startsWith('not-recorded:');
|
|
366
|
+
if (assessment.disposition !== 'reclaim-safe' || (!ownership.recorded && !includeUnownedMergedClean)) {
|
|
357
367
|
preserve.push({ ...base, reason: `${ladderReason}; ownership=${ownership.status}` });
|
|
358
368
|
continue;
|
|
359
369
|
}
|
|
@@ -992,6 +1002,7 @@ export function execHarnessClean(opts: {
|
|
|
992
1002
|
mode: HarnessCleanMode;
|
|
993
1003
|
dryRun?: boolean;
|
|
994
1004
|
force?: boolean;
|
|
1005
|
+
includeUnownedMergedClean?: boolean;
|
|
995
1006
|
branchPrefix?: string;
|
|
996
1007
|
repo?: string;
|
|
997
1008
|
window?: { offset: number; size: number };
|
|
@@ -1087,6 +1098,7 @@ export function execHarnessClean(opts: {
|
|
|
1087
1098
|
readWorktreeProvenance: opts.readWorktreeProvenance,
|
|
1088
1099
|
run,
|
|
1089
1100
|
mode: opts.mode,
|
|
1101
|
+
...(opts.includeUnownedMergedClean ? { includeUnownedMergedClean: true } : {}),
|
|
1090
1102
|
queryStatus,
|
|
1091
1103
|
queryTimings: {
|
|
1092
1104
|
worktrees: worktreesTimed.ms,
|
|
@@ -1286,10 +1298,16 @@ export function renderHarnessCleanReport(
|
|
|
1286
1298
|
item.reason.includes('reclaim-safe:merged-no-worktree'),
|
|
1287
1299
|
).length;
|
|
1288
1300
|
const branchOnlyReclaimedCount = res.plan.remove.filter((item) => item.reason.includes('branch-only')).length;
|
|
1301
|
+
const isUnownedMergedClean = (item: CleanPlanItem): boolean =>
|
|
1302
|
+
item.reason.includes('assessment=reclaim-safe:merged-clean') && item.reason.includes('ownership=not-recorded:');
|
|
1303
|
+
const plannedUnownedMergedCleanCount = res.plan.remove.filter(isUnownedMergedClean).length;
|
|
1304
|
+
const unownedMergedCleanCount = (res.dryRun ? res.plan.remove : res.removed).filter(isUnownedMergedClean).length;
|
|
1305
|
+
const unownedMergedCleanSummary = plannedUnownedMergedCleanCount > 0
|
|
1306
|
+
? ` · 미소유 merged-clean ${res.dryRun ? '제거 예정' : '회수'} ${unownedMergedCleanCount}` : '';
|
|
1289
1307
|
L.push(
|
|
1290
1308
|
omittedPreserve > 0
|
|
1291
|
-
? `규모 매칭 ${matchedTotal} · 제거 계획 ${res.plan.remove.length} · 보존 ${res.plan.preserve.length} · 화면 ${displayedCount} (보존 ${omittedPreserve}개는 생략) · 브랜치만 회수 ${branchOnlyReclaimedCount} · 워크트리 없이 안전 ${mergedNoWorktreeCount}`
|
|
1292
|
-
: `규모 매칭 ${matchedTotal} · 제거 계획 ${res.plan.remove.length} · 보존 ${res.plan.preserve.length} · 화면 ${displayedCount} · 브랜치만 회수 ${branchOnlyReclaimedCount} · 워크트리 없이 안전 ${mergedNoWorktreeCount}`,
|
|
1309
|
+
? `규모 매칭 ${matchedTotal} · 제거 계획 ${res.plan.remove.length} · 보존 ${res.plan.preserve.length} · 화면 ${displayedCount} (보존 ${omittedPreserve}개는 생략) · 브랜치만 회수 ${branchOnlyReclaimedCount} · 워크트리 없이 안전 ${mergedNoWorktreeCount}${unownedMergedCleanSummary}`
|
|
1310
|
+
: `규모 매칭 ${matchedTotal} · 제거 계획 ${res.plan.remove.length} · 보존 ${res.plan.preserve.length} · 화면 ${displayedCount} · 브랜치만 회수 ${branchOnlyReclaimedCount} · 워크트리 없이 안전 ${mergedNoWorktreeCount}${unownedMergedCleanSummary}`,
|
|
1293
1311
|
);
|
|
1294
1312
|
if (res.plan.remove.length === 0) L.push('정리 대상 없음.');
|
|
1295
1313
|
else {
|