elanous 0.2.23 → 0.2.24
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/{272-b23d2e92161d11fd.js → 272-0eed0fb0aab716b6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{3961-fbef064c213b2b18.js → 3961-aaae04ed1d32622f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/{page-d5341ac96203a983.js → page-3b9fc39362473ae7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f2b62c19ca68fef8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/{page-55c631c032dcc47e.js → page-d7602d489edf5f37.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-ad3bd8084048bee4.js → page-24282bf9f90031a5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-8b7c5ab6b5bc8d64.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/{page-5c5d0e370f495ef6.js → page-4eb84d454f8c3415.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-c325e21543cd48a8.js → page-b2e494bdad57fcb5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/{page-6168a7c43e6ef8f5.js → page-c0ab229673a8b0eb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-3e2b45f3c36dbac4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-c22aa511b28743ad.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-26345242e4049f55.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/{page-25bb425afdb27541.js → page-bf88c0f558d8ef54.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-32d72fdef1afe11a.js → page-52fec523e5b5d3ad.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-67723dfdcde92b31.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/{page-2bd9ce9c020d02d4.js → page-22725e2570f1126b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-a34096245e63b9f2.js → page-1891335950d68b70.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-3267af1ebdc73cf5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/{page-4cc66d22ca4ec502.js → page-c314b75c11234998.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live-run/{page-83b14c729fcea837.js → page-9f9c85415f61782b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-c1d4276059085587.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-833aab587f26ad4c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-ccdb83ed40c8d724.js → page-8526c17ae38e6a50.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-7ad3de8fa06fdea9.js → page-f998637a8402115c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-b7bb53828b17b335.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-1b1850de0257e46d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-786111f30b6f0969.js → page-6f23159fedb41f67.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/{page-48fb31810693c357.js → page-1d809a87139dd024.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/{page-4e17cce5e2ed0fee.js → page-da9e570234d481fd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/{page-cb154acadb025414.js → page-b92caddb5c715f30.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/page-8ce3a38e210b810a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/{page-c2dd2b19c93f90ab.js → page-57ffeeb6019440fd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-abbb417bf514630b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/{page-d3c007c8a0ddd7ee.js → page-2c2b3f29cec1efa4.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-7837ce2aacb97b04.js → page-9369279f72fc7b51.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-64861f14039499c3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-ddc4c93efd8ab94b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-901a1391b81053bc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-68d9bbaac9841a58.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/{page-119be9a0444d2482.js → page-b5c0c996f329371e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/{page-fa806c018833e332.js → page-89881ccbcf728978.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-0e87a803b3d7165e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/{page-d431fbdea6ad93dd.js → page-58d4e5cb30ef4871.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-02ce4c3b5f39e4d2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-e620d1bb8eadcec2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-847c3de6ca8a4416.js → page-acf868c64d77f950.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-54fa605fa6dba457.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/{page-74e69ba2c8e970d4.js → page-bc79f0833533fa84.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/{page-70038f41ae7cf931.js → page-d5c4398eea03d8f3.js} +1 -1
- package/apps/pwa/out/_next/static/css/72becfd42807b50d.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +14 -14
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +14 -14
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +14 -14
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +14 -14
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +14 -14
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +14 -14
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +14 -14
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +14 -14
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +14 -14
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +14 -14
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +14 -14
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +14 -14
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +14 -14
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +14 -14
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +14 -14
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +14 -14
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +14 -14
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +14 -14
- package/apps/pwa/out/live-run/index.html +1 -1
- package/apps/pwa/out/live-run/index.txt +14 -14
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +14 -14
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +14 -14
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +14 -14
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +14 -14
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +14 -14
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +14 -14
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +14 -14
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +14 -14
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +14 -14
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +15 -15
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +14 -14
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +14 -14
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +14 -14
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +14 -14
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +14 -14
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +14 -14
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +14 -14
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +14 -14
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +14 -14
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +14 -14
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +14 -14
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +14 -14
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +14 -14
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +14 -14
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +14 -14
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +14 -14
- package/catalog/models/openrouter/deepseek-v4-pro-0813.yaml +39 -0
- package/catalog/models/openrouter/deepseek-v4.1-flash.yaml +38 -0
- package/catalog/models/openrouter/glm-5.3-flash.yaml +35 -0
- package/catalog/providers/openrouter.yaml +2 -0
- package/graphs/implement-loop.yaml +31 -0
- package/graphs/ops/harness-gc.yaml +17 -0
- package/graphs/ops/recipes.yaml +3 -0
- package/graphs/overlays/README.md +1 -1
- package/graphs/overlays/stop-heal-ladder.yaml +105 -0
- package/package.json +1 -1
- package/scripts/add-api-key.sh +1 -1
- package/scripts/ci-typecheck-changed.ts +71 -14
- package/scripts/fleet/bedrock-pod-secret.ts +122 -0
- package/scripts/fleet/node-setup-linux.sh +97 -0
- package/scripts/fleet/node-setup.sh +11 -0
- package/scripts/fleet/zai-pod-secret.ts +86 -0
- package/scripts/land-global-checks.ts +3 -0
- package/scripts/measure-log-diet-2.ts +213 -0
- package/scripts/measure-tsc-affected-roots.ts +289 -0
- package/scripts/release-loop/gate-executor.ts +28 -0
- package/scripts/release-loop/gate-node.ts +262 -26
- package/scripts/release-loop/resume-release.ts +9 -3
- package/scripts/release-loop/resume.ts +41 -0
- package/scripts/release-loop/unattended-release.ts +22 -7
- package/scripts/tsc-baseline-cache.ts +147 -0
- package/scripts/usage-rollup.ts +58 -6
- package/src/agent/run-context.ts +10 -0
- package/src/agent-mission/driver.ts +18 -3
- package/src/agent-mission/resource-ladder.ts +8 -3
- package/src/agent-mission/review-loop.ts +8 -2
- package/src/agent-substrate/acp-implementer.ts +139 -0
- package/src/agent-substrate/pr-reviewer.ts +31 -4
- package/src/agent-substrate/self-review-cli.ts +48 -9
- package/src/autopilot/mission-critique.ts +18 -5
- package/src/autopilot/mission-feasibility-negotiate.ts +11 -10
- package/src/autopilot/mission-trace.ts +12 -1
- package/src/autopilot/system-lookback-run.ts +16 -10
- package/src/bench/bench.ts +183 -0
- package/src/budget/fetchers/grok.ts +15 -5
- package/src/budget/llm-cost.ts +37 -5
- package/src/budget/types.ts +8 -2
- package/src/budget/unified-usage.ts +88 -6
- package/src/cli/bench-cli.ts +34 -0
- package/src/cli/guardian-cli.ts +26 -1
- package/src/cli/image-cli.ts +101 -0
- package/src/cli/log-event-names.ts +4 -0
- package/src/cli/pr-cli.ts +62 -5
- package/src/cli/pr-land-overlap-evidence.ts +160 -12
- package/src/cli/release-cli.ts +18 -9
- package/src/cli/rfc-status.ts +52 -2
- package/src/cli/self-update.ts +104 -0
- package/src/cli/tasks-cli.ts +109 -4
- package/src/cli/usage-cli.ts +91 -8
- package/src/core-turn/run-goal-loop.ts +5 -4
- package/src/dashboard/ad-run-setup-bindings.ts +279 -0
- package/src/dashboard/index.ts +18 -276
- package/src/dashboard/stale-install-check.ts +11 -0
- package/src/debug/log-diet.ts +38 -7
- package/src/debug/log.ts +19 -1
- package/src/domains/release-tool.ts +39 -0
- package/src/error-report/report.ts +21 -4
- package/src/github/pr-labels.ts +1 -0
- package/src/graph-runner/graph-cli.ts +42 -1
- package/src/graph-runner/runner.ts +167 -6
- package/src/grok/usage.ts +23 -2
- package/src/grounding/ref-index.ts +253 -0
- package/src/harness/dispatch-backpressure.ts +10 -2
- package/src/harness/harness-clean.ts +199 -78
- package/src/harness/harness-cli-command.ts +122 -7
- package/src/harness/harness-gc.ts +331 -0
- package/src/harness/harness-stop.ts +4 -2
- package/src/harness/harness-substrate-default.ts +142 -7
- package/src/harness/launch-registry.ts +6 -0
- package/src/harness/worktree-branch-prefix.ts +9 -0
- package/src/hq/fence.ts +187 -0
- package/src/hq/seats.ts +38 -22
- package/src/images/image-ledger.ts +179 -0
- package/src/index.ts +93 -11
- package/src/instance/child-scope.ts +32 -1
- package/src/instance/dev-project.ts +133 -0
- package/src/instance/resolve.ts +24 -0
- package/src/instance/spawn-elanous.ts +45 -0
- package/src/intake-plane/ref-prefetch.ts +110 -0
- package/src/intelligence-map/model-catalog.ts +72 -0
- package/src/llm/bedrock-eventstream.ts +215 -0
- package/src/llm/bedrock-provider.ts +114 -41
- package/src/llm/bedrock.ts +174 -28
- package/src/llm/openrouter-byok-cost.ts +96 -0
- package/src/llm/openrouter-deepseek.ts +151 -0
- package/src/llm/openrouter-upstream-pins.config.ts +25 -0
- package/src/llm/zai-coding-plan.ts +700 -0
- package/src/llm.ts +87 -16
- package/src/loops/checker.ts +148 -9
- package/src/loops/orchestrator/resync-shadow.ts +34 -7
- package/src/maturity/feature-maturity.ts +2 -2
- package/src/model-tier/llm-tier-map.ts +9 -5
- package/src/models/prompts.ts +2 -1
- package/src/mss/logging/log-store.ts +58 -11
- package/src/nexus/api/missions.ts +73 -2
- package/src/nexus/api/ops-api.ts +18 -0
- package/src/nexus/index.ts +7 -75
- package/src/nexus/tools-kind.ts +78 -0
- package/src/oauth/codex-account-rotation.ts +39 -2
- package/src/oauth/codex-account-store.ts +323 -24
- package/src/oauth/fallback-chain.ts +53 -15
- package/src/onboarding/first-run.ts +37 -18
- package/src/onboarding/web-first.ts +20 -2
- package/src/ops/machine-heartbeat.ts +185 -0
- package/src/ops/overview-cache.ts +175 -0
- package/src/ops/overview.ts +8 -0
- package/src/prompt-cache/openai.ts +3 -0
- package/src/prompt-cache/types.ts +5 -0
- package/src/prompt-enhance/enhance.ts +23 -0
- package/src/release-loop/placement.ts +16 -1
- package/src/requirements/requirement-ledger.ts +536 -0
- package/src/retro/daily.ts +5 -5
- package/src/seat-loop/seat-loop.ts +32 -10
- package/src/self-dev/dev-cli.ts +44 -10
- package/src/self-dev/dev-pipeline.ts +7 -2
- package/src/self-dev/draft-residue.ts +14 -2
- package/src/self-dev/draft-sweep.ts +103 -0
- package/src/self-dev/graph-journey-route.ts +6 -0
- package/src/self-dev/orchestrate-cli.ts +25 -3
- package/src/self-dev/orchestrate.ts +41 -6
- package/src/self-dev/run-supervisor.ts +188 -15
- package/src/self-dev/run-types.ts +13 -0
- package/src/self-dev/supervisor-verdict-edges.ts +1 -0
- package/src/self-implement/budget-gate.ts +53 -13
- package/src/self-implement/child-context-composition.ts +139 -0
- package/src/self-implement/child-llm-preference.ts +13 -5
- package/src/self-implement/claude-acp-implement.ts +67 -0
- package/src/self-implement/flex-signals.ts +59 -0
- package/src/self-implement/frozen-merges.ts +8 -3
- package/src/self-implement/goal-author-decompose.ts +10 -2
- package/src/self-implement/goal-author.ts +24 -9
- package/src/self-implement/graph-overlay-condition.ts +28 -0
- package/src/self-implement/graph-overlay-yaml.ts +41 -7
- package/src/self-implement/graph-templates.ts +33 -1
- package/src/self-implement/graph-yaml.ts +56 -0
- package/src/self-implement/heal-intake.ts +5 -1
- package/src/self-implement/orchestrator.ts +144 -15
- package/src/self-implement/running-runs.ts +75 -5
- package/src/self-implement/seams.ts +47 -1
- package/src/self-implement/self-implement-cli.ts +9 -4
- package/src/self-implement/stop-autoheal.ts +69 -2
- package/src/session-runtime/retry-policy.ts +2 -1
- package/src/steward/launch.ts +5 -4
- package/src/steward/triage-budget.ts +62 -0
- package/src/steward/triage.ts +83 -22
- package/src/task-agent/actions.ts +62 -5
- package/src/task-agent/completion-evidence.ts +5 -0
- package/src/task-agent/cover.ts +101 -3
- package/src/task-agent/judge.ts +16 -0
- package/src/task-agent/land-order-ledger.ts +95 -0
- package/src/task-agent/live-move-history.ts +87 -1
- package/src/task-agent/live-moves.ts +350 -20
- package/src/task-agent/rejudge-on-head.ts +75 -20
- package/src/task-agent/review-prior-fix.ts +146 -0
- package/src/task-agent/run-adopt.ts +136 -0
- package/src/task-agent/shadow.ts +53 -9
- package/src/task-agent/task-hand.ts +54 -7
- package/src/task-orchestrator/surfaces/pod-account-broker.ts +38 -5
- package/src/task-orchestrator/surfaces/pod-bedrock-child.ts +74 -0
- package/src/task-orchestrator/surfaces/pod-child-default.ts +50 -0
- package/src/task-orchestrator/surfaces/pod-child-providers.ts +14 -1
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +79 -6
- package/src/task-orchestrator/surfaces/pod-lease.ts +17 -5
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +11 -5
- package/src/task-orchestrator/surfaces/pod-pool.ts +20 -4
- package/src/task-orchestrator/surfaces/pod-zai-child.ts +101 -0
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +213 -23
- package/src/task-orchestrator/surfaces/self-implement.ts +19 -1
- package/src/typecheck-ratchet.ts +1 -0
- package/src/user-config.ts +51 -11
- package/src/version/packed-revision.json +1 -1
- package/src/workflow-runtime/triggers/registry.ts +59 -0
- package/src/workflow-runtime/triggers/webhook-source.ts +35 -5
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-0160ab83c8685a12.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-e6784759a2531031.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-31417d3604154a6d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-e9606dc0bdec9305.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-a648bc464c9dc754.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-d27b794c8ba76bbb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-22170f4f00a15496.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-6e2781c523dad761.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-26dd415c8f13f341.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-015c02c4fec4023e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-6b0ae5b5c42f6d2f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-16258e956ea93fc2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-85f35dd1dd96ae87.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-7350ee93f4fe1033.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-00723458575f2b19.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-745d9b6c015076f8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-736d33c7414a8a1a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-c782041b5f5199ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-0c056abff82b9891.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-242d668e4fbf72c4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-81dd0d8ae923ed72.js +0 -1
- package/apps/pwa/out/_next/static/css/5619d59e87eddf66.css +0 -1
- /package/apps/pwa/out/_next/static/{rfkMm4movqkqMebBrCXiR → Q-7iofoxOEv1WX18S35of}/_buildManifest.js +0 -0
- /package/apps/pwa/out/_next/static/{rfkMm4movqkqMebBrCXiR → Q-7iofoxOEv1WX18S35of}/_ssgManifest.js +0 -0
|
@@ -23,7 +23,7 @@ import { listRunLedgers, loadFederatedRunLedger, loadRunLedger, queryRunScreenKe
|
|
|
23
23
|
import { classifyPodExitStop, recordRunStop, runStopRecorded, RUN_STOP_EVENT } from '../self-implement/run-stop.js';
|
|
24
24
|
import { ensureOpenRouterChildModelLive, resolveChildLlmEffort, resolveImplementationChildModel } from '../self-dev/dev-cli.js';
|
|
25
25
|
import { resolveHarnessTarget } from '../self-implement/harness-target-options.js';
|
|
26
|
-
import { queryRunningRuns } from '../self-implement/running-runs.js';
|
|
26
|
+
import { buildRunProgressRows, queryRunningRuns, renderRunProgressTable } from '../self-implement/running-runs.js';
|
|
27
27
|
import { DevPipelineError } from '../self-dev/dev-pipeline.js';
|
|
28
28
|
import { collectDraftMetrics, collectOverlapMetrics, DRAFT_SWEEP_CONCURRENCY, mapBounded, resolveDraftSweepCloseCap, runDraftSweep, sweepFailureReason, type DraftSweepAdapters, type DraftSweepResult, type OverlapMetricAdapters, type SweepDraft, type SweepMergedPr, type SweepReviewGate } from '../self-dev/draft-sweep.js';
|
|
29
29
|
import { readAskPreflightRowsSince, readSalvagedRowsSince } from '../self-dev/ask-launch-io.js';
|
|
@@ -58,6 +58,9 @@ import type { MissionSolveOutcome } from './mission-solve-loop.js';
|
|
|
58
58
|
import { formatAxis, formatAxisObservations, inspectAskMarkers, inspectUnpressedDecisionSignals } from '../../scripts/ask-marker-check.js';
|
|
59
59
|
import { enterJourneyNode, ensureJourneyKey, exitJourneyNode, JOURNEY_KEY_ENV, passJourneyNode, withJourneyNode } from '../self-dev/graph-journey-nodes.js';
|
|
60
60
|
import { intakeTakesQueue, readJourneyEdge, recordJourneyEdge } from '../self-dev/graph-journey-route.js';
|
|
61
|
+
import { appendBenchLedger, BENCH_LOG_CATEGORY, benchBaseBranch, defaultBenchGitRun, ensureBenchBase, nextBenchRound, parseBenchSpec, readBenchLedger, type BenchBase, type BenchGitRun } from '../bench/bench.js';
|
|
62
|
+
import { getElanousConfigDir } from '../elanous-config-dir.js';
|
|
63
|
+
import { codeRevision } from '../version/code-revision.js';
|
|
61
64
|
|
|
62
65
|
let harnessPlanRfcForTesting: typeof runHarnessPlanRfc | undefined;
|
|
63
66
|
let harnessAskMarkerInspectorForTesting: ((ask: string) => readonly string[]) | undefined;
|
|
@@ -144,6 +147,17 @@ export interface HarnessAskSayChildLlmOptions extends HarnessAskSayOptions {
|
|
|
144
147
|
childLlmProvider?: string;
|
|
145
148
|
childLlmModel?: string;
|
|
146
149
|
childLlmEffort?: string;
|
|
150
|
+
/** BENCH-ISOLATED-ARMS — `<id>[:<base-sha>]` · base 를 `bench/<id>/base` 로 고정한다(`--base` 와 함께 못 쓴다). */
|
|
151
|
+
bench?: string;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** BENCH-ISOLATED-ARMS — 시험이 실제 원격·운영 config 를 안 건드리게 주입한다. */
|
|
155
|
+
export interface HarnessBenchDeps {
|
|
156
|
+
readonly git?: BenchGitRun;
|
|
157
|
+
readonly configDir?: string;
|
|
158
|
+
readonly imageCommit?: () => string | null;
|
|
159
|
+
/** 부모(이 발사 프로세스)가 도는 코드 커밋 — 기본 `codeRevision()`(코드 자신의 위치 · cwd 무관). */
|
|
160
|
+
readonly codeCommit?: () => string | null;
|
|
147
161
|
}
|
|
148
162
|
|
|
149
163
|
export interface HarnessPlanOptions extends HarnessAskSayOptions {
|
|
@@ -288,7 +302,8 @@ function registerHarnessAskSayOptions(command: Command): Command {
|
|
|
288
302
|
.option('--pod-pool <spec>', 'pod: 풀 — 인자 > ELANOUS_POD_POOL > harness.podPool > 기존 pod.pool > 현재 컨텍스트')
|
|
289
303
|
.addOption(new Option('--pod-memory <tier>', 'pod: 메모리 등급 — lite(2Gi · 문서·조사·네트워크 스킬 골 · OOM 이면 high 로 한 번 재시도) | standard(16Gi) | high(32Gi) · 없으면 골 문면 `Pod 메모리: <등급>` 한 줄 · 그것도 없으면 골이 apps/pwa/ 를 담을 때 high').choices(['lite', 'standard', 'high']))
|
|
290
304
|
.option('--after <goal-or-pr>', 'pod: 선행 — 이 골 ID(16자) 또는 PR(#N) 이 병합될 때까지 Pod 발사가 큐에서 이유와 함께 기다린다(병합 없이 닫히면 blocked) · 골 문면 `선행: #N` 한 줄과 같다 · 대상 파일만 겹치면 선행이 아니다(경고만)')
|
|
291
|
-
.option('--source <spec>', 'pod: 원천 — commit:<40자 sha> | pr:<정수> | worktree:<경로> | files:<경로>[,<경로>…] · `--substrate pod` 와 함께')
|
|
305
|
+
.option('--source <spec>', 'pod: 원천 — commit:<40자 sha> | pr:<정수> | worktree:<경로> | files:<경로>[,<경로>…] · `--substrate pod` 와 함께')
|
|
306
|
+
.option('--bench <id[:sha]>', '벤치 팔 — base 를 bench/<id>/base 로 고정(없으면 sha·생략 시 origin/main 으로 만든다 · 있으면 sha 일치 확인 · 불일치 거부) · 팔 가지 bench/<id>/<arm> · PR 라벨 elanous:bench · PR 직전 main 동기화 생략 · 원장 elanous bench ls <id>');
|
|
292
307
|
}
|
|
293
308
|
|
|
294
309
|
function registerHarnessPlanOptions(command: Command): Command {
|
|
@@ -793,6 +808,36 @@ async function readHarnessLaunchBudget(
|
|
|
793
808
|
}
|
|
794
809
|
}
|
|
795
810
|
|
|
811
|
+
type BenchLaunchRow = Parameters<typeof appendBenchLedger>[0];
|
|
812
|
+
|
|
813
|
+
/** BENCH-ISOLATED-ARMS ④ — 원장 «launched» 줄. ⛔ 원장을 못 써도 발사는 막지 않는다(관측 `bench`/`ledger-unwritten`). */
|
|
814
|
+
function recordBenchLaunch(bench: BenchBase, opts: HarnessAskSayChildLlmOptions, substrate: string, entrance: string, deps: HarnessBenchDeps): BenchLaunchRow {
|
|
815
|
+
const configDir = deps.configDir ?? getElanousConfigDir();
|
|
816
|
+
const childLlmModel = opts.childLlmModel?.trim() || null;
|
|
817
|
+
let round = 1;
|
|
818
|
+
try { round = nextBenchRound(readBenchLedger(bench.id, configDir), childLlmModel); } catch { /* 못 읽으면 1 — 줄에 그대로 남는다 */ }
|
|
819
|
+
let imageCommit: string | null = null;
|
|
820
|
+
try { imageCommit = deps.imageCommit ? deps.imageCommit() : getUserConfig().harness?.imageCommit ?? null; } catch { imageCommit = null; }
|
|
821
|
+
// ⭐ base sha 와 «다른 칸»이다 — base 는 자식 작업 가지의 출발점일 뿐, 부모·Pod 발사 코드는 지금 도는 코드다
|
|
822
|
+
// (10-11 사고: 부모를 base 의 옛 트리에서 띄워 옛 발사 코드가 provider 를 조용히 바꿨다).
|
|
823
|
+
let parentCodeCommit: string | null = null;
|
|
824
|
+
try { parentCodeCommit = deps.codeCommit ? deps.codeCommit() : codeRevision() ?? null; } catch { parentCodeCommit = null; }
|
|
825
|
+
const row: BenchLaunchRow = {
|
|
826
|
+
ts: new Date().toISOString(), event: 'launched', benchId: bench.id, baseBranch: bench.branch, baseSha: bench.sha,
|
|
827
|
+
parentCodeCommit, imageCommit, childLlmProvider: opts.childLlmProvider?.trim() || null, childLlmModel, round,
|
|
828
|
+
entrance, substrate, ...(opts.seat ? { seat: opts.seat } : {}),
|
|
829
|
+
};
|
|
830
|
+
try { appendBenchLedger(row, configDir); }
|
|
831
|
+
catch (error) { try { debug.log(BENCH_LOG_CATEGORY, 'ledger-unwritten', { benchId: bench.id, event: 'launched', reason: error instanceof Error ? error.message : String(error) }); } catch { /* fail-soft */ } }
|
|
832
|
+
return row;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
function recordBenchFinish(launched: BenchLaunchRow, deps: HarnessBenchDeps): void {
|
|
836
|
+
const exitCode = typeof process.exitCode === 'number' ? process.exitCode : Number(process.exitCode ?? 0) || 0;
|
|
837
|
+
try { appendBenchLedger({ ...launched, ts: new Date().toISOString(), event: 'finished', exitCode }, deps.configDir ?? getElanousConfigDir()); }
|
|
838
|
+
catch (error) { try { debug.log(BENCH_LOG_CATEGORY, 'ledger-unwritten', { benchId: launched.benchId, event: 'finished', reason: error instanceof Error ? error.message : String(error) }); } catch { /* fail-soft */ } }
|
|
839
|
+
}
|
|
840
|
+
|
|
796
841
|
async function dispatchHarnessAskSay(
|
|
797
842
|
opts: HarnessAskSayChildLlmOptions & HarnessDryRunOpts,
|
|
798
843
|
dryRunPreview: {
|
|
@@ -806,6 +851,7 @@ async function dispatchHarnessAskSay(
|
|
|
806
851
|
queueDeps?: HarnessQueueDeps,
|
|
807
852
|
immediateLaunch?: HarnessQueueDeps['launch'],
|
|
808
853
|
immediateExit?: () => Promise<number>,
|
|
854
|
+
benchDeps: HarnessBenchDeps = {},
|
|
809
855
|
): Promise<void> {
|
|
810
856
|
// HARNESS-FULL-GRAPH — 여정 조인 키는 이 발사 동안만 환경에 싣는다(자식은 그동안 물려받는다).
|
|
811
857
|
const journeyPreset = Boolean(process.env[JOURNEY_KEY_ENV]?.trim());
|
|
@@ -815,6 +861,29 @@ async function dispatchHarnessAskSay(
|
|
|
815
861
|
if (refusePodChildProviderBeforeLaunch(resolveHarnessSubstrate({ flag: opts as HarnessSubstrateOpts, config: getUserConfig(), env: process.env }), opts)) return;
|
|
816
862
|
if (opts.childLlmModel !== undefined) await ensureOpenRouterChildModelLive(opts.childLlmProvider, opts.childLlmModel, { persist: !isHarnessDryRun(opts) });
|
|
817
863
|
assertHarnessChildLlmModel(opts);
|
|
864
|
+
// BENCH-ISOLATED-ARMS ① — 기준선 가지를 발사 «전»에 고정한다(꼴이 틀리거나 sha 가 어긋나면 발사 0).
|
|
865
|
+
let bench: BenchBase | undefined;
|
|
866
|
+
if (opts.bench !== undefined) {
|
|
867
|
+
const spec = parseBenchSpec(opts.bench);
|
|
868
|
+
if (opts.base !== undefined) {
|
|
869
|
+
console.error(`❌ --bench 와 --base 는 함께 못 쓴다 — 벤치 base 는 ${benchBaseBranch(spec.id)} 로 정해진다`);
|
|
870
|
+
process.exitCode = 2;
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
if (isHarnessDryRun(opts)) {
|
|
874
|
+
console.log(`[dry-run] bench: ${spec.id} → base ${benchBaseBranch(spec.id)} (${spec.sha ?? '가지가 있으면 그 sha · 없으면 발사 시점 origin/main'}) · 원격 변경 없음`);
|
|
875
|
+
} else {
|
|
876
|
+
try {
|
|
877
|
+
bench = ensureBenchBase(spec, benchDeps.git ?? defaultBenchGitRun(process.cwd()));
|
|
878
|
+
} catch (error) {
|
|
879
|
+
try { debug.log(BENCH_LOG_CATEGORY, 'base-refused', { benchId: spec.id, requestedSha: spec.sha ?? null, reason: error instanceof Error ? error.message : String(error) }); } catch { /* observation is fail-soft */ }
|
|
880
|
+
throw error;
|
|
881
|
+
}
|
|
882
|
+
try { debug.log(BENCH_LOG_CATEGORY, 'base-ensured', { benchId: bench.id, branch: bench.branch, sha: bench.sha, created: bench.created }); } catch { /* observation is fail-soft */ }
|
|
883
|
+
console.error(`🧪 bench ${bench.id} — base ${bench.branch} @ ${bench.sha.slice(0, 12)}${bench.created ? ' (새로 만듦)' : ' (기존 가지 · sha 일치)'}`);
|
|
884
|
+
opts = { ...opts, base: bench.branch };
|
|
885
|
+
}
|
|
886
|
+
}
|
|
818
887
|
if (isHarnessDryRun(opts)) {
|
|
819
888
|
resolveLaunchSubstrate(opts);
|
|
820
889
|
printHarnessLaunchDryRun(dryRunPreview);
|
|
@@ -857,7 +926,9 @@ async function dispatchHarnessAskSay(
|
|
|
857
926
|
resolveNestedElanousOption(opts);
|
|
858
927
|
const flags: string[] = [];
|
|
859
928
|
const values: Array<[string, string | undefined]> = [
|
|
860
|
-
|
|
929
|
+
// 벤치는 «--bench id:sha» 로 넘긴다 — 대기열 자식이 sha 를 다시 확인하고 원장 줄을 쓴다.
|
|
930
|
+
['--base', bench ? undefined : opts.base], ['--bench', bench ? `${bench.id}:${bench.sha}` : undefined],
|
|
931
|
+
['--target', opts.target], ['--correlation', opts.correlation],
|
|
861
932
|
['--goal-type', opts.goalType], ...(dryRunPreview.entrance === 'cli-harness-say' ? [['--author-grade', opts.authorGradeSource === 'flag' ? opts.authorGrade : undefined] as [string, string | undefined]] : []), ['--child-llm-provider', opts.childLlmProvider],
|
|
862
933
|
['--child-llm-model', opts.childLlmModel], ['--child-llm-effort', opts.childLlmEffort],
|
|
863
934
|
['--pod-pool', (opts as HarnessSubstrateOpts).podPool], ['--pod-memory', (opts as HarnessSubstrateOpts).podMemory],
|
|
@@ -937,8 +1008,10 @@ async function dispatchHarnessAskSay(
|
|
|
937
1008
|
else delete process.env.ELANOUS_HARNESS_SEAT;
|
|
938
1009
|
// 배치 노드 — 실행 칸(local | pod)이 그 출구 라벨이고, YAML 의 decompose 간선(on: substrate)이 그 값으로 갈린다.
|
|
939
1010
|
passJourneyNode('dispatch', { ...journey, outcome: resolved.substrate });
|
|
1011
|
+
const benchRow = bench ? recordBenchLaunch(bench, stampedOpts, resolved.substrate, dryRunPreview.entrance, benchDeps) : undefined;
|
|
940
1012
|
try { await dispatch(resolved, stampedOpts); }
|
|
941
1013
|
finally {
|
|
1014
|
+
if (benchRow) recordBenchFinish(benchRow, benchDeps);
|
|
942
1015
|
if (previous === undefined) delete process.env.ELANOUS_HARNESS_SEAT;
|
|
943
1016
|
else process.env.ELANOUS_HARNESS_SEAT = previous;
|
|
944
1017
|
}
|
|
@@ -2299,8 +2372,16 @@ export type GithubDraftSweepAdapters = DraftSweepAdapters & {
|
|
|
2299
2372
|
runLedgerMatches(): readonly RunLedgerMatch[];
|
|
2300
2373
|
};
|
|
2301
2374
|
|
|
2375
|
+
/** DRAFT-LINEAGE-ONE-1 — how the sweep stops a superseded run: `harness stop` (process ⊕ Pod Job by run label). */
|
|
2376
|
+
export type DraftSweepStopRun = (runId: string) => Promise<Pick<import('./harness-stop.js').HarnessStopResult, 'process' | 'jobsDeleted' | 'scanned'>>;
|
|
2377
|
+
const defaultDraftSweepStopRun: DraftSweepStopRun = async (runId) => {
|
|
2378
|
+
const { stopHarnessRun, defaultHarnessStopDeps } = await import('./harness-stop.js');
|
|
2379
|
+
return stopHarnessRun(runId, defaultHarnessStopDeps());
|
|
2380
|
+
};
|
|
2381
|
+
|
|
2302
2382
|
export function githubDraftSweepAdapters(execute: GhExecute = executeGh, git: DraftSweepGitExecute = runGitCommand, cwd = process.cwd(),
|
|
2303
|
-
executeAsync: (args: string[]) => Promise<string> = execute === executeGh ? executeGhAsync : async (args) => execute(args)
|
|
2383
|
+
executeAsync: (args: string[]) => Promise<string> = execute === executeGh ? executeGhAsync : async (args) => execute(args),
|
|
2384
|
+
stopRun: DraftSweepStopRun = defaultDraftSweepStopRun): GithubDraftSweepAdapters {
|
|
2304
2385
|
const inventory = async (repository: string, state: 'open' | 'closed'): Promise<GithubPull[]> => {
|
|
2305
2386
|
// ⛔ A large repository has tens of thousands of closed PRs (2026-09-28: 21k → «exceeded pagination limit», no sweep at all).
|
|
2306
2387
|
// Merged twins only matter after the oldest open draft was opened, so the closed listing stops at that time.
|
|
@@ -2568,10 +2649,25 @@ export function githubDraftSweepAdapters(execute: GhExecute = executeGh, git: Dr
|
|
|
2568
2649
|
commentDraft: async (repository, number, body) => { execute(['pr', 'comment', String(number), '--repo', repository, '--body', body]); },
|
|
2569
2650
|
removeLabel: async (repository, number, name) => { execute(['pr', 'edit', String(number), '--repo', repository, '--remove-label', name]); },
|
|
2570
2651
|
getReviewGate: async (draft, repository) => reviewGateForDraft(draft, repository),
|
|
2652
|
+
// DRAFT-LINEAGE-ONE-1 — the sweep calls this only with `--apply`, only for a live run whose newer same-lineage run
|
|
2653
|
+
// is live too. The run id must come from this PR's own ledger owner; a stop that cannot be confirmed rejects, so
|
|
2654
|
+
// the draft is neither labelled nor closed.
|
|
2655
|
+
supersedeRun: async (draft) => {
|
|
2656
|
+
if (!draft.runId) throw new Error(`run id of #${draft.number} unknown (no single ledger owner) — not stopping`);
|
|
2657
|
+
const stopped = await stopRun(draft.runId);
|
|
2658
|
+
// `absent` after a complete process-table scan = nothing left to stop (a dead run whose worktree remains) — confirmed.
|
|
2659
|
+
const confirmed = stopped.process === 'stopped' || stopped.process === 'killed' || stopped.jobsDeleted > 0
|
|
2660
|
+
|| (stopped.process === 'absent' && stopped.scanned);
|
|
2661
|
+
if (!confirmed) {
|
|
2662
|
+
throw new Error(`run ${draft.runId} not confirmed stopped (process ${stopped.process} · jobs ${stopped.jobsDeleted} · scanned ${stopped.scanned})`);
|
|
2663
|
+
}
|
|
2664
|
+
},
|
|
2571
2665
|
};
|
|
2572
2666
|
}
|
|
2573
2667
|
|
|
2574
2668
|
export interface HarnessDraftSweepDeps {
|
|
2669
|
+
/** DRAFT-LINEAGE-ONE-1 test seam for the real adapter's run stop; omitted ⇒ `harness stop`. */
|
|
2670
|
+
readonly stopRun?: DraftSweepStopRun;
|
|
2575
2671
|
readonly adapters?: DraftSweepAdapters;
|
|
2576
2672
|
/** Optional overlap source injection; tests do not read GitHub or logs.db. */
|
|
2577
2673
|
readonly overlapAdapters?: OverlapMetricAdapters;
|
|
@@ -2729,7 +2825,7 @@ function installHarnessDraftSweepCommand(harnessCmd: Command, deps: HarnessDraft
|
|
|
2729
2825
|
throw new HarnessCliInputError(`invalid --repo (expected owner/name): ${repository}`);
|
|
2730
2826
|
}
|
|
2731
2827
|
// One adapter instance per sweep: its open listing and ledger scan are shared with the RUN-TTL step.
|
|
2732
|
-
const github = deps.adapters ? undefined : githubDraftSweepAdapters(deps.execute);
|
|
2828
|
+
const github = deps.adapters ? undefined : githubDraftSweepAdapters(deps.execute, undefined, undefined, undefined, deps.stopRun);
|
|
2733
2829
|
const configCap = deps.closeCapConfig
|
|
2734
2830
|
? deps.closeCapConfig()
|
|
2735
2831
|
: (() => { try { return getUserConfig().tools?.selfImplement?.draftSweepCloseCap; } catch { return undefined; } })();
|
|
@@ -3022,6 +3118,8 @@ export interface HarnessCliCommandDeps {
|
|
|
3022
3118
|
say?: HarnessSayHandler;
|
|
3023
3119
|
/** Observe-only Pod host dispatch seam. */
|
|
3024
3120
|
podDispatchTask?: (input: DispatchTaskInput, deps?: DispatchTaskDeps) => ReturnType<typeof dispatchTask>;
|
|
3121
|
+
/** BENCH-ISOLATED-ARMS — 벤치 base 고정·원장 주입(시험). */
|
|
3122
|
+
bench?: HarnessBenchDeps;
|
|
3025
3123
|
launchGate?: PreLaunchGateDeps & { readBudget?: () => Promise<BudgetDecision | 'unknown'> };
|
|
3026
3124
|
plan?: HarnessPlanHandler;
|
|
3027
3125
|
/** ⭐ `plan` 주입이 «없을 때» 가는 기본 분기. 시험이 이 자리로 «CLI 의 기본 배선»을 문다
|
|
@@ -3029,6 +3127,8 @@ export interface HarnessCliCommandDeps {
|
|
|
3029
3127
|
planRfc?: typeof runHarnessPlanRfc;
|
|
3030
3128
|
mission?: HarnessMissionHandler;
|
|
3031
3129
|
missionLoop?: HarnessMissionLoopHandler;
|
|
3130
|
+
/** `harness progress` 가 도는 런 관측을 읽는 자리(시험 주입). 기본은 queryRunningRuns. */
|
|
3131
|
+
progress?: { query?: typeof queryRunningRuns; nowMs?: () => number };
|
|
3032
3132
|
}
|
|
3033
3133
|
|
|
3034
3134
|
export function installHarnessCliCommand(program: Command, deps: HarnessCliCommandDeps): Command {
|
|
@@ -3087,6 +3187,21 @@ export function installHarnessCliCommand(program: Command, deps: HarnessCliComma
|
|
|
3087
3187
|
process.exitCode = 1;
|
|
3088
3188
|
}
|
|
3089
3189
|
});
|
|
3190
|
+
harnessCmd.command('progress').description('도는 골마다 진도를 한 표로 읽는다 — 단계 · 진입 시각 · 마지막 진전 · pty (읽기 전용)')
|
|
3191
|
+
.option('--json', '같은 행 배열을 JSON 으로 출력')
|
|
3192
|
+
.action((opts: { json?: boolean }) => {
|
|
3193
|
+
try {
|
|
3194
|
+
const result = (deps.progress?.query ?? queryRunningRuns)({ caller: 'harness.progress' });
|
|
3195
|
+
const nowMs = (deps.progress?.nowMs ?? Date.now)();
|
|
3196
|
+
if (opts.json) {
|
|
3197
|
+
console.log(JSON.stringify(buildRunProgressRows(result, nowMs)));
|
|
3198
|
+
if (result.completeness === 'partial') console.error(renderRunProgressTable(result, nowMs).split('\n')[0]);
|
|
3199
|
+
} else console.log(renderRunProgressTable(result, nowMs));
|
|
3200
|
+
} catch (error) {
|
|
3201
|
+
console.error(humanErrorLine(error));
|
|
3202
|
+
process.exitCode = 1;
|
|
3203
|
+
}
|
|
3204
|
+
});
|
|
3090
3205
|
installDeliverableVerifyCliCommand(harnessCmd, deps.deliverableVerify);
|
|
3091
3206
|
installHarnessProcessObservationCommand(harnessCmd, deps.processObservation);
|
|
3092
3207
|
installHarnessBudgetCommand(harnessCmd);
|
|
@@ -3194,7 +3309,7 @@ export function installHarnessCliCommand(program: Command, deps: HarnessCliComma
|
|
|
3194
3309
|
}
|
|
3195
3310
|
return resolved.substrate === 'pod' ? onPod(stampedOpts, 'cli-harness-ask', goalPath, resolved.pool!, deps.podDispatchTask) : (announceLocalChildProvider(stampedOpts), ask(goalPath, normalizeHarnessAskSayOptions(stampedOpts)));
|
|
3196
3311
|
},
|
|
3197
|
-
deps.launchGate, queueDeps, immediateLaunch, queueDeps.launch ? undefined : immediateExit,
|
|
3312
|
+
deps.launchGate, queueDeps, immediateLaunch, queueDeps.launch ? undefined : immediateExit, deps.bench,
|
|
3198
3313
|
);
|
|
3199
3314
|
});
|
|
3200
3315
|
}
|
|
@@ -3227,7 +3342,7 @@ export function installHarnessCliCommand(program: Command, deps: HarnessCliComma
|
|
|
3227
3342
|
? onPod(stampedOpts, 'cli-harness-say', sentence.join(' '), resolved.pool!, deps.podDispatchTask, harnessAuthorOnPod())
|
|
3228
3343
|
: (announceLocalChildProvider(stampedOpts), say(sentence, normalizeHarnessAskSayOptions(stampedOpts)));
|
|
3229
3344
|
},
|
|
3230
|
-
deps.launchGate, queueDeps, immediateLaunch, queueDeps.launch ? undefined : immediateExit,
|
|
3345
|
+
deps.launchGate, queueDeps, immediateLaunch, queueDeps.launch ? undefined : immediateExit, deps.bench,
|
|
3231
3346
|
);
|
|
3232
3347
|
});
|
|
3233
3348
|
}
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { isAbsolute, join, resolve } from 'node:path';
|
|
4
|
+
import { debug } from '../debug/log.js';
|
|
5
|
+
import { configuredWorktreeRoot } from '../user-config.js';
|
|
6
|
+
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
7
|
+
import { acquireLockSync } from '../storage/file-lock.js';
|
|
8
|
+
import { createGhRunner, execHarnessClean, gitStdout, type GitRunner, type GhRunner, type ActiveDirectoryProvider } from './harness-clean.js';
|
|
9
|
+
import type { WorktreeAssessmentInput, WorktreeAssessment } from './harness-worktrees.js';
|
|
10
|
+
|
|
11
|
+
export interface HarnessGcOptions {
|
|
12
|
+
repos?: string[];
|
|
13
|
+
dryRun?: boolean;
|
|
14
|
+
window?: number;
|
|
15
|
+
maxRemove?: number;
|
|
16
|
+
budgetMs?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface HarnessGcDeps {
|
|
20
|
+
stateRoot?: string;
|
|
21
|
+
worktreeRoot?: string;
|
|
22
|
+
now?: () => number;
|
|
23
|
+
run?: GitRunner;
|
|
24
|
+
runGh?: GhRunner;
|
|
25
|
+
activeDirectoryProvider?: ActiveDirectoryProvider;
|
|
26
|
+
assessWorktree?: (input: WorktreeAssessmentInput) => WorktreeAssessment;
|
|
27
|
+
readWorktreeProvenance?: (path: string, run: GitRunner) => { owner: string; command: string; createdAt: string };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface HarnessGcRepo {
|
|
31
|
+
gitDir: string;
|
|
32
|
+
offset: number;
|
|
33
|
+
window: number;
|
|
34
|
+
total: number;
|
|
35
|
+
removed: number;
|
|
36
|
+
preserved: number;
|
|
37
|
+
deferred: number;
|
|
38
|
+
failed: number;
|
|
39
|
+
preservedByReason: Record<string, number>;
|
|
40
|
+
orphanScan: 'scanned' | 'unavailable' | 'not-requested';
|
|
41
|
+
unseenOutsideWindow?: number;
|
|
42
|
+
unseenOutsidePrefix?: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface HarnessGcResult {
|
|
46
|
+
outcome: 'ok' | 'skipped' | 'fail';
|
|
47
|
+
reason?: 'overlap' | 'no-target-repo' | 'repo-failed' | 'budget-exhausted';
|
|
48
|
+
repos: number;
|
|
49
|
+
removed: number;
|
|
50
|
+
preserved: number;
|
|
51
|
+
deferred: number;
|
|
52
|
+
failed: number;
|
|
53
|
+
unseenOutsideWindow?: number;
|
|
54
|
+
unseenOutsidePrefix?: number;
|
|
55
|
+
undiscoverable: number;
|
|
56
|
+
orphanScanUnavailable: number;
|
|
57
|
+
budgetMs: number;
|
|
58
|
+
elapsedMs: number;
|
|
59
|
+
repositories: HarnessGcRepo[];
|
|
60
|
+
repoFailures?: Array<{ gitDir: string; error: string }>;
|
|
61
|
+
/** The tick budget ran out inside a repository's queries; that repository was left unverified (cursor kept). */
|
|
62
|
+
budgetExhausted?: boolean;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Discover current linked worktrees; the tick also retains previously verified automatic targets. */
|
|
66
|
+
export function discoverHarnessGcRepos(root: string, expired: () => boolean = () => false):
|
|
67
|
+
{ repos: string[]; undiscoverable: number; stopped?: boolean } {
|
|
68
|
+
const repos = new Set<string>();
|
|
69
|
+
let undiscoverable = 0;
|
|
70
|
+
// The deadline is checked *between* reads (each readdir / .git read); a stopped walk leaves the rest unverified.
|
|
71
|
+
let stopped = false;
|
|
72
|
+
const halt = (): boolean => { if (!stopped && expired()) stopped = true; return stopped; };
|
|
73
|
+
const done = () => ({ repos: [...repos].sort(), undiscoverable, ...(stopped ? { stopped } : {}) });
|
|
74
|
+
const children = (path: string): string[] => readdirSync(path, { withFileTypes: true })
|
|
75
|
+
.filter((entry) => entry.isDirectory()).map((entry) => join(path, entry.name));
|
|
76
|
+
let scopes: string[];
|
|
77
|
+
if (halt()) return done();
|
|
78
|
+
try { scopes = children(root); }
|
|
79
|
+
catch { return { repos: [], undiscoverable: 0 }; }
|
|
80
|
+
for (const scope of scopes) {
|
|
81
|
+
if (halt()) return done();
|
|
82
|
+
let parents: string[];
|
|
83
|
+
try { parents = children(scope); }
|
|
84
|
+
catch { undiscoverable++; continue; }
|
|
85
|
+
for (const parent of parents) {
|
|
86
|
+
if (!parent.endsWith('.worktrees')) continue;
|
|
87
|
+
if (halt()) return done();
|
|
88
|
+
let paths: string[];
|
|
89
|
+
try { paths = children(parent); }
|
|
90
|
+
catch { undiscoverable++; continue; }
|
|
91
|
+
for (const path of paths) {
|
|
92
|
+
if (halt()) return done();
|
|
93
|
+
try {
|
|
94
|
+
const line = readFileSync(join(path, '.git'), 'utf8').trim();
|
|
95
|
+
const match = /^gitdir:\s*(.+?)[/\\]worktrees[/\\][^/\\]+\s*$/.exec(line);
|
|
96
|
+
if (!match) { undiscoverable++; continue; }
|
|
97
|
+
const gitDir = isAbsolute(match[1]!) ? match[1]! : resolve(path, match[1]!);
|
|
98
|
+
repos.add(resolve(gitDir));
|
|
99
|
+
} catch { undiscoverable++; }
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return done();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function defaultRun(args: string[]) {
|
|
107
|
+
const result = spawnSync('git', args, { encoding: 'utf8', timeout: 20_000 });
|
|
108
|
+
return { status: result.status, stdout: result.stdout ?? '', stderr: result.stderr ?? '' };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function positive(value: number | undefined, fallback: number): number {
|
|
112
|
+
if (value === undefined) return fallback;
|
|
113
|
+
if (!Number.isSafeInteger(value) || value <= 0) throw new Error(`invalid GC limit: ${value}`);
|
|
114
|
+
return value;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** One lock guards discovery, every repository window, deletion, and cursor writes. */
|
|
118
|
+
export function runHarnessGcTick(opts: HarnessGcOptions = {}, deps: HarnessGcDeps = {}): HarnessGcResult {
|
|
119
|
+
const windowSize = positive(opts.window, 150);
|
|
120
|
+
const maxRemove = positive(opts.maxRemove, 30);
|
|
121
|
+
const budgetMs = positive(opts.budgetMs, 240_000);
|
|
122
|
+
const now = deps.now ?? Date.now;
|
|
123
|
+
const started = now();
|
|
124
|
+
const state = join(deps.stateRoot ?? effectiveInstanceRoot(), 'harness-gc');
|
|
125
|
+
mkdirSync(state, { recursive: true });
|
|
126
|
+
const lockPath = join(state, 'tick.lock');
|
|
127
|
+
let holder = '';
|
|
128
|
+
let release: () => void;
|
|
129
|
+
try {
|
|
130
|
+
release = acquireLockSync(lockPath, { maxTries: 2, retryBusyMs: 1, breakDeadHolder: true,
|
|
131
|
+
staleMs: Math.max(2 * budgetMs, 600_000), onWait: (value) => { holder = value; } });
|
|
132
|
+
} catch (error) {
|
|
133
|
+
if (!String(error).includes('timed out')) throw error;
|
|
134
|
+
debug.log('harness.gc', 'tick-skipped', { reason: 'overlap', holder });
|
|
135
|
+
return { outcome: 'skipped', reason: 'overlap', repos: 0, removed: 0, preserved: 0, deferred: 0, failed: 0,
|
|
136
|
+
undiscoverable: 0, orphanScanUnavailable: 0, budgetMs, elapsedMs: now() - started, repositories: [] };
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
const runner = deps.run ?? defaultRun;
|
|
140
|
+
const cursorPath = join(state, 'cursor.json');
|
|
141
|
+
const targetsPath = join(state, 'targets.json');
|
|
142
|
+
let cursor: Record<string, number> = {};
|
|
143
|
+
let persistedTargets: string[] = [];
|
|
144
|
+
try {
|
|
145
|
+
const parsed: unknown = JSON.parse(readFileSync(cursorPath, 'utf8'));
|
|
146
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) cursor = parsed as Record<string, number>;
|
|
147
|
+
} catch { /* No cursor yet, or corrupt state: start at the beginning. */ }
|
|
148
|
+
try {
|
|
149
|
+
const parsed: unknown = JSON.parse(readFileSync(targetsPath, 'utf8'));
|
|
150
|
+
if (Array.isArray(parsed)) persistedTargets = parsed.filter((path): path is string =>
|
|
151
|
+
typeof path === 'string' && isAbsolute(path));
|
|
152
|
+
} catch { /* No persisted automatic targets yet. */ }
|
|
153
|
+
const discovery = opts.repos === undefined
|
|
154
|
+
? discoverHarnessGcRepos(deps.worktreeRoot ?? configuredWorktreeRoot(), () => now() - started >= budgetMs)
|
|
155
|
+
: { repos: opts.repos, undiscoverable: 0 };
|
|
156
|
+
// A verified automatic target survives removal of its last linked worktree; --repo never enrolls it.
|
|
157
|
+
const candidates = opts.repos === undefined
|
|
158
|
+
? [...new Set([...discovery.repos, ...persistedTargets])]
|
|
159
|
+
: discovery.repos;
|
|
160
|
+
const targets = new Set<string>();
|
|
161
|
+
const repoFailures: Array<{ gitDir: string; error: string }> = [];
|
|
162
|
+
let failed = 0;
|
|
163
|
+
// A discovery walk cut by the deadline: repositories it did not reach are unverified, not absent.
|
|
164
|
+
let discoveryBudgetExhausted = 'stopped' in discovery && discovery.stopped === true;
|
|
165
|
+
for (const repo of [...candidates].sort()) {
|
|
166
|
+
if (now() - started >= budgetMs) { discoveryBudgetExhausted = true; break; }
|
|
167
|
+
const common = gitStdout(['-C', repo, 'rev-parse', '--path-format=absolute', '--git-common-dir'], runner);
|
|
168
|
+
if (common.ok && common.value && isAbsolute(common.value)) {
|
|
169
|
+
const gitDir = resolve(common.value);
|
|
170
|
+
targets.add(gitDir);
|
|
171
|
+
if (!opts.dryRun && opts.repos === undefined) persistedTargets.push(gitDir);
|
|
172
|
+
} else { failed++; repoFailures.push({ gitDir: repo, error: 'git common directory unavailable' }); }
|
|
173
|
+
}
|
|
174
|
+
const repositories: HarnessGcRepo[] = [];
|
|
175
|
+
let removed = 0;
|
|
176
|
+
let preserved = 0;
|
|
177
|
+
let deferred = 0;
|
|
178
|
+
let unseenOutsideWindow: number | undefined = repoFailures.length || discoveryBudgetExhausted ? undefined : 0;
|
|
179
|
+
let unseenOutsidePrefix: number | undefined = repoFailures.length || discoveryBudgetExhausted ? undefined : 0;
|
|
180
|
+
let orphanScanUnavailable = 0;
|
|
181
|
+
let processedTargets = 0;
|
|
182
|
+
let budgetExhausted = false;
|
|
183
|
+
// maxRemove bounds deletion *attempts* (incl. partial/failed ones), not only successes.
|
|
184
|
+
let attempts = 0;
|
|
185
|
+
const overBudget = (): boolean => now() - started >= budgetMs;
|
|
186
|
+
const baseGh: GhRunner = deps.runGh ?? createGhRunner();
|
|
187
|
+
for (const gitDir of [...targets].sort()) {
|
|
188
|
+
if (overBudget()) { budgetExhausted = true; break; } // remaining repositories unprocessed — record it
|
|
189
|
+
processedTargets++;
|
|
190
|
+
const offset = Number.isSafeInteger(cursor[gitDir]) && cursor[gitDir]! >= 0 ? cursor[gitDir]! : 0;
|
|
191
|
+
// The deadline is enforced inside the clean too: once it passes, every further *read* (git query, PR lookup,
|
|
192
|
+
// safety measurement) fails fast instead of running on past the tick. Deletions are never cut mid-item —
|
|
193
|
+
// `shouldRemove` already refuses to start one after the deadline.
|
|
194
|
+
let budgetHit = false;
|
|
195
|
+
const isDeletion = (args: readonly string[]): boolean =>
|
|
196
|
+
(args.includes('worktree') && args.includes('remove')) || (args.includes('branch') && args.includes('-D'));
|
|
197
|
+
const run: GitRunner = (args) => {
|
|
198
|
+
if (!isDeletion(args) && overBudget()) { budgetHit = true; return { status: 1, stdout: '', stderr: 'harness gc tick budget exhausted' }; }
|
|
199
|
+
return runner(args[0] === '-C' ? args : ['-C', gitDir, ...args]);
|
|
200
|
+
};
|
|
201
|
+
const runGh: GhRunner = (args, options) => {
|
|
202
|
+
if (overBudget()) { budgetHit = true; return { status: 1, stdout: '', stderr: 'harness gc tick budget exhausted', error: 'budget' }; }
|
|
203
|
+
return baseGh(args, options);
|
|
204
|
+
};
|
|
205
|
+
try {
|
|
206
|
+
// gh's {owner}/{repo} placeholders refer to cwd, not this target repository.
|
|
207
|
+
const remote = gitStdout(['config', '--get', 'remote.origin.url'], run);
|
|
208
|
+
const identity = remote.ok ? /(?:github\.com[:/])([^/:]+)\/([^/]+?)(?:\.git)?\/?$/.exec(remote.value) : null;
|
|
209
|
+
if (!identity) throw new Error('target repository has no GitHub origin identity');
|
|
210
|
+
const result = execHarnessClean({ mode: 'merged', dryRun: opts.dryRun, repo: `${identity[1]}/${identity[2]}`,
|
|
211
|
+
window: { offset, size: windowSize }, forceWorktreeRemove: false, run, runGh, expired: overBudget,
|
|
212
|
+
activeDirectoryProvider: deps.activeDirectoryProvider,
|
|
213
|
+
assessWorktree: deps.assessWorktree,
|
|
214
|
+
readWorktreeProvenance: deps.readWorktreeProvenance,
|
|
215
|
+
shouldRemove: () => {
|
|
216
|
+
if (attempts >= maxRemove) return false; // count cap, not the time budget
|
|
217
|
+
if (overBudget()) { budgetExhausted = true; return false; } // deletion deferred by the time budget
|
|
218
|
+
attempts++;
|
|
219
|
+
return true;
|
|
220
|
+
} });
|
|
221
|
+
const plan = result.plan;
|
|
222
|
+
if (result.expired) budgetHit = true;
|
|
223
|
+
if (budgetHit) {
|
|
224
|
+
// Unverified, not failed: nothing is counted as preserved/failed and the cursor is kept for the next tick.
|
|
225
|
+
removed += result.removed.length;
|
|
226
|
+
failed += result.failed.length;
|
|
227
|
+
budgetExhausted = true;
|
|
228
|
+
debug.log('harness.gc', 'repo-budget-exhausted', { gitDir, removed: result.removed.length });
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
if (plan.unavailable) {
|
|
232
|
+
repoFailures.push({ gitDir, error: 'required clean query unavailable' });
|
|
233
|
+
failed++;
|
|
234
|
+
}
|
|
235
|
+
const pending = result.deferred?.length ?? 0;
|
|
236
|
+
const byReason: Record<string, number> = {};
|
|
237
|
+
for (const item of plan.preserve) byReason[item.reason] = (byReason[item.reason] ?? 0) + 1;
|
|
238
|
+
const repo: HarnessGcRepo = {
|
|
239
|
+
gitDir, offset: result.window!.offset, window: result.window!.size, total: result.window!.total,
|
|
240
|
+
removed: result.removed.length, preserved: plan.preserve.length, deferred: pending,
|
|
241
|
+
failed: result.failed.length, preservedByReason: byReason, orphanScan: plan.orphanScan,
|
|
242
|
+
...(plan.queryStatus?.branches && plan.queryStatus.worktrees
|
|
243
|
+
? { unseenOutsideWindow: result.window!.unseenOutsideWindow } : {}),
|
|
244
|
+
...(plan.queryStatus?.branches && plan.queryStatus.worktrees && plan.scope?.registeredWorktrees !== undefined
|
|
245
|
+
? { unseenOutsidePrefix: Math.max(0, plan.scope.registeredWorktrees - plan.scope.matchedWorktrees) } : {}),
|
|
246
|
+
};
|
|
247
|
+
repositories.push(repo);
|
|
248
|
+
debug.log('harness.gc', 'repo', repo);
|
|
249
|
+
removed += repo.removed;
|
|
250
|
+
preserved += repo.preserved;
|
|
251
|
+
deferred += repo.deferred;
|
|
252
|
+
failed += repo.failed;
|
|
253
|
+
if (repo.unseenOutsideWindow === undefined) unseenOutsideWindow = undefined;
|
|
254
|
+
else if (unseenOutsideWindow !== undefined) unseenOutsideWindow += repo.unseenOutsideWindow;
|
|
255
|
+
if (repo.unseenOutsidePrefix === undefined) unseenOutsidePrefix = undefined;
|
|
256
|
+
else if (unseenOutsidePrefix !== undefined) unseenOutsidePrefix += repo.unseenOutsidePrefix;
|
|
257
|
+
if (plan.orphanScan === 'unavailable') orphanScanUnavailable++;
|
|
258
|
+
if (!opts.dryRun && repo.deferred === 0 && repo.failed === 0 && !plan.unavailable) {
|
|
259
|
+
const next = repo.offset + repo.window - repo.removed;
|
|
260
|
+
cursor[gitDir] = next >= repo.total - repo.removed ? 0 : Math.max(0, next);
|
|
261
|
+
}
|
|
262
|
+
} catch (error) {
|
|
263
|
+
if (budgetHit) {
|
|
264
|
+
budgetExhausted = true;
|
|
265
|
+
debug.log('harness.gc', 'repo-budget-exhausted', { gitDir, error: String(error) });
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
failed++;
|
|
269
|
+
unseenOutsideWindow = undefined;
|
|
270
|
+
unseenOutsidePrefix = undefined;
|
|
271
|
+
repoFailures.push({ gitDir, error: String(error) });
|
|
272
|
+
debug.log('harness.gc', 'repo-failed', { gitDir, error: String(error) }, { level: 'error' });
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (processedTargets < targets.size || budgetExhausted) {
|
|
276
|
+
unseenOutsideWindow = undefined;
|
|
277
|
+
unseenOutsidePrefix = undefined;
|
|
278
|
+
}
|
|
279
|
+
if (!opts.dryRun && targets.size) {
|
|
280
|
+
const temp = join(state, `cursor.${process.pid}.tmp`);
|
|
281
|
+
writeFileSync(temp, JSON.stringify(cursor));
|
|
282
|
+
renameSync(temp, cursorPath);
|
|
283
|
+
}
|
|
284
|
+
if (!opts.dryRun && opts.repos === undefined && persistedTargets.length) {
|
|
285
|
+
const temp = join(state, `targets.${process.pid}.tmp`);
|
|
286
|
+
writeFileSync(temp, JSON.stringify([...new Set(persistedTargets)].sort()));
|
|
287
|
+
renameSync(temp, targetsPath);
|
|
288
|
+
}
|
|
289
|
+
// A tick whose budget ran out before any repository was verified did not succeed — it is «skipped», never «ok».
|
|
290
|
+
const outcome = (targets.size === 0 && !discoveryBudgetExhausted) || failed > 0 ? 'fail'
|
|
291
|
+
: repositories.length === 0 ? 'skipped' : 'ok';
|
|
292
|
+
const summary: HarnessGcResult = { outcome,
|
|
293
|
+
...(outcome === 'fail' ? { reason: targets.size === 0 ? 'no-target-repo' as const : 'repo-failed' as const }
|
|
294
|
+
: outcome === 'skipped' ? { reason: 'budget-exhausted' as const } : {}),
|
|
295
|
+
repos: repositories.length, removed, preserved, deferred, failed,
|
|
296
|
+
...(unseenOutsideWindow === undefined ? {} : { unseenOutsideWindow }),
|
|
297
|
+
...(unseenOutsidePrefix === undefined ? {} : { unseenOutsidePrefix }),
|
|
298
|
+
undiscoverable: discovery.undiscoverable, orphanScanUnavailable, budgetMs,
|
|
299
|
+
elapsedMs: now() - started, repositories,
|
|
300
|
+
...(repoFailures.length ? { repoFailures } : {}),
|
|
301
|
+
...(budgetExhausted || discoveryBudgetExhausted ? { budgetExhausted: true } : {}) };
|
|
302
|
+
debug.log('harness.gc', 'tick', summary);
|
|
303
|
+
return summary;
|
|
304
|
+
} finally { release(); }
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function cliArgs(args: string[]): HarnessGcOptions {
|
|
308
|
+
const opts: HarnessGcOptions = {};
|
|
309
|
+
for (let i = 0; i < args.length; i++) {
|
|
310
|
+
const arg = args[i];
|
|
311
|
+
if (arg === '--json') continue;
|
|
312
|
+
if (arg === '--dry-run') { opts.dryRun = true; continue; }
|
|
313
|
+
if (arg === '--repo') { (opts.repos ??= []).push(args[++i] ?? ''); continue; }
|
|
314
|
+
const key = arg === '--window' ? 'window' : arg === '--max-remove' ? 'maxRemove' : arg === '--budget-ms' ? 'budgetMs' : null;
|
|
315
|
+
if (!key) throw new Error(`unknown GC option: ${arg}`);
|
|
316
|
+
opts[key] = Number(args[++i]);
|
|
317
|
+
}
|
|
318
|
+
if (opts.repos?.some((repo) => !repo)) throw new Error('--repo needs a path');
|
|
319
|
+
return opts;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (import.meta.main) {
|
|
323
|
+
try {
|
|
324
|
+
const result = runHarnessGcTick(cliArgs(process.argv.slice(2)));
|
|
325
|
+
console.log(JSON.stringify(result));
|
|
326
|
+
if (result.outcome === 'fail') process.exitCode = 1;
|
|
327
|
+
} catch (error) {
|
|
328
|
+
console.log(JSON.stringify({ outcome: 'fail', reason: String(error) }));
|
|
329
|
+
process.exitCode = 1;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
@@ -299,8 +299,10 @@ export async function stopHarnessRun(runId: string, deps: HarnessStopDeps, conte
|
|
|
299
299
|
const targets = dryRun ? [] : contexts && contexts.length > 0 ? [...contexts] : deps.kubeContexts();
|
|
300
300
|
let jobsDeleted = 0;
|
|
301
301
|
for (const context of targets) {
|
|
302
|
-
|
|
303
|
-
|
|
302
|
+
// `-o name` → «job.batch/<name>» 한 줄씩. 맨 출력은 «job.batch "<name>" deleted» 라 «job.batch/» 로만 세면
|
|
303
|
+
// 지워도 늘 0 이었다(10-11 · 여섯 번 «0개 지움» 뒤 Pod Terminating). 두 꼴을 다 센다.
|
|
304
|
+
const r = deps.kubectl(['--context', context, '-n', deps.namespace, 'delete', 'job', '-l', `elanous.run=${runId}`, '--wait=false', '-o', 'name']);
|
|
305
|
+
if (r.status === 0) jobsDeleted += r.stdout.split('\n').filter((line) => /^job\.batch(?:\/|\s+")/.test(line.trim())).length;
|
|
304
306
|
else debug.log('harness.stop', 'job-delete-failed', { runId, context, error: r.stderr.slice(0, 200) }, { level: 'warn' });
|
|
305
307
|
}
|
|
306
308
|
const result: HarnessStopResult = { runId, ...(given !== runId ? { givenRunId: given } : {}), process: processState, ...(record ? { pid: record.pid } : {}), candidates, scanned: scan.status === 'ok', pidRecordFound: record !== null, dryRun, jobsDeleted, contexts: targets };
|