elanous 0.2.14 → 0.2.16
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/{1307-6e7e0377c1db0bdc.js → 1307-b46d45b705e8c9ae.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +1 -0
- package/apps/pwa/out/_next/static/chunks/899-46946fe8363e1262.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9103-7a3ae7f60af3b99f.js → 9103-60fbef33e66f0a46.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-79c3ed84baaedd03.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-7b2b4f378354869a.js → page-3cb52e5ea8fc2fa5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-a424ef80885d1743.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-c1e20d7d338713f1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/{page-1512d91c7491ed62.js → page-482c5dbde699947c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-1e962b31d1f11081.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-a627db94ec6904a0.js → page-8c2d9526164f5d09.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-ffff6d819fbc6e0f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/{page-e6b84ab07d3567d5.js → page-a0e2a6d1bfb52fc5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-c53598cfce6156f2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/{page-fed69f9759d248d0.js → page-13a63953f53c6a23.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-15374909d8f1aebd.js → page-5d3c8429d04f3831.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-012a742f81218b01.js → page-3193bdc0bbab047a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-76270efaee638f17.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-99effd9d893a43dc.js → page-6107ca8ddd5e3bdd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{layout-938de57ae0315e46.js → layout-dce686e582c23937.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-d3d53b146416e422.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/{page-160089123a40dd91.js → page-1a61c7ca1ceb3ae7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/market/{page-629e1ede482ec6eb.js → page-51f811fa119308b6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-dacff46049e1557e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-27e99b4e54a849d4.js → page-4167348049166852.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-de52e8fdf9095f7a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-36a7d994f4ca0299.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-32cde713be47cddb.js → page-ca3952cd78a83ca2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/{page-e7e10cd3386222e5.js → page-5b042736aa113756.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-acb749f1e432bcf3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-cd78862707999911.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-54b9ef8afe6278fd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/{page-e64caff084f7ce21.js → page-1ebfe049c640e171.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-db07eaffba0df7e3.js → page-57924393e3e4e539.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-9e0ce6c8129fa641.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-31fa1ce4c4aff9ae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-86f91034bd5cabe5.js → page-0f5cd57ca928f42b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-abe8e3b74bea1ffc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-4364ef9f4e7c7f0f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/{page-ce4d8a1215ce53a3.js → page-d28573d6792b4275.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/{page-58043db78ba519ae.js → page-59f03fda76c03d22.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/{page-357750dfb91bf960.js → page-d751de5f8062ff5e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/term/{page-274703bc41b71333.js → page-fe1759f94003b32b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-d64c984e855c8c68.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-86914b0dee8ab7f8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/{page-3b7e821dc492bdfd.js → page-9afdee66b820c76e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d4aef67abf264874.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/{page-f8da5dc6f3752276.js → page-1ea64acadd704ace.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/{page-2f810b18d89ecba6.js → page-3544f154c87b43ee.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-30c2073bfc21c004.js +1 -0
- package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +1 -0
- package/apps/pwa/out/_next/static/css/{04dabcbcc4bfa6db.css → 085673d826b98b62.css} +1 -1
- package/apps/pwa/out/_next/static/rv9cierV0jSOvDBm1g0XB/_buildManifest.js +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +15 -15
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +15 -15
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +15 -15
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +15 -15
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +15 -15
- package/apps/pwa/out/ceo/index.html +1 -0
- package/apps/pwa/out/ceo/index.txt +33 -0
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +15 -15
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +15 -15
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +15 -15
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +15 -15
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +15 -15
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +15 -15
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +15 -15
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +15 -15
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +15 -15
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +15 -15
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +15 -15
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +15 -15
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +15 -15
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +15 -15
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +15 -15
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +15 -15
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +15 -15
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +15 -15
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +15 -15
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +15 -15
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +16 -16
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +15 -15
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +15 -15
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +15 -15
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +15 -15
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +15 -15
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +15 -15
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +15 -15
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +15 -15
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +15 -15
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +15 -15
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +15 -15
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +15 -15
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +15 -15
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +15 -15
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +15 -15
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +15 -15
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +15 -15
- package/graphs/doc-gen/doc-gen.yaml +24 -0
- package/graphs/doc-gen/recipes.yaml +3 -0
- package/graphs/heal/heal-loop.yaml +1 -1
- package/graphs/implement-loop.yaml +1 -1
- package/graphs/orchestrator/orchestrator.yaml +7 -0
- package/graphs/orchestrator/recipes.yaml +3 -0
- package/graphs/release/recipes.yaml +3 -0
- package/graphs/release/release-loop.yaml +4 -2
- package/graphs/steward/recipes.yaml +2 -1
- package/package.json +1 -1
- package/scripts/brand/check.ts +7 -5
- package/scripts/ci-public-export-import-gate.ts +154 -0
- package/scripts/ci-test-interference-gate.ts +5 -1
- package/scripts/ci-typecheck-changed.ts +82 -31
- package/scripts/docs/doc-rot.ts +164 -0
- package/scripts/docs/generate-graph-anatomy.ts +437 -0
- package/scripts/docs-cli-check.ts +4 -4
- package/scripts/expose-rubric-suggest.ts +18 -12
- package/scripts/goals/goal-census.ts +343 -0
- package/scripts/lecture-note/node.ts +31 -11
- package/scripts/lessons/handbook-page.ts +30 -3
- package/scripts/marketing/generate-ax-brief.ts +140 -0
- package/scripts/marketing/generate-feature-map.ts +118 -0
- package/scripts/measure/author-section-contribution.ts +84 -0
- package/scripts/release-loop/checklist-gate-node.ts +53 -14
- package/scripts/release-loop/cut-branch.ts +43 -13
- package/scripts/release-loop/gate-node.ts +82 -14
- package/scripts/release-loop/vault-note-node.ts +257 -8
- package/scripts/release-story/draft.ts +57 -9
- package/scripts/rhythm/daily.ts +263 -76
- package/scripts/rhythm/monthly.ts +97 -0
- package/scripts/rhythm/weekly.ts +30 -9
- package/scripts/smart-merge/resync-shadow.ts +147 -0
- package/src/acp/client.ts +17 -2
- package/src/agent-env/profile.ts +161 -0
- package/src/agent-mission/driver.ts +51 -1
- package/src/agent-mission/supervisor-metrics-cli.ts +40 -0
- package/src/agent-mission/supervisor-metrics.ts +218 -0
- package/src/agent-substrate/pr-reviewer.ts +2 -0
- package/src/ask-user-question/pending-questions.ts +5 -0
- package/src/auth/github-app-token.ts +112 -13
- package/src/autopilot/build/llm-conflict-merge.ts +32 -6
- package/src/autopilot/track-agent-shadow.ts +2 -1
- package/src/briefing/brief-items.ts +276 -0
- package/src/budget/usage-outcomes.ts +204 -0
- package/src/claims/claims-render.ts +14 -5
- package/src/cli/agent-env-cli.ts +21 -0
- package/src/cli/brief-cli.ts +73 -0
- package/src/cli/decisions-cli.ts +9 -0
- package/src/cli/doctor-cli.ts +23 -1
- package/src/cli/doctor-node-modules.ts +147 -0
- package/src/cli/intake-cli.ts +46 -17
- package/src/cli/know-cli.ts +20 -0
- package/src/cli/logs-cli.ts +43 -1
- package/src/cli/pod-cli.ts +2 -0
- package/src/cli/pr-cli.ts +80 -12
- package/src/cli/release-cli.ts +66 -29
- package/src/cli/seat-requests-cli.ts +52 -0
- package/src/cli/self-update.ts +200 -1
- package/src/cli/usage-cli.ts +26 -0
- package/src/context-bus/context-now-public.ts +16 -6
- package/src/context-bus/context-now.ts +3 -1
- package/src/context-bus/coord-events.ts +6 -4
- package/src/decisions/decision-cards.ts +33 -7
- package/src/decisions/proact-meter.ts +286 -0
- package/src/decisions/proact1-lite.ts +86 -0
- package/src/decisions/web-push-decision-cards.ts +1 -1
- package/src/discord-self-message.ts +9 -0
- package/src/domains/alert-audit.ts +271 -0
- package/src/domains/core-tools.ts +4 -3
- package/src/domains/ops-facts-tool.ts +31 -1
- package/src/flow-loop/split.ts +24 -0
- package/src/harness/harness-cli-command.ts +147 -17
- package/src/harness/harness-incidents.ts +2 -0
- package/src/harness/harness-pod-dispatch.ts +67 -8
- package/src/harness/harness-queue.ts +380 -50
- package/src/harness/harness-seams.ts +13 -3
- package/src/harness/helper-repair.ts +218 -0
- package/src/harness/helper-scan.ts +27 -4
- package/src/harness/launch-stamp.ts +236 -0
- package/src/harness/ledger-write-guard-child.fixture.ts +12 -0
- package/src/harness/ledger-write-guard.ts +35 -0
- package/src/harness/nested-elanous-policy.ts +114 -0
- package/src/harness/pod-memory-policy.ts +15 -0
- package/src/harness/repository-name.ts +55 -0
- package/src/hq/fence-audit.ts +90 -0
- package/src/hq/hq.ts +70 -6
- package/src/hq/lease.ts +9 -7
- package/src/hq/move-plan.ts +126 -0
- package/src/hq/promote.ts +31 -0
- package/src/hq/transfer.ts +185 -0
- package/src/index.ts +189 -13
- package/src/intake-plane/check.ts +63 -0
- package/src/intake-plane/digest.ts +65 -21
- package/src/intake-plane/front-classifier.ts +6 -0
- package/src/intake-plane/items.ts +3 -2
- package/src/intake-plane/lens.ts +177 -0
- package/src/intake-plane/route.ts +145 -1
- package/src/knowledge/know-find.ts +105 -0
- package/src/llm.ts +75 -19
- package/src/loops/budget.ts +36 -5
- package/src/loops/checker.ts +2 -2
- package/src/loops/orchestrator/author-depth.ts +65 -9
- package/src/loops/orchestrator/author-input-check.ts +8 -3
- package/src/loops/orchestrator/author-ledger.ts +3 -1
- package/src/loops/orchestrator/finish-rate.ts +14 -3
- package/src/loops/orchestrator/seat-share.ts +56 -0
- package/src/loops/orchestrator/tick.ts +107 -29
- package/src/loops/orchestrator/traffic.ts +117 -11
- package/src/loops/registry.ts +142 -61
- package/src/maturity/bot-slash-catalog.ts +11 -2
- package/src/maturity/feature-maturity.ts +4 -3
- package/src/maturity/tui-slash-availability.ts +40 -0
- package/src/mss/logging/log-store.ts +35 -4
- package/src/nexus/api/http-server.ts +28 -14
- package/src/nexus/api/loop-edges.ts +173 -0
- package/src/nexus/api/schedules-read.ts +13 -0
- package/src/nexus/api/seat-requests.ts +79 -56
- package/src/oauth/overload-failover-launch.ts +75 -0
- package/src/oauth/overload-failover.ts +137 -0
- package/src/prompt-enhance/enhance.ts +6 -1
- package/src/release-loop/auto-start.ts +6 -3
- package/src/release-loop/checklist.ts +19 -5
- package/src/release-loop/feature-store.ts +59 -31
- package/src/release-loop/merged-pr-checklist.ts +125 -0
- package/src/release-loop/placement.ts +98 -29
- package/src/seat-dispatch/seat-ask.ts +33 -6
- package/src/seat-dispatch/seat-request-eligibility.ts +19 -0
- package/src/seat-dispatch/seat-request-ledger.ts +133 -0
- package/src/seat-loop/seat-loop.ts +215 -12
- package/src/self-dev/ask-launch-flow.ts +22 -1
- package/src/self-dev/authored-goal-trace.ts +64 -0
- package/src/self-dev/dev-cli.ts +19 -1
- package/src/self-dev/dev-pipeline.ts +14 -9
- package/src/self-dev/draft-sweep.ts +90 -10
- package/src/self-dev/draft-triage-rules.ts +25 -8
- package/src/self-dev/entrance-registry.ts +10 -6
- package/src/self-dev/old-door.ts +73 -0
- package/src/self-dev/orchestrate.ts +15 -1
- package/src/self-dev/review-provider-fallback.ts +6 -1
- package/src/self-dev/run-supervisor.ts +49 -2
- package/src/self-implement/app-api-budget.ts +166 -0
- package/src/self-implement/budget-gate.ts +7 -5
- package/src/self-implement/follow-up-goals.ts +289 -0
- package/src/self-implement/frozen-merges.ts +3 -2
- package/src/self-implement/goal-author.ts +50 -6
- package/src/self-implement/goal-clarification-escalation.ts +5 -0
- package/src/self-implement/heal-intake.ts +120 -2
- package/src/self-implement/heal-triage.ts +44 -0
- package/src/self-implement/host-regate.ts +9 -2
- package/src/self-implement/l8-merge-queue.ts +132 -0
- package/src/self-implement/orchestrator.ts +325 -47
- package/src/self-implement/pipeline-shape.ts +1 -1
- package/src/self-implement/revert-guard.ts +42 -7
- package/src/self-implement/run-ledger.ts +6 -0
- package/src/self-implement/run-status-mapping.ts +2 -0
- package/src/self-implement/seams.ts +45 -16
- package/src/self-implement/self-implement-cli.ts +1 -0
- package/src/self-implement/speed-claims.ts +338 -0
- package/src/session-runtime/index.ts +2 -0
- package/src/session-runtime/retry-api.ts +87 -0
- package/src/session-runtime/retry-policy.ts +107 -6
- package/src/steward/triage.ts +74 -3
- package/src/steward/working-backwards.ts +20 -2
- package/src/task-cards/card-cli.ts +25 -0
- package/src/task-cards/card-store.ts +5 -3
- package/src/task-orchestrator/surfaces/pod-artifact-return.ts +63 -48
- package/src/task-orchestrator/surfaces/pod-command-job.ts +84 -9
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +48 -0
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +4 -3
- package/src/task-orchestrator/surfaces/pod-ledger-prod-sink.ts +75 -0
- package/src/task-orchestrator/surfaces/pod-pool.ts +70 -9
- package/src/task-orchestrator/surfaces/pod-source-receive.ts +37 -1
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +166 -46
- package/src/task-orchestrator/surfaces/self-implement.ts +7 -1
- package/src/telegram-commands.ts +5 -0
- package/src/telegram.ts +72 -4
- package/src/user-config.ts +64 -7
- package/src/version/packed-revision.json +1 -1
- package/src/workflow-runtime/nodes/bash.ts +9 -1
- package/apps/pwa/out/_next/static/chunks/4562-071d8bac44a01069.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5977-2df489622a52a33a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-74383f890f075511.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-c496bb16da85385f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-1abfe3aec3be6bdc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-fb260b9283dbf646.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-61ec1d2e92bdd50a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-fd7e200d4d83fbd7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-d47266766766d227.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-2ce5471926761fdd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-e995d30f6800b6bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-39f89c53038a51d4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-5a753bb4ec0cc511.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-04250f0e2c0d03df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-341480ed8e3c1ea8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-8ac8d71c2adf9c8a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-e1e47d8d940aba3d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-94dc383d3d54db1a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-b3759cc14da80908.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-194dce4a83143fce.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-35bdeae2e2c17147.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-16bef3da05a900c3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-134c1bc762aee62c.js +0 -1
- package/apps/pwa/out/_next/static/css/2e33713c444d5b39.css +0 -1
- package/apps/pwa/out/_next/static/x6mjI6vqOkVVu0jFuDkz1/_buildManifest.js +0 -1
- /package/apps/pwa/out/_next/static/{x6mjI6vqOkVVu0jFuDkz1 → rv9cierV0jSOvDBm1g0XB}/_ssgManifest.js +0 -0
package/src/cli/release-cli.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
// elanous release verify [--version <x.y.z>]
|
|
7
7
|
// elanous release notes --from <ref> [--to <ref>]
|
|
8
8
|
// elanous release run --version <x.y.z> [--cut-commit <sha>] [--dry-run] [--json] [--if-ready]
|
|
9
|
+
// elanous release auto-start [--window-minutes <n>] [--apply] [--json]
|
|
9
10
|
// elanous release cut-branch --version <x.y.z> --base <sha> --pick <sha>... [--dry-run]
|
|
10
11
|
//
|
|
11
12
|
// 🩸 계기(2026-09-25 v0.1.0 첫 공개): 손으로 밟은 절차에서 둘을 빠뜨릴 뻔했다 — 공개본 git 커밋(판 커밋이 공개본 커밋이 된다) ·
|
|
@@ -38,6 +39,7 @@ import { runUnattendedRelease, type UnattendedReleaseDeps } from '../../scripts/
|
|
|
38
39
|
import { cutReleaseBranch } from '../../scripts/release-loop/cut-branch.js';
|
|
39
40
|
import { releaseReadiness } from '../../scripts/release-loop/release-readiness.js';
|
|
40
41
|
import { runReleaseIfReady } from './release-run-if-ready.js';
|
|
42
|
+
import { autoStartScheduledRelease, type AutoStartDeferral } from '../release-loop/auto-start.js';
|
|
41
43
|
import { landingFreezeMessage, LandingFrozenError, readLandingFreeze } from '../release-loop/landing-freeze.js';
|
|
42
44
|
|
|
43
45
|
export const DEFAULT_PUBLIC_REPO = 'ElanvitalAI/elanous';
|
|
@@ -498,7 +500,7 @@ async function checklistOutput(v: string, codenames: Record<string, string>, mod
|
|
|
498
500
|
console.log(`⚠ 짝 경고 ${parity.length}: ${parity.map(({ id }) => id).join(', ') || '없음'}`);
|
|
499
501
|
console.log(`${v}${result.codename ? ` (${result.codename})` : ''} · 공개 ${data.released || '없음'} · 개발 ${data.dev}`);
|
|
500
502
|
if (mode === 'list') {
|
|
501
|
-
for (const item of data.items) console.log(`${({ green: '🟢', yellow: '🟡', red: '🔴', done: '✅' } as const)[item.status]} ${item.id} ${item.title}${item.owner ? ` · ${item.owner}` : ''}`);
|
|
503
|
+
for (const item of data.items) console.log(`${({ green: '🟢', yellow: '🟡', red: '🔴', done: '✅' } as const)[item.status]} ${item.id} ${item.title}${item.owner ? ` · ${item.owner}` : ''}${item.ceoMinutes !== undefined ? ` · 대표 손 ${item.ceoMinutes}분${item.ceoDate ? ` (${item.ceoDate})` : ''}` : ''}`);
|
|
502
504
|
} else {
|
|
503
505
|
console.log(`🟢 ${summary.green} · 🟡 ${summary.yellow} · 🔴 ${summary.red} · ✅ ${summary.done}`);
|
|
504
506
|
console.log(`🔴 칸: ${summary.blocked.join(', ') || '없음'}`);
|
|
@@ -629,7 +631,7 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
|
|
|
629
631
|
if (!opts.dryRun && !mayWriteLedger('release place', false, scheduleLedgerRoot())) return;
|
|
630
632
|
const priority = opts.priority ?? item.priority;
|
|
631
633
|
if (!priority) throw new CliUserError(`우선순위가 없는 칸: ${id} — --priority P0|P1|P2 를 지정하라`);
|
|
632
|
-
const result = placeCell({ id, title: item.title, owner: item.owner, priority: priority as PlacementPriority, predecessors: item.predecessors ?? [], deadlineVersion: item.deadlineVersion }, { schedules: rows, dryRun: opts.dryRun, by: who ?? 'cli' });
|
|
634
|
+
const result = placeCell({ id, title: item.title, owner: item.owner, priority: priority as PlacementPriority, predecessors: item.predecessors ?? [], deadlineVersion: item.deadlineVersion, ceoMinutes: item.ceoMinutes, ceoDate: item.ceoDate, ...(item.accelerator === true ? { accelerator: true } : {}) }, { schedules: rows, dryRun: opts.dryRun, by: who ?? 'cli' });
|
|
633
635
|
console.log(`${opts.dryRun ? '· 드라이런' : '✅'} ${id} ${result.from ?? '-'} → ${result.version} · ${result.reason}${result.displaced.length ? ` · P2 이월 ${result.displaced.map((row) => row.id).join(', ')}` : ''}`);
|
|
634
636
|
});
|
|
635
637
|
release.command('rebalance').description('마감 2시간 전 미시작 칸을 다음 판으로')
|
|
@@ -637,10 +639,15 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
|
|
|
637
639
|
.action((opts: { version: string; dryRun?: boolean }) => {
|
|
638
640
|
if (process.env.ELANOUS_TRACK !== 'OP' && !opts.dryRun) throw new CliUserError('판 이월은 COO 에 요청');
|
|
639
641
|
if (!opts.dryRun && !mayWriteLedger('release rebalance', false, scheduleLedgerRoot())) return;
|
|
640
|
-
const decisions = rebalance(opts.version, { schedules: listSchedules(scheduleLedgerRoot()), dryRun: opts.dryRun });
|
|
642
|
+
const { decisions, blocked } = rebalance(opts.version, { schedules: listSchedules(scheduleLedgerRoot()), dryRun: opts.dryRun });
|
|
641
643
|
for (const row of decisions) console.log(`${opts.dryRun ? '· 드라이런' : '✅'} ${row.id} ${row.from} → ${row.version} · ${row.reason}`);
|
|
642
|
-
|
|
644
|
+
for (const row of blocked) console.log(`⛔ ${row.id} ${row.from} → ${row.to} 이월 거부 · ${row.reason}`);
|
|
645
|
+
if (!decisions.length && !blocked.length) console.log('이월할 미시작 칸 없음');
|
|
643
646
|
});
|
|
647
|
+
const ceoMinutes = (value: string): number => {
|
|
648
|
+
if (!/^\d+$/.test(value) || !Number.isSafeInteger(Number(value))) throw new CliUserError('대표 손 분량은 0 이상의 정수 분이어야 한다');
|
|
649
|
+
return Number(value);
|
|
650
|
+
};
|
|
644
651
|
const checklist = release.command('checklist').description('판별 확인표 조회·갱신')
|
|
645
652
|
.option('--version <v>', '판 또는 별칭(기본: package.json 의 개발판에서 -dev.N 제거)')
|
|
646
653
|
.option('--json', '결과 JSON')
|
|
@@ -732,24 +739,28 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
|
|
|
732
739
|
});
|
|
733
740
|
withContext(checklist.command('add <id> <title>').description('칸 추가')).option('--owner <owner>', '담당').option('--kind <kind>', 'screen = 다섯 화면 짝 칸 · 근거에 짝: PWA … · 데스크톱 … · 폴드 … · 아이폰 … · 아이패드 … 한 줄')
|
|
734
741
|
.option('--priority <priority>', 'P0|P1|P2').option('--deadline-version <v>', '마감 판').option('--predecessor <id...>', '선행 칸 id')
|
|
742
|
+
.option('--ceo-minutes <minutes>', '대표 손 분량(분)').option('--ceo-date <date>', '대표 손 날짜 YYYY-MM-DD (없으면 판 착지일 KST)')
|
|
743
|
+
.option('--accelerator', '가속 등급 — 다른 칸의 처리량을 올리는 칸(자율성·효율성·동시성). 생략하면 등급 없음')
|
|
735
744
|
.option('--allow-duplicate-id', '다른 판에 같은 id 가 있어도 경고 후 추가')
|
|
736
|
-
.action((id: string, title: string, opts: { owner?: string; kind?: string; priority?: string; deadlineVersion?: string; predecessor?: string[]; allowDuplicateId?: boolean }, cmd: Command) => {
|
|
745
|
+
.action((id: string, title: string, opts: { owner?: string; kind?: string; priority?: string; deadlineVersion?: string; predecessor?: string[]; ceoMinutes?: string; ceoDate?: string; accelerator?: boolean; allowDuplicateId?: boolean }, cmd: Command) => {
|
|
737
746
|
const { version, json } = context(cmd);
|
|
738
747
|
if (!mayWrite(cmd)) return;
|
|
739
|
-
const data = addItem(version, { id, title, ...(opts.owner !== undefined ? { owner: opts.owner } : {}), ...(opts.kind !== undefined ? { kind: opts.kind as ChecklistKind } : {}), ...(opts.priority !== undefined ? { priority: opts.priority as PlacementPriority } : {}), ...(opts.deadlineVersion !== undefined ? { deadlineVersion: opts.deadlineVersion } : {}), ...(opts.predecessor !== undefined ? { predecessors: opts.predecessor } : {}) }, { allowDuplicateId: opts.allowDuplicateId });
|
|
748
|
+
const data = addItem(version, { id, title, ...(opts.owner !== undefined ? { owner: opts.owner } : {}), ...(opts.kind !== undefined ? { kind: opts.kind as ChecklistKind } : {}), ...(opts.priority !== undefined ? { priority: opts.priority as PlacementPriority } : {}), ...(opts.deadlineVersion !== undefined ? { deadlineVersion: opts.deadlineVersion } : {}), ...(opts.predecessor !== undefined ? { predecessors: opts.predecessor } : {}), ...(opts.ceoMinutes !== undefined ? { ceoMinutes: ceoMinutes(opts.ceoMinutes) } : {}), ...(opts.ceoDate !== undefined ? { ceoDate: opts.ceoDate } : {}), ...(opts.accelerator ? { accelerator: true } : {}) }, { allowDuplicateId: opts.allowDuplicateId });
|
|
740
749
|
if (json) console.log(JSON.stringify(data)); else console.log(`✅ ${id} 추가`);
|
|
741
750
|
});
|
|
742
751
|
withContext(checklist.command('set <id>').description('칸 상태·근거·담당·처분 갱신'))
|
|
743
752
|
.option('--status <status>', 'green|yellow|red|done').option('--evidence <evidence>', '근거').option('--owner <owner>', '담당')
|
|
744
753
|
.option('--disposition <disposition>', 'move|known-issue|block').option('--kind <kind>', 'screen = 다섯 화면 짝 칸 · 근거에 짝: PWA … · 데스크톱 … · 폴드 … · 아이폰 … · 아이패드 … 한 줄')
|
|
745
754
|
.option('--priority <priority>', 'P0|P1|P2').option('--deadline-version <v>', '마감 판').option('--predecessor <id...>', '선행 칸 id')
|
|
746
|
-
.
|
|
755
|
+
.option('--ceo-minutes <minutes>', '대표 손 분량(분)').option('--ceo-date <date>', '대표 손 날짜 YYYY-MM-DD')
|
|
756
|
+
.option('--accelerator', '가속 등급 부여').option('--no-accelerator', '가속 등급 해제(기본은 없음)')
|
|
757
|
+
.action((id: string, opts: { status?: string; evidence?: string; owner?: string; disposition?: string; kind?: string; priority?: string; deadlineVersion?: string; predecessor?: string[]; ceoMinutes?: string; ceoDate?: string; accelerator?: boolean }, cmd: Command) => {
|
|
747
758
|
const { version, json } = context(cmd);
|
|
748
759
|
if (opts.status !== undefined && !['green', 'yellow', 'red', 'done'].includes(opts.status)) throw new CliUserError(`잘못된 상태: ${opts.status}`, 'green|yellow|red|done');
|
|
749
760
|
if (opts.disposition !== undefined && !['move', 'known-issue', 'block'].includes(opts.disposition)) throw new CliUserError(`잘못된 처분: ${opts.disposition}`, 'move|known-issue|block');
|
|
750
|
-
if (opts.status === undefined && opts.evidence === undefined && opts.owner === undefined && opts.disposition === undefined && opts.kind === undefined && opts.priority === undefined && opts.deadlineVersion === undefined && opts.predecessor === undefined) throw new CliUserError('갱신할 칸을 지정하라', '--status · --evidence · --owner · --disposition · --kind · --priority · --deadline-version · --predecessor 중 하나');
|
|
761
|
+
if (opts.status === undefined && opts.evidence === undefined && opts.owner === undefined && opts.disposition === undefined && opts.kind === undefined && opts.priority === undefined && opts.deadlineVersion === undefined && opts.predecessor === undefined && opts.ceoMinutes === undefined && opts.ceoDate === undefined && opts.accelerator === undefined) throw new CliUserError('갱신할 칸을 지정하라', '--status · --evidence · --owner · --disposition · --kind · --priority · --deadline-version · --predecessor · --ceo-minutes · --ceo-date · --accelerator · --no-accelerator 중 하나');
|
|
751
762
|
if (!mayWrite(cmd)) return;
|
|
752
|
-
const data = setItem(version, id, { ...(opts.status !== undefined ? { status: opts.status as ChecklistStatus } : {}), ...(opts.evidence !== undefined ? { evidence: opts.evidence } : {}), ...(opts.owner !== undefined ? { owner: opts.owner } : {}), ...(opts.disposition !== undefined ? { disposition: opts.disposition as ChecklistDisposition } : {}), ...(opts.kind !== undefined ? { kind: opts.kind as ChecklistKind } : {}), ...(opts.priority !== undefined ? { priority: opts.priority as PlacementPriority } : {}), ...(opts.deadlineVersion !== undefined ? { deadlineVersion: opts.deadlineVersion } : {}), ...(opts.predecessor !== undefined ? { predecessors: opts.predecessor } : {}) }, process.env.ELANOUS_TRACK || 'cli');
|
|
763
|
+
const data = setItem(version, id, { ...(opts.status !== undefined ? { status: opts.status as ChecklistStatus } : {}), ...(opts.evidence !== undefined ? { evidence: opts.evidence } : {}), ...(opts.owner !== undefined ? { owner: opts.owner } : {}), ...(opts.disposition !== undefined ? { disposition: opts.disposition as ChecklistDisposition } : {}), ...(opts.kind !== undefined ? { kind: opts.kind as ChecklistKind } : {}), ...(opts.priority !== undefined ? { priority: opts.priority as PlacementPriority } : {}), ...(opts.deadlineVersion !== undefined ? { deadlineVersion: opts.deadlineVersion } : {}), ...(opts.predecessor !== undefined ? { predecessors: opts.predecessor } : {}), ...(opts.ceoMinutes !== undefined ? { ceoMinutes: ceoMinutes(opts.ceoMinutes) } : {}), ...(opts.ceoDate !== undefined ? { ceoDate: opts.ceoDate } : {}), ...(opts.accelerator !== undefined ? { accelerator: opts.accelerator ? true : null } : {}) }, process.env.ELANOUS_TRACK || 'cli');
|
|
753
764
|
const item = data.items.find((entry) => entry.id === id);
|
|
754
765
|
const why = item?.kind === 'screen' && item.status === 'green' ? parityGap(item.evidence) : null;
|
|
755
766
|
if (why) {
|
|
@@ -888,24 +899,17 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
|
|
|
888
899
|
console.log(renderReleaseNotes(draftReleaseNotes(readLandedCommits(o.from, o.to), o.from, o.to)));
|
|
889
900
|
});
|
|
890
901
|
release.command('cut-branch')
|
|
891
|
-
.description('원래 컷에 main 의 수리 커밋만 얹은 release/<v> 가지를 만들고 push')
|
|
902
|
+
.description('원래 컷에 main 의 수리 커밋만 얹은 release/<v> 가지를 만들고 push. --append 는 이미 있는 가지에 fast-forward 로만 더 얹는다')
|
|
892
903
|
.requiredOption('--version <v>', '릴리스 판(x.y.z)')
|
|
893
904
|
.requiredOption('--base <sha>', '원래 컷 SHA')
|
|
894
905
|
.requiredOption('--pick <sha...>', '차례로 얹을 main 커밋 SHA')
|
|
906
|
+
.option('--append', '이미 있는 release/<v> 를 출발점으로 fast-forward push(강제 push 없음)')
|
|
895
907
|
.option('--dry-run', '가지·worktree·push 없이 계획만 출력')
|
|
896
908
|
.option('--json', '결과 한 줄 JSON(stdout)')
|
|
897
|
-
.action(async (o: { version: string; base: string; pick: string[]; dryRun?: boolean; json?: boolean }) => {
|
|
909
|
+
.action(async (o: { version: string; base: string; pick: string[]; append?: boolean; dryRun?: boolean; json?: boolean }) => {
|
|
898
910
|
await jsonAction(o.json, async (log) => cutReleaseBranch({ ...o, log }), () => true);
|
|
899
911
|
});
|
|
900
|
-
|
|
901
|
-
.description('릴리스 루프 실행 — 원장 직전 판과 release.loop 설정으로 그래프 입력 구성 · --dry-run 은 입력만 출력')
|
|
902
|
-
.requiredOption('--version <v>', '공개할 판(x.y.z)')
|
|
903
|
-
.option('--cut-commit <sha>', 'origin/main HEAD 대신 컷으로 쓸 커밋')
|
|
904
|
-
.option('--dry-run', '체크리스트·그래프 실행 없이 전체 입력 보기')
|
|
905
|
-
.option('--if-ready', '준비되지 않았으면 사유를 출력하고 성공으로 건너뛴다')
|
|
906
|
-
.option('--force-freeze', '동결 중 게이트·발행 강행(관측 기록)')
|
|
907
|
-
.option('--json', '결과 한 줄 JSON(stdout) · 사람 줄은 stderr')
|
|
908
|
-
.action(async (o: { version: string; cutCommit?: string; dryRun?: boolean; ifReady?: boolean; forceFreeze?: boolean; json?: boolean }) => {
|
|
912
|
+
const runAction = async (o: { version: string; cutCommit?: string; dryRun?: boolean; ifReady?: boolean; forceFreeze?: boolean; json?: boolean }, logRun = console.log): Promise<boolean | AutoStartDeferral | undefined> => {
|
|
909
913
|
if (!o.dryRun) {
|
|
910
914
|
try {
|
|
911
915
|
const frozen = readLandingFreeze(releaseRunDeps.freezeRoot);
|
|
@@ -915,20 +919,20 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
|
|
|
915
919
|
// `--if-ready` (the scheduled path) defers quietly; a direct run fails — the release run is run again after `freeze off`.
|
|
916
920
|
if (o.ifReady) {
|
|
917
921
|
if (o.json) await writeStdoutJson(`${JSON.stringify({ ok: true, skipped: true, reason: 'frozen', detail: landingFreezeMessage(frozen) })}\n`);
|
|
918
|
-
else
|
|
922
|
+
else logRun(`· ${landingFreezeMessage(frozen)} · 릴리스 루프 연기`);
|
|
919
923
|
} else {
|
|
920
924
|
if (o.json) await writeStdoutJson(`${JSON.stringify({ ok: false, reason: 'frozen', error: landingFreezeMessage(frozen) })}\n`);
|
|
921
925
|
else console.error(`⛔ ${landingFreezeMessage(frozen)} · 릴리스 루프 거부(--force-freeze 로만 강행)`);
|
|
922
926
|
process.exitCode = 1;
|
|
923
927
|
}
|
|
924
|
-
return;
|
|
928
|
+
return o.ifReady ? { deferred: true, reason: 'frozen' } : false;
|
|
925
929
|
}
|
|
926
930
|
}
|
|
927
931
|
} catch (e) {
|
|
928
932
|
if (o.json) await writeStdoutJson(`${JSON.stringify({ ok: false, error: (e as Error).message })}\n`);
|
|
929
933
|
else console.error(`⛔ ${(e as Error).message}`);
|
|
930
934
|
process.exitCode = 1;
|
|
931
|
-
return;
|
|
935
|
+
return false;
|
|
932
936
|
}
|
|
933
937
|
}
|
|
934
938
|
if (o.ifReady && !o.dryRun) {
|
|
@@ -940,32 +944,65 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
|
|
|
940
944
|
});
|
|
941
945
|
if (outcome.skipped) {
|
|
942
946
|
if (o.json) await writeStdoutJson(`${JSON.stringify(outcome)}\n`);
|
|
943
|
-
else
|
|
944
|
-
return;
|
|
947
|
+
else logRun(`· 준비 안 됨 ${o.version} · ${outcome.reason} · ${outcome.detail}`);
|
|
948
|
+
return { deferred: true, reason: outcome.reason };
|
|
945
949
|
}
|
|
946
950
|
const result = outcome.result;
|
|
947
951
|
const ok = result.dryRun || result.state?.status === 'done' || result.state?.status === 'awaiting-approval';
|
|
948
|
-
(o.json ? console.error :
|
|
952
|
+
(o.json ? console.error : logRun)(`${result.dryRun ? '· 드라이런' : '▶ 릴리스 루프'} ${result.input.version} · 입력 ${JSON.stringify(result.input)}`);
|
|
949
953
|
if (o.json) await writeStdoutJson(`${JSON.stringify({ ok, ...result })}\n`);
|
|
950
954
|
if (!ok && !process.exitCode) process.exitCode = 1;
|
|
955
|
+
return ok;
|
|
951
956
|
} catch (e) {
|
|
952
957
|
if (e instanceof LandingFrozenError) {
|
|
953
958
|
// A freeze switched on after the entry check still defers the scheduled run rather than failing it.
|
|
954
959
|
debug.log('release.run', 'frozen', { version: o.version, reason: e.freeze.reason, until: e.freeze.until, at: 'run-boundary' });
|
|
955
960
|
if (o.json) await writeStdoutJson(`${JSON.stringify({ ok: true, skipped: true, reason: 'frozen', detail: e.message })}\n`);
|
|
956
|
-
else
|
|
957
|
-
return;
|
|
961
|
+
else logRun(`· ${e.message} · 릴리스 루프 연기`);
|
|
962
|
+
return { deferred: true, reason: 'frozen' };
|
|
958
963
|
}
|
|
959
964
|
if (o.json) await writeStdoutJson(`${JSON.stringify({ ok: false, error: (e as Error).message })}\n`);
|
|
960
965
|
else console.error(`⛔ ${(e as Error).message}`);
|
|
961
966
|
process.exitCode = 1;
|
|
967
|
+
return false;
|
|
962
968
|
}
|
|
963
|
-
return;
|
|
964
969
|
}
|
|
965
970
|
await jsonAction(o.json, async (log) => {
|
|
966
971
|
const result = await runUnattendedRelease({ version: o.version, dryRun: o.dryRun, cutCommit: o.cutCommit, forceFreeze: o.forceFreeze }, releaseRunDeps);
|
|
967
972
|
log(`${result.dryRun ? '· 드라이런' : '▶ 릴리스 루프'} ${result.input.version} · 입력 ${JSON.stringify(result.input)}`);
|
|
968
973
|
return result;
|
|
969
974
|
}, (r) => r.dryRun || r.state?.status === 'done' || r.state?.status === 'awaiting-approval');
|
|
975
|
+
};
|
|
976
|
+
release.command('run')
|
|
977
|
+
.description('릴리스 루프 실행 — 원장 직전 판과 release.loop 설정으로 그래프 입력 구성 · --dry-run 은 입력만 출력')
|
|
978
|
+
.requiredOption('--version <v>', '공개할 판(x.y.z)')
|
|
979
|
+
.option('--cut-commit <sha>', 'origin/main HEAD 대신 컷으로 쓸 커밋')
|
|
980
|
+
.option('--dry-run', '체크리스트·그래프 실행 없이 전체 입력 보기')
|
|
981
|
+
.option('--if-ready', '준비되지 않았으면 사유를 출력하고 성공으로 건너뛴다')
|
|
982
|
+
.option('--force-freeze', '동결 중 게이트·발행 강행(관측 기록)')
|
|
983
|
+
.option('--json', '결과 한 줄 JSON(stdout) · 사람 줄은 stderr')
|
|
984
|
+
.action(async (o: { version: string; cutCommit?: string; dryRun?: boolean; ifReady?: boolean; forceFreeze?: boolean; json?: boolean }) => { await runAction(o); });
|
|
985
|
+
release.command('auto-start')
|
|
986
|
+
.description('판 일정의 컷 창에서 릴리스 시작을 미리 본다 · --apply 로 release run --if-ready 실행')
|
|
987
|
+
.option('--window-minutes <n>', '컷 뒤 선택 창(분)', '15')
|
|
988
|
+
.option('--apply', '선택한 판을 release run --if-ready 로 시작')
|
|
989
|
+
.option('--json', '결과 한 줄 JSON(stdout)')
|
|
990
|
+
.action(async (o: { windowMinutes: string; apply?: boolean; json?: boolean }) => {
|
|
991
|
+
await jsonAction(o.json, async (log) => {
|
|
992
|
+
const result = await autoStartScheduledRelease({
|
|
993
|
+
windowMinutes: Number(o.windowMinutes), apply: o.apply, ledgerRoot: scheduleLedgerRoot(),
|
|
994
|
+
readiness: (version, ledgerRoot, now) => releaseReadiness(version, { ledgerRoot, now: () => now, checklist: releaseRunDeps.checklist }),
|
|
995
|
+
launch: async (version) => {
|
|
996
|
+
const ran = await runAction({ version, ifReady: true, json: false }, log);
|
|
997
|
+
if (ran && typeof ran === 'object' && ran.deferred) return ran;
|
|
998
|
+
if (ran !== true) throw new Error(`release run 미시작 또는 실패: ${version}`);
|
|
999
|
+
},
|
|
1000
|
+
});
|
|
1001
|
+
if (result.status === 'planned') log(`· 미리 보기 ${result.version} · 컷 ${result.cutAt} · 실행하려면 --apply`);
|
|
1002
|
+
else if (result.status === 'started') log(`▶ 릴리스 시작 ${result.version} · 컷 ${result.cutAt}`);
|
|
1003
|
+
else if (result.status === 'skipped') log(`· 릴리스 연기 ${result.version} · ${result.reason}`);
|
|
1004
|
+
else log('· 선택할 컷 없음');
|
|
1005
|
+
return result;
|
|
1006
|
+
}, () => true);
|
|
970
1007
|
});
|
|
971
1008
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
3
|
+
import { hqCliWriteAllowed, type HqDeps } from '../hq/hq.js';
|
|
4
|
+
import { isIsolatedLedgerWriteRoot } from '../hq/ledger-write-target.js';
|
|
5
|
+
import { closeSeatRequests, listSeatRequests, type SeatRequestStatus } from '../seat-dispatch/seat-request-ledger.js';
|
|
6
|
+
|
|
7
|
+
function duration(value: string): number {
|
|
8
|
+
const match = /^(\d+)(ms|s|m|h|d)$/.exec(value);
|
|
9
|
+
if (!match) throw new Error(`invalid --older-than: ${value} (use e.g. 24h)`);
|
|
10
|
+
const ms = Number(match[1]) * ({ ms: 1, s: 1_000, m: 60_000, h: 3_600_000, d: 86_400_000 }[match[2]! as 'ms' | 's' | 'm' | 'h' | 'd']);
|
|
11
|
+
if (!Number.isSafeInteger(ms)) throw new Error(`invalid --older-than: ${value}`);
|
|
12
|
+
return ms;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function registerSeatRequestsCommands(seat: Command, deps: { root?: () => string; hq?: HqDeps; now?: () => Date } = {}): void {
|
|
16
|
+
const requests = seat.command('requests').description('자리 요청 원장 조회·닫기');
|
|
17
|
+
requests.command('list').description('키별 최신 요청 상태 조회')
|
|
18
|
+
.option('--seat <seat>', '자리 ID')
|
|
19
|
+
.option('--status <status>', 'pending|queued|rejected|done')
|
|
20
|
+
.option('--json', 'JSON 출력')
|
|
21
|
+
.action((opts: { seat?: string; status?: string; json?: boolean }) => {
|
|
22
|
+
try {
|
|
23
|
+
if (opts.status && !['pending', 'queued', 'rejected', 'done'].includes(opts.status)) throw new Error(`invalid status: ${opts.status}`);
|
|
24
|
+
const rows = listSeatRequests((deps.root ?? effectiveInstanceRoot)(), { seat: opts.seat, status: opts.status as SeatRequestStatus | undefined });
|
|
25
|
+
if (opts.json) console.log(JSON.stringify(rows));
|
|
26
|
+
else for (const row of rows) console.log(`${row.key} ${row.seat} ${row.status} ${row.queuedAt} ${row.text}`);
|
|
27
|
+
} catch (error) { console.error(`seat requests list: ${error instanceof Error ? error.message : String(error)}`); process.exitCode = 1; }
|
|
28
|
+
});
|
|
29
|
+
requests.command('close <keys...>').description('열린 요청을 사유와 함께 종료 (원장에 덧붙임)')
|
|
30
|
+
.requiredOption('--reason <reason>', '닫는 이유')
|
|
31
|
+
.option('--status <status>', 'rejected|done', 'rejected')
|
|
32
|
+
.option('--older-than <duration>', '최소 대기 시간 (예: 24h)')
|
|
33
|
+
.option('--dry-run', '쓰기 없이 대상만 미리 보기')
|
|
34
|
+
.action((keys: string[], opts: { reason: string; status: string; olderThan?: string; dryRun?: boolean }) => {
|
|
35
|
+
try {
|
|
36
|
+
if (!opts.reason.trim()) throw new Error('reason must not be empty');
|
|
37
|
+
if (opts.status !== 'rejected' && opts.status !== 'done') throw new Error(`invalid close status: ${opts.status}`);
|
|
38
|
+
const olderThan = opts.olderThan === undefined ? undefined : duration(opts.olderThan);
|
|
39
|
+
const root = (deps.root ?? effectiveInstanceRoot)();
|
|
40
|
+
const known = new Set(listSeatRequests(root).map((row) => row.key));
|
|
41
|
+
const missing = keys.find((key) => !known.has(key));
|
|
42
|
+
if (missing) throw new Error(`unknown key: ${missing}`);
|
|
43
|
+
if (!opts.dryRun && !isIsolatedLedgerWriteRoot(root) && !hqCliWriteAllowed('seat requests close', false, deps.hq)) return;
|
|
44
|
+
const rows = closeSeatRequests(root, keys, {
|
|
45
|
+
reason: opts.reason, status: opts.status,
|
|
46
|
+
...(olderThan === undefined ? {} : { olderThan }),
|
|
47
|
+
...(deps.now ? { now: deps.now() } : {}), dryRun: opts.dryRun,
|
|
48
|
+
});
|
|
49
|
+
for (const row of rows) console.log(`${opts.dryRun ? 'would close' : 'closed'} ${row.key} ${row.status}: ${row.reason}`);
|
|
50
|
+
} catch (error) { console.error(`seat requests close: ${error instanceof Error ? error.message : String(error)}`); process.exitCode = 1; }
|
|
51
|
+
});
|
|
52
|
+
}
|
package/src/cli/self-update.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, readFileSync, realpathSync, readlinkSync, readdirSync, statSync, rmSync, symlinkSync, renameSync } from 'node:fs';
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, readlinkSync, readdirSync, statSync, rmSync, symlinkSync, renameSync, writeFileSync, lstatSync } from 'node:fs';
|
|
2
2
|
import { resolve, join, basename, dirname } from 'node:path';
|
|
3
3
|
import { homedir, platform, userInfo } from 'node:os';
|
|
4
4
|
import { spawnSync } from 'node:child_process';
|
|
@@ -18,6 +18,10 @@ export interface SelfUpdateOptions {
|
|
|
18
18
|
skipPwaBuild?: boolean;
|
|
19
19
|
/** 설치된 릴리스에서만 사용한다. 체크아웃 갱신에는 적용하지 않는다. */
|
|
20
20
|
version?: string;
|
|
21
|
+
/** 판 사이 dev 빌드 설치 — versions/<판>-dev.<sha12> · install.json channel:'dev'. 사람 승인 명령. */
|
|
22
|
+
dev?: boolean;
|
|
23
|
+
/** install.json 의 이전 versionDir 로 current 를 되돌린다. 이전이 없으면 거부. */
|
|
24
|
+
rollback?: boolean;
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
export interface SelfUpdateDeps {
|
|
@@ -45,6 +49,15 @@ export interface SelfUpdateDeps {
|
|
|
45
49
|
out?: { log: (s: string) => void; error: (s: string) => void };
|
|
46
50
|
/** 판올림 성공 알림(주입 안 하면 sendOutbound 'report' · 시험에선 안 보냄). */
|
|
47
51
|
notice?: (text: string) => void;
|
|
52
|
+
/**
|
|
53
|
+
* 설치 루트(versions · current · install.json). 주입하지 않으면 ~/.local/share/elanous.
|
|
54
|
+
* 시험은 임시 루트만 넘긴다 — 실물 심링크·재시작을 만지지 않는다.
|
|
55
|
+
*/
|
|
56
|
+
installRoot?: string;
|
|
57
|
+
/** 패키지 버전(package.json). 주입하지 않으면 체크아웃 package.json. */
|
|
58
|
+
packageVersion?: (checkout: string) => string;
|
|
59
|
+
/** 재시작을 실제로 호출해도 되는지. NODE_ENV=test · ELANOUS_TEST_HOME 이면 기본 false. */
|
|
60
|
+
allowLiveRestart?: boolean;
|
|
48
61
|
}
|
|
49
62
|
|
|
50
63
|
export interface SelfUpdateResult {
|
|
@@ -63,6 +76,12 @@ export interface SelfUpdateResult {
|
|
|
63
76
|
prune?: VersionPruneOutcome;
|
|
64
77
|
/** 재시작 뒤 건강 — ok · rolled-back(직전 판으로 되돌려 회복) · rollback-failed · no-rollback-target. */
|
|
65
78
|
health?: 'ok' | 'rolled-back' | 'rollback-failed' | 'no-rollback-target' | 'unmeasured';
|
|
79
|
+
/** --dev 설치 또는 --rollback 이 옮긴 current 대상(versions/<name>). */
|
|
80
|
+
current?: string;
|
|
81
|
+
/** install.json channel. dev 설치면 'dev'. */
|
|
82
|
+
channel?: string;
|
|
83
|
+
/** --dev 가 남긴 이전 versionDir. */
|
|
84
|
+
previous?: string;
|
|
66
85
|
rolledBackTo?: string;
|
|
67
86
|
/** relay(`com.elanous.openai-relay`) 판정 — 넥서스와 «따로» 본다(T5 · 2026-09-24). */
|
|
68
87
|
relay?: RelayUpdateOutcome;
|
|
@@ -182,6 +201,142 @@ export function rollbackTarget(versions: readonly string[], daemonSha: string, i
|
|
|
182
201
|
|
|
183
202
|
const VERSIONS_DIR = () => join(homedir(), '.local/share/elanous/versions');
|
|
184
203
|
|
|
204
|
+
/** 시험·ELANOUS_TEST_HOME 에서는 실물 재시작을 호출하지 않는다. */
|
|
205
|
+
export function liveRestartBlocked(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
206
|
+
return env.NODE_ENV === 'test' || Boolean(env.ELANOUS_TEST_HOME?.trim());
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export interface InstallRecord {
|
|
210
|
+
version?: string;
|
|
211
|
+
versionDir?: string;
|
|
212
|
+
source?: string;
|
|
213
|
+
installedAt?: string;
|
|
214
|
+
commit?: string;
|
|
215
|
+
channel?: string;
|
|
216
|
+
previous?: string;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function defaultInstallRoot(): string {
|
|
220
|
+
return join(homedir(), '.local/share/elanous');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function readInstallRecord(root: string): InstallRecord | null {
|
|
224
|
+
try {
|
|
225
|
+
const parsed: unknown = JSON.parse(readFileSync(join(root, 'install.json'), 'utf8'));
|
|
226
|
+
if (typeof parsed !== 'object' || parsed === null) return null;
|
|
227
|
+
return parsed as InstallRecord;
|
|
228
|
+
} catch {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** install.json 의 version·commit. 못 읽으면 둘 다 «unknown». */
|
|
234
|
+
export function readInstalledIdentity(root: string = defaultInstallRoot()): { installedVersion: string; installedCommit: string } {
|
|
235
|
+
const record = readInstallRecord(root);
|
|
236
|
+
const version = typeof record?.version === 'string' && record.version.trim() ? record.version.trim() : '';
|
|
237
|
+
const commit = typeof record?.commit === 'string' && record.commit.trim() ? record.commit.trim() : '';
|
|
238
|
+
return { installedVersion: version || 'unknown', installedCommit: commit || 'unknown' };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const VERSION_DIR_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
242
|
+
|
|
243
|
+
function versionDirName(versionDir: string | undefined): string | null {
|
|
244
|
+
if (typeof versionDir !== 'string') return null;
|
|
245
|
+
if (!/^versions\/[A-Za-z0-9][A-Za-z0-9._-]*$/.test(versionDir) || versionDir.includes('..')) return null;
|
|
246
|
+
return basename(versionDir);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** 판 디렉터리에 실행 진입 파일이 있나(설치기가 실제로 내용을 깔았나). */
|
|
250
|
+
export function installEntryExists(root: string, versionName: string): boolean {
|
|
251
|
+
return existsSync(join(root, 'versions', versionName, 'node_modules', 'elanous', 'bin', 'elanous.mjs'));
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** 판 이름 «<판>-<커밋12>» · «<판>-dev.<커밋12>» 에서 판·커밋을 읽는다(되돌림 메타 일치용). */
|
|
255
|
+
export function parseVersionDirName(name: string): { version: string; commit: string } | null {
|
|
256
|
+
const m = /^(.+?)-(?:dev\.)?([0-9a-f]{12})(?:-dirty)?$/.exec(name);
|
|
257
|
+
return m ? { version: m[1]!, commit: m[2]! } : null;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** current 심링크를 versions/<name> 으로 원자적으로 옮긴다. 대상 폴더가 없으면 거부. */
|
|
261
|
+
export function relinkInstallCurrent(root: string, versionName: string): void {
|
|
262
|
+
if (!VERSION_DIR_NAME.test(versionName) || versionName.includes('..')) throw new Error(`잘못된 판 이름: ${versionName}`);
|
|
263
|
+
if (!existsSync(join(root, 'versions', versionName))) throw new Error(`판 디렉터리 없음: ${versionName}`);
|
|
264
|
+
// EDGE-RAIL-VERIFY(10-06 05:30 사고): 진입 파일이 없는 판으로는 current 를 옮기지 않는다(빈 판 → 전역 elanous·크론 전부 실패).
|
|
265
|
+
if (!installEntryExists(root, versionName)) throw new Error(`빈 판(진입 파일 없음): ${versionName} — current 를 옮기지 않는다`);
|
|
266
|
+
const tmp = join(root, `.current-dev-${process.pid}`);
|
|
267
|
+
rmSync(tmp, { force: true });
|
|
268
|
+
symlinkSync(`versions/${versionName}`, tmp);
|
|
269
|
+
renameSync(tmp, join(root, 'current'));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function checkoutPackageVersion(checkout: string): string {
|
|
273
|
+
const parsed = JSON.parse(readFileSync(join(checkout, 'package.json'), 'utf8')) as { version?: unknown };
|
|
274
|
+
if (typeof parsed.version !== 'string' || !parsed.version.trim()) throw new Error('package.json version 없음');
|
|
275
|
+
return parsed.version.trim();
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* 판 사이 dev 빌드 — versions/<판>-dev.<sha12> 를 만들고 current 를 옮긴다.
|
|
280
|
+
* install.json 에 channel:'dev' 와 이전 versionDir(previous) 를 남긴다. 실제 설치기·재시작은 부르지 않는다.
|
|
281
|
+
*/
|
|
282
|
+
export function installDevBuild(checkout: string, commit: string, deps: Pick<SelfUpdateDeps, 'installRoot' | 'packageVersion'> & { runInstaller?: () => { status: number | null; stderr: string } } = {}): { versionName: string; versionDir: string; previous: string; record: InstallRecord } {
|
|
283
|
+
const sha12 = commit.trim().toLowerCase().slice(0, 12);
|
|
284
|
+
if (!/^[0-9a-f]{12}$/.test(sha12)) throw new Error(`dev 설치 커밋이 12자가 아님: ${commit}`);
|
|
285
|
+
const root = deps.installRoot ?? defaultInstallRoot();
|
|
286
|
+
const prior = readInstallRecord(root);
|
|
287
|
+
const previous = versionDirName(prior?.versionDir) ? prior!.versionDir! : (typeof prior?.previous === 'string' ? prior.previous : '');
|
|
288
|
+
// EDGE-RAIL-VERIFY: 빈 디렉터리를 만들고 current 만 옮기던 길(10-06 05:30 사고)을 버리고, 정식 길과 같은 설치기로 내용을 깐다.
|
|
289
|
+
if (!deps.runInstaller) throw new Error('dev 설치기 없음 — 빈 판을 깔지 않는다');
|
|
290
|
+
// install.sh 는 current 를 «먼저» 옮기고 뒤에서 실패할 수 있다 — 실패·빈 판 어느 쪽이든 이전 판·install.json 으로 되돌린다.
|
|
291
|
+
const restore = (why: string, versionName: string | null): never => {
|
|
292
|
+
const previousName = versionDirName(previous);
|
|
293
|
+
if (previousName && installEntryExists(root, previousName)) relinkInstallCurrent(root, previousName);
|
|
294
|
+
if (prior) writeFileSync(join(root, 'install.json'), `${JSON.stringify(prior, null, 2)}\n`);
|
|
295
|
+
try { debug.log('self-update', 'dev-install-refused', { why, versionName, kept: previousName ?? null }); } catch { /* 관측 실패가 막지 않는다 */ }
|
|
296
|
+
throw new Error(`${why}: ${versionName ?? '?'} — current 를 이전 판(${previousName ?? '없음'})으로 유지`);
|
|
297
|
+
};
|
|
298
|
+
let result: { status: number | null; stderr: string };
|
|
299
|
+
try { result = deps.runInstaller(); }
|
|
300
|
+
catch (error) { return restore(`설치기 예외(${String(error).slice(0, 200)})`, null); }
|
|
301
|
+
const after = readInstallRecord(root);
|
|
302
|
+
const versionName = versionDirName(after?.versionDir);
|
|
303
|
+
if (result.status !== 0) return restore(`설치기 실패(rc=${result.status}: ${result.stderr.slice(0, 200)})`, versionName);
|
|
304
|
+
if (!versionName || !installEntryExists(root, versionName)) return restore('빈 판(진입 파일 없음)', versionName);
|
|
305
|
+
const versionDir = `versions/${versionName}`;
|
|
306
|
+
const record: InstallRecord = {
|
|
307
|
+
...(after ?? {}),
|
|
308
|
+
versionDir,
|
|
309
|
+
channel: 'dev',
|
|
310
|
+
...(previous && previous !== versionDir ? { previous } : {}),
|
|
311
|
+
};
|
|
312
|
+
writeFileSync(join(root, 'install.json'), `${JSON.stringify(record, null, 2)}\n`);
|
|
313
|
+
return { versionName, versionDir, previous, record };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** install.json.previous 의 판으로 current 를 되돌린다. previous 가 없으면 거부(exit 2). */
|
|
317
|
+
export function rollbackDevInstall(deps: Pick<SelfUpdateDeps, 'installRoot'> = {}): { ok: true; versionName: string; record: InstallRecord } | { ok: false; reason: string } {
|
|
318
|
+
const root = deps.installRoot ?? defaultInstallRoot();
|
|
319
|
+
const prior = readInstallRecord(root);
|
|
320
|
+
const previousName = versionDirName(prior?.previous);
|
|
321
|
+
if (!prior || !previousName) return { ok: false, reason: '이전 판(install.json previous)이 없다 — rollback 거부' };
|
|
322
|
+
if (!existsSync(join(root, 'versions', previousName))) return { ok: false, reason: `이전 판 디렉터리 없음: ${previousName}` };
|
|
323
|
+
const currentName = versionDirName(prior.versionDir);
|
|
324
|
+
relinkInstallCurrent(root, previousName);
|
|
325
|
+
const parsed = parseVersionDirName(previousName);
|
|
326
|
+
const record: InstallRecord = {
|
|
327
|
+
...prior,
|
|
328
|
+
...(parsed ? { version: parsed.version, commit: parsed.commit } : {}),
|
|
329
|
+
versionDir: `versions/${previousName}`,
|
|
330
|
+
channel: prior.channel === 'dev' ? undefined : prior.channel,
|
|
331
|
+
previous: currentName ? `versions/${currentName}` : undefined,
|
|
332
|
+
installedAt: new Date().toISOString(),
|
|
333
|
+
};
|
|
334
|
+
if (!record.channel) delete record.channel;
|
|
335
|
+
if (!record.previous) delete record.previous;
|
|
336
|
+
writeFileSync(join(root, 'install.json'), `${JSON.stringify(record, null, 2)}\n`);
|
|
337
|
+
return { ok: true, versionName: previousName, record };
|
|
338
|
+
}
|
|
339
|
+
|
|
185
340
|
/** `unmeasured` = 건강을 «잴 수» 없었다(REST 주소를 한 번도 못 얻음) — 「건강하지 않다」와 다르다. 되돌리지 않는다. */
|
|
186
341
|
export interface VerifyRestartResult { ok: boolean; daemonSha?: string; reason?: string; unmeasured?: boolean }
|
|
187
342
|
|
|
@@ -399,9 +554,14 @@ export async function runReleaseUpdate(options: ReleaseUpdateOptions = {}, deps:
|
|
|
399
554
|
}
|
|
400
555
|
let previous = '';
|
|
401
556
|
let previousBuild = '';
|
|
557
|
+
let replacedDev: { from: string; to: string } | null = null;
|
|
402
558
|
try {
|
|
403
559
|
const metadata: unknown = JSON.parse(readFileSync(join(prefix, 'install.json'), 'utf8'));
|
|
404
560
|
previousBuild = installedBuild(metadata);
|
|
561
|
+
if (typeof metadata === 'object' && metadata !== null && (metadata as { channel?: unknown }).channel === 'dev') {
|
|
562
|
+
const fromDir = typeof (metadata as { versionDir?: unknown }).versionDir === 'string' ? (metadata as { versionDir: string }).versionDir : '';
|
|
563
|
+
replacedDev = { from: fromDir, to: '' };
|
|
564
|
+
}
|
|
405
565
|
if (typeof metadata === 'object' && metadata !== null && 'versionDir' in metadata && typeof metadata.versionDir === 'string') {
|
|
406
566
|
const dir = metadata.versionDir;
|
|
407
567
|
if (/^versions\/[a-zA-Z0-9._-]+$/.test(dir) && !dir.includes('..')) previous = basename(dir);
|
|
@@ -418,6 +578,12 @@ export async function runReleaseUpdate(options: ReleaseUpdateOptions = {}, deps:
|
|
|
418
578
|
const installedVersion = typeof metadata === 'object' && metadata !== null && 'version' in metadata && typeof metadata.version === 'string'
|
|
419
579
|
? metadata.version : null;
|
|
420
580
|
if (!installedVersion) return fail(1, '설치판 확인 실패: install.json version 없음');
|
|
581
|
+
if (replacedDev) {
|
|
582
|
+
const toDir = typeof metadata === 'object' && metadata !== null && typeof (metadata as { versionDir?: unknown }).versionDir === 'string'
|
|
583
|
+
? (metadata as { versionDir: string }).versionDir : installedVersion;
|
|
584
|
+
replacedDev = { from: replacedDev.from, to: toDir };
|
|
585
|
+
try { debug.log('self-update', 'dev-replaced', replacedDev); } catch { /* 관측 실패가 갱신을 막지 않는다 */ }
|
|
586
|
+
}
|
|
421
587
|
const internalRevision = isTailnet(source) && version?.match(/^(.*)-([0-9a-f]{12})$/);
|
|
422
588
|
if (version && installedVersion !== version && (!internalRevision || installedVersion !== internalRevision[1])) {
|
|
423
589
|
return fail(1, `설치판 버전 불일치: 요청 ${version}, 설치 ${installedVersion}`);
|
|
@@ -491,6 +657,9 @@ export async function runUpdateForInstallation(
|
|
|
491
657
|
out.log(options.json ? JSON.stringify(result) : `self-update: ${result.reason}`);
|
|
492
658
|
return result;
|
|
493
659
|
}
|
|
660
|
+
if (options.dev || options.rollback) {
|
|
661
|
+
return runSelfUpdate(options, { cliRoot, ...deps.checkout });
|
|
662
|
+
}
|
|
494
663
|
if (options.version !== undefined) {
|
|
495
664
|
const result: SelfUpdateResult = { exitCode: 2, installedVersion: null, decision: null, restarted: false, reason: '--version 은 릴리스 설치본에서만 사용 가능' };
|
|
496
665
|
const out = deps.checkout?.out ?? console;
|
|
@@ -537,6 +706,15 @@ export async function runSelfUpdate(options: SelfUpdateOptions = {}, deps: SelfU
|
|
|
537
706
|
return result;
|
|
538
707
|
};
|
|
539
708
|
const reject = (reason: string): SelfUpdateResult => emit({ exitCode: 2, installedVersion: null, decision: null, restarted: false, reason });
|
|
709
|
+
if (options.rollback) {
|
|
710
|
+
const rolled = rollbackDevInstall(deps);
|
|
711
|
+
if (!rolled.ok) return reject(rolled.reason);
|
|
712
|
+
return emit({
|
|
713
|
+
exitCode: 0, installedVersion: rolled.versionName, decision: null, restarted: false,
|
|
714
|
+
current: `versions/${rolled.versionName}`, previous: rolled.record.previous, channel: rolled.record.channel,
|
|
715
|
+
reason: `rollback: current → ${rolled.versionName}`,
|
|
716
|
+
});
|
|
717
|
+
}
|
|
540
718
|
try {
|
|
541
719
|
if (!existsSync(join(checkout, 'scripts/install.sh'))) return reject(`체크아웃 없음 또는 설치기 없음: ${checkout}`);
|
|
542
720
|
const root = git(checkout, ['rev-parse', '--show-toplevel']);
|
|
@@ -546,6 +724,24 @@ export async function runSelfUpdate(options: SelfUpdateOptions = {}, deps: SelfU
|
|
|
546
724
|
const dirty = git(checkout, ['diff', '--quiet', 'HEAD', '--']);
|
|
547
725
|
if (dirty.status !== 0) return reject(dirty.status === 1 ? '추적 파일 변경: 체크아웃이 더러움' : `git diff 실패: ${dirty.stderr}`);
|
|
548
726
|
|
|
727
|
+
if (options.dev) {
|
|
728
|
+
try {
|
|
729
|
+
const installed = installDevBuild(checkout, head.stdout.trim(), { ...deps, runInstaller: () => run('bash', [join(checkout, 'scripts/install.sh'), '--no-modify-path'], checkout) });
|
|
730
|
+
return emit({
|
|
731
|
+
exitCode: 0,
|
|
732
|
+
installedVersion: installed.versionName,
|
|
733
|
+
decision: null,
|
|
734
|
+
restarted: false,
|
|
735
|
+
current: installed.versionDir,
|
|
736
|
+
channel: 'dev',
|
|
737
|
+
previous: installed.previous || undefined,
|
|
738
|
+
reason: `dev 설치: current → ${installed.versionName}`,
|
|
739
|
+
});
|
|
740
|
+
} catch (error) {
|
|
741
|
+
return emit({ exitCode: 1, installedVersion: null, decision: null, restarted: false, reason: `dev 설치 실패: ${error instanceof Error ? error.message : String(error)}` });
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
549
745
|
const silence = { log: (_s: string) => {}, error: (_s: string) => {} };
|
|
550
746
|
let decision: RestartNeededResult;
|
|
551
747
|
try {
|
|
@@ -628,6 +824,9 @@ export async function runSelfUpdate(options: SelfUpdateOptions = {}, deps: SelfU
|
|
|
628
824
|
const os = deps.os ?? platform();
|
|
629
825
|
const command = os === 'darwin' ? 'launchctl' : os === 'linux' ? 'systemctl' : null;
|
|
630
826
|
if (!command) return emit({ exitCode: 1, installedVersion, decision, restarted: false, prune, reason: `지원하지 않는 플랫폼: ${os}` });
|
|
827
|
+
if (deps.allowLiveRestart === false || (deps.allowLiveRestart !== true && liveRestartBlocked())) {
|
|
828
|
+
return emit({ exitCode: 0, installedVersion, decision, restarted: false, prune, reason: '테스트 가드: 실물 재시작 0' });
|
|
829
|
+
}
|
|
631
830
|
const args = os === 'darwin' ? ['kickstart', '-k', `gui/${deps.uid ?? process.getuid?.() ?? userInfo().uid}/com.elanous.nexus`] : ['--user', 'restart', 'elanous-nexus'];
|
|
632
831
|
try {
|
|
633
832
|
const restarted = run(command, args, checkout);
|
package/src/cli/usage-cli.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type { Command } from 'commander';
|
|
|
10
10
|
import { collectUnifiedUsage, formatUnifiedUsage } from '../budget/unified-usage.js';
|
|
11
11
|
import { consumeCodexResetCredits, listCodexResetCredits } from '../budget/codex-reset-credits.js';
|
|
12
12
|
import { rollupRunUsage, type RunUsageInput } from '../budget/run-usage-rollup.js';
|
|
13
|
+
import { formatUsageOutcomes, outcomeTableHasCodexGrokUnknown, queryUsageOutcomes, type UsageOutcomesDeps, type UsageOutcomesReport } from '../budget/usage-outcomes.js';
|
|
13
14
|
import { LogStore, logsDbPath, type LogQuery, type LogStoreRow } from '../mss/logging/log-store.js';
|
|
14
15
|
import { LOGS_SINCE_OPTION, parseSince } from './logs-cli.js';
|
|
15
16
|
import { formatCodexCreditPlan, type CodexCreditPlan } from '../budget/codex-credit-plan.js';
|
|
@@ -28,6 +29,9 @@ export interface UsageCliDeps {
|
|
|
28
29
|
/** 계정 이름 → CODEX_HOME. ⛔ 여기 없는 이름은 «거부»한다(모르는 곳에 쓰지 않는다). */
|
|
29
30
|
readonly homes?: Readonly<Record<string, string>>;
|
|
30
31
|
readonly exit?: (code: number) => never;
|
|
32
|
+
/** 런 원장 결과 표. 시험은 디렉터리를 주입한다. */
|
|
33
|
+
readonly outcomes?: (sinceDays: number) => UsageOutcomesReport;
|
|
34
|
+
readonly outcomeDeps?: UsageOutcomesDeps;
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
/** 계정 이름 → CODEX_HOME 기본 표.
|
|
@@ -134,6 +138,28 @@ export function registerUsageCommand(program: Command, deps: UsageCliDeps = {}):
|
|
|
134
138
|
// 🩸 그래서 ⑴ `--yes` 없이는 «안 쓴다» ⑵ 쓰기 «전»에 남은 수를 보여 준다
|
|
135
139
|
// ⑶ 쓴 «뒤»에도 다시 조회해 「줄었나」를 값으로 낸다.
|
|
136
140
|
// 📏 2026-09-24 현황 예: default 100%·team 100%·third 46% ⇒ third 에 쓰면 54% 가 버려진다.
|
|
141
|
+
const outcomes = deps.outcomes ?? ((sinceDays: number) => queryUsageOutcomes(sinceDays, deps.outcomeDeps));
|
|
142
|
+
usage
|
|
143
|
+
.command('outcomes')
|
|
144
|
+
.description('프로바이더별 무인 착지율 · 라운드 수 · 막힘 사유 분포 (런 원장 childProvider · 미기록은 불명)')
|
|
145
|
+
.option('--days <n>', '최근 N일', '2')
|
|
146
|
+
.option('--json', 'JSON 출력')
|
|
147
|
+
.action((opts: { days?: string; json?: boolean }, command: Command) => {
|
|
148
|
+
const json = opts.json || command.optsWithGlobals<{ json?: boolean }>().json;
|
|
149
|
+
const days = Number(opts.days);
|
|
150
|
+
if (!Number.isFinite(days) || days <= 0) {
|
|
151
|
+
out.log(`⛔ --days 는 양의 수: '${opts.days}'`);
|
|
152
|
+
exit(2);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const report = outcomes(days);
|
|
156
|
+
if (json) {
|
|
157
|
+
out.log(JSON.stringify({ ...report, hasCodexGrokUnknown: outcomeTableHasCodexGrokUnknown(report) }, null, 2));
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
out.log(formatUsageOutcomes(report));
|
|
161
|
+
});
|
|
162
|
+
|
|
137
163
|
const list = deps.list ?? listCodexResetCredits;
|
|
138
164
|
const consume = deps.consume ?? consumeCodexResetCredits;
|
|
139
165
|
const homes = deps.homes ?? defaultHomes();
|