elanous 0.2.9 → 0.2.10
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/2DsiDfDCks44g1cPUMhiW/_buildManifest.js +1 -0
- package/apps/pwa/out/_next/static/chunks/24-e6d28380efab7faf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3615-740dd270764ffa24.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3734-1015fe991eb9b7e7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5012-a7fe6f3c8b6fe45a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5783-ef40ebfc91d55230.js +1 -0
- package/apps/pwa/out/_next/static/chunks/62-ff227c3edb8b248b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7633-1b3709b0854c9412.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7951-8b03dbb10b24cd06.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8067-87ce253ec2f23697.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5080-1613752891fcadb4.js → 8195-6fde3d7a39859206.js} +5 -5
- package/apps/pwa/out/_next/static/chunks/8894-b5fb36bab837cd29.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9454-84d0d5d77446bd92.js +1 -0
- package/apps/pwa/out/_next/static/chunks/988-5f92e7ab7ccea3dd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9989-5582582f06429840.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-d2253d7e58ef1890.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-5441d4a9ab35e518.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-d9b12d680922d980.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-f972fc1c00a62e88.js → page-54b32bd27726588d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-e268d6d145827a22.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-f8116559c7424da7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-1085399fd0f32112.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-1df686a14f65895a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-54e1f966ab60a4dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/{page-973996d584f008c1.js → page-7ed95f296f5ee8e9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-11edac668d77e5c3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-59e2b9a22cd5a6d5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-2976df21ad74ab48.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-34d211a67f16d510.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-8ff652d5d950639b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/{page-8b0402a46a749a3d.js → page-b31610deda76337b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-037f83e2a1edc8e2.js → page-6cb0e1bbefe5fb3d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-a286a152cf584576.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-d9237581e7003bcb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-83274e761643fdd2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-ceee6cfb1d4e6d68.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-9c9f8bb3e4a64091.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-c17217603ea3426a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-efb6177cbf1e8a51.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-d307523703bc4df3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-462f4e4bab45ce7d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-8990e96f33af3837.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-0df30e0c7e50148a.js → page-e168fa1030af0548.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-1233e05a4068bc9c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-13a1840b1fac337a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-64a29fc80b11ab55.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-68e787339a16b3dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-4fddc90b23feb215.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-886ba8260dc79c79.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-0f26507a1722e40b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-109d4ccd42259c9d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-f5c2a4e510726774.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-b2e0039416f42522.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-90670589b87abea8.js → page-26102fd7350f171e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-708948bc8a802794.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-5e5611620f78fb0b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/{page-35bedde6d885f08f.js → page-139ba5c367bbf60a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/main-app-202b4e81f33895ea.js +1 -0
- package/apps/pwa/out/_next/static/css/43feca5fd387dcbd.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/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/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/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/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 -0
- package/apps/pwa/out/ops/seats/index.txt +30 -0
- 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 -0
- package/apps/pwa/out/today/index.txt +33 -0
- 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/graphs/field/field-feed.yaml +2 -0
- package/graphs/implement-loop.yaml +1 -1
- package/package.json +1 -1
- package/plugins/card-followup/graphs/run-step.ts +48 -13
- package/plugins/doc-draft/graphs/doc-draft.yaml +3 -3
- package/plugins/doc-draft/graphs/run-step.ts +13 -6
- package/scripts/ci-pwa-build-gate.ts +160 -0
- package/scripts/coord-channel-watch.sh +42 -8
- package/scripts/coord-digest.ts +199 -0
- package/scripts/coord-post.sh +60 -3
- package/scripts/install.ps1 +68 -9
- package/scripts/install.sh +39 -12
- package/scripts/lib/pwa-scenarios/scenarios.ts +20 -0
- package/scripts/lib/tui-regress-checks.ts +146 -0
- package/scripts/memory-lifecycle-cycle.ts +2 -2
- package/scripts/ops-now-probe.ts +327 -0
- package/scripts/publish-internal-dist.ts +11 -1
- package/scripts/release-loop/cutoff.ts +19 -8
- package/scripts/release-loop/ops-upgrade-node.ts +72 -16
- package/scripts/release-loop/release-readiness.ts +9 -0
- package/scripts/release-loop/version-node.ts +58 -10
- package/scripts/repo-watch-cycle.ts +1 -1
- package/scripts/retro-cycle.ts +2 -2
- package/scripts/schedule-health-report.ts +1 -1
- package/scripts/storyboard/storyboard.ts +5 -161
- package/scripts/test-diet/lib.ts +30 -1
- package/scripts/test-diet/node.ts +82 -7
- package/scripts/test-timeout-audit.ts +114 -0
- package/scripts/tree-sync-alert.ts +1 -1
- package/scripts/tui-regress.ts +71 -0
- package/scripts/waitlist-daily-digest.ts +1 -1
- package/skills/explainer-video/SKILL.md +4 -0
- package/skills/explainer-video/engine/reel.mjs +18 -13
- package/skills/explainer-video/engine/reel.sh +24 -5
- package/skills/explainer-video/engine/stitch.mjs +145 -0
- package/skills/pitch-deck/SKILL.md +36 -0
- package/skills/pitch-deck/template.html +80 -0
- package/src/acp/codex-approval-adapter.ts +9 -3
- package/src/acp/core-turn-bridge.ts +117 -18
- package/src/acp/fact-question.ts +11 -0
- package/src/acp/server.ts +48 -7
- package/src/acp/tool-approval.ts +126 -0
- package/src/acp/turn-runner.ts +36 -0
- package/src/agent/monad-agent-turn.ts +8 -0
- package/src/ask-user-question/pending-questions.ts +25 -10
- package/src/ask-user-question/types.ts +9 -0
- package/src/auth/setup-link-tokens.ts +16 -5
- package/src/autopilot/build/integrity-gate.ts +12 -0
- package/src/boot/daemon-prompt-turn.ts +3 -0
- package/src/boot/daemon-tools/index.ts +8 -0
- package/src/boot/daemon-tools/types.ts +2 -0
- package/src/boot/daemon-tools/web-search.ts +1 -0
- package/src/chat/ctrl-c-in-input.ts +14 -0
- package/src/chat/index.ts +52 -0
- package/src/chat/pickers/state.ts +6 -2
- package/src/cli/agent-cli.ts +7 -0
- package/src/cli/decisions-cli.ts +25 -3
- package/src/cli/doctor-bundle-upload.ts +68 -0
- package/src/cli/doctor-cli.ts +38 -4
- package/src/cli/ops-cli.ts +20 -0
- package/src/cli/pod-cli.ts +49 -0
- package/src/cli/pr-cli.ts +31 -2
- package/src/cli/pwa-stop.ts +163 -13
- package/src/cli/pwa-test.ts +3 -0
- package/src/cli/release-cli.ts +63 -20
- package/src/cli/self-update.ts +14 -0
- package/src/cli/storyboard-cli.ts +37 -0
- package/src/cli/test-state-dir-flag.ts +11 -4
- package/src/connectors/linear.ts +146 -0
- package/src/context-bus/coord-events.ts +115 -0
- package/src/dashboard/dashboard-boot-fixture.ts +23 -0
- package/src/dashboard/index.ts +58 -21
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +7 -0
- package/src/dashboard/slash-runtime/help-from-registry.ts +43 -28
- package/src/decisions/decision-cards.ts +49 -1
- package/src/decisions/decision-ledger.ts +75 -4
- package/src/decisions/decision-linear-projection.ts +157 -0
- package/src/decisions/web-push-decision-cards.ts +50 -0
- package/src/directive/interpretation-card.ts +36 -0
- package/src/discord.ts +2 -1
- package/src/domains/coo-admin-tool.ts +62 -0
- package/src/domains/core-tools.ts +11 -3
- package/src/domains/elanous-card.ts +29 -0
- package/src/domains/ops-facts-tool.ts +76 -0
- package/src/domains/outbound-alert.ts +41 -13
- package/src/domains/release-tool.ts +168 -0
- package/src/domains/telegram-kind-route.ts +19 -1
- package/src/exec-requests/planner.ts +98 -28
- package/src/exec-requests/runner.ts +107 -10
- package/src/exec-requests/store.ts +8 -2
- package/src/field/field-media.ts +10 -7
- package/src/field/field-reel.ts +14 -4
- package/src/git-fs/worktree.ts +45 -6
- package/src/graph-runner/graph-approval-card.ts +113 -0
- package/src/graph-runner/graph-cli.ts +129 -4
- package/src/graph-runner/graph-notify.ts +25 -3
- package/src/graph-runner/runner.ts +142 -16
- package/src/harness/browser-verify.ts +25 -2
- package/src/harness/harness-cli-command.ts +231 -35
- package/src/harness/harness-incidents-writer.fixture.ts +24 -0
- package/src/harness/harness-incidents.ts +129 -0
- package/src/harness/harness-stop.ts +53 -3
- package/src/hitl/clarification-policy.ts +3 -1
- package/src/index.ts +156 -37
- package/src/intake-plane/author-goals.ts +37 -4
- package/src/intake-plane/check.ts +3 -0
- package/src/intake-plane/discord-seat-work.ts +47 -2
- package/src/intake-plane/seat-doc-route.ts +170 -6
- package/src/intake-plane/telegram-seat-work.ts +44 -2
- package/src/intake-plane/telegram-work.ts +1 -0
- package/src/maturity/cli-maturity.ts +34 -0
- package/src/maturity/feature-maturity.ts +98 -1
- package/src/maturity/tui-slash-maturity.ts +31 -0
- package/src/memory.ts +32 -4
- package/src/nexus/api/exec-requests.ts +33 -3
- package/src/nexus/api/http-server.ts +53 -1
- package/src/nexus/api/meta-api.ts +11 -0
- package/src/nexus/api/ops-api.ts +10 -1
- package/src/nexus/api/ops-seats-sources.ts +37 -7
- package/src/nexus/api/ops-seats.ts +16 -6
- package/src/nexus/api/seat-requests.ts +88 -8
- package/src/nexus/api/sessions-store.ts +63 -0
- package/src/nexus/api/setup-codex-login.ts +160 -0
- package/src/nexus/api/usage-api.ts +102 -0
- package/src/nexus/index.ts +45 -4
- package/src/onboarding/first-run.ts +89 -0
- package/src/onboarding/lan-address.ts +45 -0
- package/src/onboarding/web-first.ts +119 -24
- package/src/onboarding.ts +4 -0
- package/src/ops-now/ops-now-refresh.ts +100 -0
- package/src/project/project-store.ts +115 -0
- package/src/release-loop/checklist.ts +78 -4
- package/src/release-loop/feature-store.ts +154 -21
- package/src/release-loop/release-schedule.ts +53 -0
- package/src/repl/index.ts +7 -0
- package/src/seat-dispatch/ceo-commands.ts +41 -8
- package/src/seat-dispatch/ceo-intent.ts +38 -0
- package/src/seat-loop/seat-loop.ts +231 -0
- package/src/seat-loop/seat-report.ts +64 -0
- package/src/self-dev/decompose.ts +66 -11
- package/src/self-dev/run-store.ts +10 -1
- package/src/self-implement/execution-clarification.ts +115 -0
- package/src/self-implement/gate-cli.ts +6 -3
- package/src/self-implement/orchestrator.ts +37 -3
- package/src/self-implement/pipeline-shape.ts +1 -1
- package/src/self-implement/run-status-mapping.ts +5 -3
- package/src/session/chat.ts +2 -0
- package/src/session/index.ts +9 -0
- package/src/shell-env-bootstrap.ts +17 -0
- package/src/steward/directive.ts +51 -2
- package/src/steward/launch.ts +250 -0
- package/src/steward/outcome-notify.ts +115 -0
- package/src/steward/steward-cards.ts +8 -2
- package/src/steward/triage-plan.ts +32 -4
- package/src/steward/triage.ts +93 -9
- package/src/steward/working-backwards.ts +64 -0
- package/src/storyboard/storyboard.ts +141 -0
- package/src/task-cards/card-store.ts +2 -2
- package/src/task-orchestrator/surfaces/pod-lease.ts +260 -0
- package/src/telegram-card-followup.ts +233 -0
- package/src/telegram-report.ts +15 -9
- package/src/telegram-run.ts +7 -0
- package/src/telegram.ts +29 -8
- package/src/tool-runtime/types.ts +4 -0
- package/src/tui-client/dashboard-session.ts +2 -0
- package/src/user-config.ts +118 -11
- package/src/version/packed-revision.json +1 -1
- package/src/web-push/notify-exec-request.ts +32 -0
- package/apps/pwa/out/_next/static/bFI5EEwg7T6aG88ibX4Zq/_buildManifest.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6090-85a3f05f954e5697.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6284-69db415193d5f8bd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7032-f4ef698518fba61c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8067-d976b441a104ab74.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8519-5632377cce994f86.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8630-cdfc61e82c653ffc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8894-e263b332208e3957.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9454-139040d37c79dbc9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9740-8f5ff96c522b5f55.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-fb7c2d458f32fcad.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-35e2713597dd24d1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-918b656f064577ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-5f30e00693527ebd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-7fbae173b3c103a7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-3b4edcce364062ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-c5794f484c251f44.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-1130d8eefcd87e76.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-016e2bda04eecb1f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-b4d6d9a3b56f2940.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-ec49a466121233ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-f72db356744ceade.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-a6fd9b50d1b85378.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-adfbd2ba9598e59a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-46fedff57d4bec67.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-0cf318e9dcee2bbf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-899b327cb8b6845c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-6c1e6ba743a29dfe.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-1a8e4807292f6b17.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-28deba6bcb834916.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-de68e2205794037d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-c71c33a573230672.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-4fc56790fe0d2682.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-59df2aebb38a7c1e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-4647be5dbcbd0795.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-e025892bda74b743.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-a0955de73b89f078.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-369846d2921af584.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-72cbc1c8cdd0f65d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-77b28bfb0996d5ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-3220c4934f49c6c2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-7df9243ed021a4cc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-4498c3853f4f688d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +0 -1
- package/apps/pwa/out/_next/static/css/f92f63a5df0ba229.css +0 -1
- package/src/dashboard/turn-typeahead-echo.ts +0 -23
- /package/apps/pwa/out/_next/static/{bFI5EEwg7T6aG88ibX4Zq → 2DsiDfDCks44g1cPUMhiW}/_ssgManifest.js +0 -0
package/src/cli/pwa-stop.ts
CHANGED
|
@@ -23,6 +23,12 @@ import { probeTailscale, type TailscaleProbe } from '../nexus/onboarding/tailsca
|
|
|
23
23
|
import { unregisterPwaInstance } from './pwa-registry.js';
|
|
24
24
|
import { readNexusLock } from '../nexus/supervisor/lock.js';
|
|
25
25
|
import { resolveNexusPwa, type NexusPwaResolution } from './nexus-show.js';
|
|
26
|
+
import { debug } from '../debug/log.js';
|
|
27
|
+
import { isPidAlive } from '../process/pid-liveness.js';
|
|
28
|
+
import { execFileSync } from 'node:child_process';
|
|
29
|
+
import { join as joinPath, resolve as resolvePath } from 'node:path';
|
|
30
|
+
import { hostname } from 'node:os';
|
|
31
|
+
import { safeReadLock } from '../telegram-lock.js';
|
|
26
32
|
|
|
27
33
|
export interface PwaStopOpts {
|
|
28
34
|
out?: { log: (s: string) => void; error: (s: string) => void };
|
|
@@ -45,7 +51,19 @@ export interface PwaStopOpts {
|
|
|
45
51
|
* from `~/.elanous/pwa-registry.json`. */
|
|
46
52
|
unregisterFn?: (pid: number) => void;
|
|
47
53
|
/** P4 — read the nexus lock to recover daemon pid. Test seam. */
|
|
48
|
-
readLockFn?: () => { pid: number } | null;
|
|
54
|
+
readLockFn?: () => { pid: number; host?: string; startedAt?: string } | null;
|
|
55
|
+
/** Isolated test daemon stop only: the project-local root to inspect for orphans. */
|
|
56
|
+
isolatedRoot?: string;
|
|
57
|
+
/** Test seams for signals, liveness and bounded polling. */
|
|
58
|
+
signalFn?: (pid: number, signal: 'SIGINT' | 'SIGTERM') => void;
|
|
59
|
+
pidAliveFn?: (pid: number) => boolean;
|
|
60
|
+
sleepFn?: (ms: number) => Promise<void>;
|
|
61
|
+
stopTimeoutMs?: number;
|
|
62
|
+
nowFn?: () => number;
|
|
63
|
+
/** Process table for isolated daemon identity checks and orphan discovery (pid, start time, argv). */
|
|
64
|
+
processTableFn?: () => Array<{ pid: number; startedAt: string; args: string }>;
|
|
65
|
+
/** Test seam for the raw `ps -eo pid=,lstart=,args=` response. */
|
|
66
|
+
psOutputFn?: () => string;
|
|
49
67
|
}
|
|
50
68
|
|
|
51
69
|
export type PwaStopShareUnmount =
|
|
@@ -57,7 +75,7 @@ export interface PwaStopResult {
|
|
|
57
75
|
exitCode: number;
|
|
58
76
|
/** True when the dev BG was actually killed (vs no-lock no-op). */
|
|
59
77
|
devKilled: boolean;
|
|
60
|
-
/** True when the
|
|
78
|
+
/** True when the daemon stop completed (or the ordinary stop trigger succeeded). */
|
|
61
79
|
nexusStopped: boolean;
|
|
62
80
|
/** True when `tailscale serve reset` was issued (not skipped). */
|
|
63
81
|
shareReset: boolean;
|
|
@@ -73,6 +91,126 @@ async function defaultNexusStop(): Promise<{ exitCode: number }> {
|
|
|
73
91
|
return { exitCode: 0 };
|
|
74
92
|
}
|
|
75
93
|
|
|
94
|
+
function runningProcesses(output: string): Array<{ pid: number; startedAt: string; args: string }> {
|
|
95
|
+
const lines = output.split('\n');
|
|
96
|
+
return lines.flatMap(line => {
|
|
97
|
+
const match = line.match(/^\s*(\d+)\s+((?:\S+\s+){4}\d{4})\s+(.+)$/);
|
|
98
|
+
return match ? [{ pid: Number(match[1]), startedAt: match[2]!, args: match[3]! }] : [];
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
type ProcessRow = ReturnType<typeof runningProcesses>[number];
|
|
103
|
+
|
|
104
|
+
function isolatedDaemons(root: string, table: ProcessRow[]): ProcessRow[] {
|
|
105
|
+
return table.filter(row => {
|
|
106
|
+
if (!/^(?:(?:\S*\/)?(?:bun|node)\s+)?(?:\S*\/)?elanous(?:\.(?:mjs|cjs))?\s/.test(row.args)
|
|
107
|
+
|| !/(?:^|\s)nexus\s+run(?:\s|$)/.test(row.args)) return false;
|
|
108
|
+
const roots = ['--config-dir', '--test-state-dir'].flatMap(flag => {
|
|
109
|
+
const value = row.args.match(new RegExp(`(?:^|\\s)${flag}(?:=|\\s+)(?:"([^"]+)"|'([^']+)'|(\\S+))`));
|
|
110
|
+
const arg = value?.[1] ?? value?.[2] ?? value?.[3];
|
|
111
|
+
return arg === undefined ? [] : [resolvePath(arg)];
|
|
112
|
+
});
|
|
113
|
+
return roots.length > 0 && roots.every(value => value === resolvePath(root));
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function stopIsolatedNexus(
|
|
118
|
+
opts: PwaStopOpts,
|
|
119
|
+
out: NonNullable<PwaStopOpts['out']>,
|
|
120
|
+
lock: ReturnType<NonNullable<PwaStopOpts['readLockFn']>>,
|
|
121
|
+
): Promise<{ exitCode: number }> {
|
|
122
|
+
const root = opts.isolatedRoot!;
|
|
123
|
+
const inspect = (): ProcessRow[] => isolatedDaemons(root, opts.processTableFn
|
|
124
|
+
? opts.processTableFn()
|
|
125
|
+
: runningProcesses((opts.psOutputFn ?? (() => execFileSync('ps', ['-eo', 'pid=,lstart=,args='], { encoding: 'utf8' })))()));
|
|
126
|
+
const reportOrphans = (): { exitCode: number } => {
|
|
127
|
+
const orphans = inspect();
|
|
128
|
+
debug.log('nexus.stop', 'orphans', { pid: 0, waitedMs: 0, count: orphans.length });
|
|
129
|
+
if (orphans.length === 0) {
|
|
130
|
+
out.log(' nothing to stop (no lock)');
|
|
131
|
+
return { exitCode: 0 };
|
|
132
|
+
}
|
|
133
|
+
for (const row of orphans) out.error(` orphan pid ${row.pid} · started ${row.startedAt} · root ${root}`);
|
|
134
|
+
out.error(` cleanup: ${orphans.map(row => `kill ${row.pid}`).join(' ; ')}`);
|
|
135
|
+
return { exitCode: 1 };
|
|
136
|
+
};
|
|
137
|
+
if (!lock) {
|
|
138
|
+
try { return reportOrphans(); }
|
|
139
|
+
catch (error) {
|
|
140
|
+
out.error(` cannot inspect nexus processes: ${error instanceof Error ? error.message : String(error)}`);
|
|
141
|
+
return { exitCode: 1 };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const pid = lock.pid;
|
|
145
|
+
if (lock.host && lock.host !== hostname()) {
|
|
146
|
+
out.error(` nexus lock held by remote host ${lock.host}; cannot stop from here`);
|
|
147
|
+
return { exitCode: 1 };
|
|
148
|
+
}
|
|
149
|
+
if (!Number.isSafeInteger(pid) || pid <= 0) {
|
|
150
|
+
out.error(` invalid nexus lock pid ${pid}`);
|
|
151
|
+
return { exitCode: 1 };
|
|
152
|
+
}
|
|
153
|
+
const signal = opts.signalFn ?? ((target: number, name: 'SIGINT' | 'SIGTERM') => process.kill(target, name));
|
|
154
|
+
const alive = opts.pidAliveFn ?? isPidAlive;
|
|
155
|
+
const sleep = opts.sleepFn ?? ((ms: number) => new Promise<void>(resolve => setTimeout(resolve, ms)));
|
|
156
|
+
const timeout = opts.stopTimeoutMs ?? 10_000;
|
|
157
|
+
const now = opts.nowFn ?? Date.now;
|
|
158
|
+
let waitedMs = 0;
|
|
159
|
+
const wait = async (): Promise<boolean> => {
|
|
160
|
+
const start = now();
|
|
161
|
+
let elapsed = 0;
|
|
162
|
+
while (alive(pid) && elapsed < timeout) {
|
|
163
|
+
const interval = Math.min(100, timeout - elapsed);
|
|
164
|
+
await sleep(interval);
|
|
165
|
+
elapsed = Math.max(elapsed + interval, now() - start);
|
|
166
|
+
}
|
|
167
|
+
waitedMs += elapsed;
|
|
168
|
+
debug.log('nexus.stop', 'wait', { pid, waitedMs, count: 1 });
|
|
169
|
+
return !alive(pid);
|
|
170
|
+
};
|
|
171
|
+
try {
|
|
172
|
+
if (!alive(pid)) return reportOrphans();
|
|
173
|
+
const identity = inspect().find(row => row.pid === pid);
|
|
174
|
+
if (!identity) {
|
|
175
|
+
out.error(` pid ${pid} does not match this tree's isolated nexus daemon; no signal sent`);
|
|
176
|
+
return { exitCode: 1 };
|
|
177
|
+
}
|
|
178
|
+
if (lock.startedAt) {
|
|
179
|
+
const processStart = Date.parse(identity.startedAt);
|
|
180
|
+
const lockStart = Date.parse(lock.startedAt);
|
|
181
|
+
if (!Number.isFinite(processStart) || !Number.isFinite(lockStart) || processStart > lockStart + 1_000) {
|
|
182
|
+
out.error(` pid ${pid} start time cannot match the nexus lock; no signal sent`);
|
|
183
|
+
return { exitCode: 1 };
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// Recheck the argv and start time immediately before each signal: a stale
|
|
187
|
+
// lock or a PID recycled during the wait must never target another process.
|
|
188
|
+
const stillOwned = (): boolean => inspect().some(row => row.pid === pid && row.startedAt === identity.startedAt && row.args === identity.args);
|
|
189
|
+
if (!stillOwned()) {
|
|
190
|
+
out.error(` pid ${pid} no longer matches the locked daemon; no signal sent`);
|
|
191
|
+
return { exitCode: 1 };
|
|
192
|
+
}
|
|
193
|
+
signal(pid, 'SIGINT');
|
|
194
|
+
out.log(` SIGINT sent to pid ${pid}`);
|
|
195
|
+
if (await wait()) return { exitCode: 0 };
|
|
196
|
+
if (!alive(pid)) return { exitCode: 0 };
|
|
197
|
+
if (!stillOwned()) {
|
|
198
|
+
out.error(` pid ${pid} no longer matches the locked daemon; SIGTERM not sent`);
|
|
199
|
+
return { exitCode: 1 };
|
|
200
|
+
}
|
|
201
|
+
signal(pid, 'SIGTERM');
|
|
202
|
+
debug.log('nexus.stop', 'escalated', { pid, waitedMs, count: 1 });
|
|
203
|
+
if (await wait()) return { exitCode: 0 };
|
|
204
|
+
} catch (error) {
|
|
205
|
+
out.error(` pid ${pid} 이 멈추지 않았다: ${error instanceof Error ? error.message : String(error)}`);
|
|
206
|
+
debug.log('nexus.stop', 'stuck', { pid, waitedMs, count: 1 });
|
|
207
|
+
return { exitCode: 1 };
|
|
208
|
+
}
|
|
209
|
+
out.error(` pid ${pid} 이 멈추지 않았다`);
|
|
210
|
+
debug.log('nexus.stop', 'stuck', { pid, waitedMs, count: 1 });
|
|
211
|
+
return { exitCode: 1 };
|
|
212
|
+
}
|
|
213
|
+
|
|
76
214
|
async function defaultShareReset(binary: string, port: number): Promise<{ exitCode: number }> {
|
|
77
215
|
const r = await unmountTailscaleServe({
|
|
78
216
|
mode: { kind: 'tls-tcp', port },
|
|
@@ -111,11 +249,12 @@ export async function runPwaStop(opts: PwaStopOpts = {}): Promise<PwaStopResult>
|
|
|
111
249
|
const out = opts.out ?? console;
|
|
112
250
|
const portResolution = resolveShareUnmountPort(opts);
|
|
113
251
|
const devStopFn = opts.devStopFn ?? (() => runPwaDevStop({ out }));
|
|
114
|
-
const nexusStopFn = opts.nexusStopFn ?? defaultNexusStop;
|
|
115
252
|
const shareProbeFn = opts.shareProbeFn ?? (() => probeTailscale());
|
|
116
253
|
const shareResetFn = opts.shareResetFn ?? defaultShareReset;
|
|
117
|
-
const unregisterFn = opts.unregisterFn ?? unregisterPwaInstance;
|
|
118
|
-
const readLockFn = opts.readLockFn ?? (
|
|
254
|
+
const unregisterFn = opts.unregisterFn ?? (opts.isolatedRoot ? undefined : unregisterPwaInstance);
|
|
255
|
+
const readLockFn = opts.readLockFn ?? (opts.isolatedRoot
|
|
256
|
+
? () => safeReadLock(joinPath(opts.isolatedRoot!, 'nexus', '.lock'))
|
|
257
|
+
: () => readNexusLock());
|
|
119
258
|
|
|
120
259
|
out.log('elanous nexus pwa stop: cascade');
|
|
121
260
|
|
|
@@ -157,19 +296,30 @@ export async function runPwaStop(opts: PwaStopOpts = {}): Promise<PwaStopResult>
|
|
|
157
296
|
}
|
|
158
297
|
|
|
159
298
|
// P4 registry unregister — read the lock for the daemon pid before
|
|
160
|
-
// we send SIGINT (the lock is removed during stop).
|
|
161
|
-
//
|
|
299
|
+
// we send SIGINT (the lock is removed during stop). The isolated stop
|
|
300
|
+
// never writes to the production registry. Missing lock/pid is best-effort.
|
|
162
301
|
let unregisteredPid: number | undefined;
|
|
302
|
+
let lock: ReturnType<typeof readLockFn> = null;
|
|
303
|
+
try { lock = readLockFn(); }
|
|
304
|
+
catch (error) {
|
|
305
|
+
if (opts.isolatedRoot) {
|
|
306
|
+
out.error(` cannot read nexus lock: ${error instanceof Error ? error.message : String(error)}`);
|
|
307
|
+
return { exitCode: 1, devKilled: false, nexusStopped: false, shareReset, shareUnmount };
|
|
308
|
+
}
|
|
309
|
+
}
|
|
163
310
|
try {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
311
|
+
if (lock && typeof lock.pid === 'number' && (!opts.isolatedRoot || !lock.host || lock.host === hostname())) {
|
|
312
|
+
if (unregisterFn) {
|
|
313
|
+
unregisterFn(lock.pid);
|
|
314
|
+
unregisteredPid = lock.pid;
|
|
315
|
+
}
|
|
168
316
|
}
|
|
169
|
-
} catch { /*
|
|
317
|
+
} catch { /* registry unregister failure — non-fatal */ }
|
|
170
318
|
|
|
171
319
|
const devR = await devStopFn();
|
|
172
|
-
const nexusR = await nexusStopFn(
|
|
320
|
+
const nexusR = await (opts.nexusStopFn ?? (opts.isolatedRoot
|
|
321
|
+
? () => stopIsolatedNexus(opts, out, lock)
|
|
322
|
+
: defaultNexusStop))();
|
|
173
323
|
const exitCode = devR.exitCode === 0 && nexusR.exitCode === 0 ? 0 : 1;
|
|
174
324
|
return {
|
|
175
325
|
exitCode,
|
package/src/cli/pwa-test.ts
CHANGED
|
@@ -720,8 +720,11 @@ async function runStop(
|
|
|
720
720
|
out,
|
|
721
721
|
shareProbeFn: async () => ({ installed: false, alive: false }),
|
|
722
722
|
shareResetFn: async () => ({ exitCode: 0 }),
|
|
723
|
+
isolatedRoot: layout.stateDir,
|
|
723
724
|
});
|
|
724
725
|
|
|
726
|
+
// Keep the record when the daemon did not stop; status must not imply a clean shutdown.
|
|
727
|
+
if (stopRes.exitCode !== 0) return { exitCode: stopRes.exitCode };
|
|
725
728
|
// Cleanup state files — leave logs/ for post-mortem.
|
|
726
729
|
const stateFile = joinPath(layout.stateDir, 'test-state.json');
|
|
727
730
|
if (existsSync(stateFile)) {
|
package/src/cli/release-cli.ts
CHANGED
|
@@ -23,9 +23,11 @@ import { effectiveInstanceRoot, releaseLedgerRoot } from '../instance/resolve.js
|
|
|
23
23
|
import { getElanousConfigDirOverride } from '../elanous-config-dir.js';
|
|
24
24
|
import { envLiteral } from '../platform/env-literal.js';
|
|
25
25
|
import { userConfigPath } from '../user-config.js';
|
|
26
|
-
import { addItem, devVersion, listChecklist, removeItem, seedFromRoadmap, setItem, summarizeChecklist, type ChecklistStatus, type ChecklistDisposition, type ChecklistKind } from '../release-loop/checklist.js';
|
|
26
|
+
import { addItem, claimItem, decodeClaimHistoryEntry, devVersion, listChecklist, ownerMatches, parseOwner, parityGap, removeItem, seedFromRoadmap, setItem, summarizeChecklist, type ChecklistStatus, type ChecklistDisposition, type ChecklistKind } from '../release-loop/checklist.js';
|
|
27
27
|
import * as features from '../release-loop/feature-store.js';
|
|
28
|
+
import { formatSchedule, getSchedule, listSchedules, setSchedule } from '../release-loop/release-schedule.js';
|
|
28
29
|
import { writeStdoutJson } from './stdout-json.js';
|
|
30
|
+
import { CliUserError } from './cli-user-error.js';
|
|
29
31
|
import { runUnattendedRelease, type UnattendedReleaseDeps } from '../../scripts/release-loop/unattended-release.js';
|
|
30
32
|
import { releaseReadiness } from '../../scripts/release-loop/release-readiness.js';
|
|
31
33
|
import { runReleaseIfReady } from './release-run-if-ready.js';
|
|
@@ -468,15 +470,23 @@ function checklistCodenames(): Record<string, string> {
|
|
|
468
470
|
function checklistVersion(input: string | undefined, codenames: Record<string, string>): string {
|
|
469
471
|
const v = input ?? devVersion().replace(/-dev\.\d+$/, '');
|
|
470
472
|
const resolved = isReleaseVersion(v) ? v : Object.entries(codenames).find(([, name]) => name === v)?.[0] ?? v;
|
|
471
|
-
if (!isReleaseVersion(resolved)) throw new
|
|
473
|
+
if (!isReleaseVersion(resolved)) throw new CliUserError(`체크리스트 판 또는 별칭이 아니다: ${v}`);
|
|
472
474
|
return resolved;
|
|
473
475
|
}
|
|
474
476
|
|
|
475
|
-
async function checklistOutput(v: string, codenames: Record<string, string>, mode: 'list' | 'status', json?: boolean): Promise<void> {
|
|
476
|
-
|
|
477
|
+
async function checklistOutput(v: string, codenames: Record<string, string>, mode: 'list' | 'status', json?: boolean, owner?: string): Promise<void> {
|
|
478
|
+
if (owner !== undefined) parseOwner(owner);
|
|
479
|
+
const snapshot = listChecklist(v);
|
|
480
|
+
const data = owner === undefined ? snapshot : { ...snapshot, items: snapshot.items.filter((item) => ownerMatches(item.owner, owner)) };
|
|
477
481
|
const summary = summarizeChecklist(data);
|
|
478
|
-
const
|
|
482
|
+
const parity = data.items.flatMap((item) => {
|
|
483
|
+
const why = item.kind === 'screen' ? parityGap(item.evidence) : null;
|
|
484
|
+
return why ? [{ id: item.id, why }] : [];
|
|
485
|
+
});
|
|
486
|
+
const result = { ...data, codename: codenames[v] ?? '', ...summary, parity };
|
|
487
|
+
for (const { id, why } of parity) debug.log('release.checklist', 'parity-warning', { version: v, id, why });
|
|
479
488
|
if (json) { await writeStdoutJson(`${JSON.stringify(result)}\n`); return; }
|
|
489
|
+
console.log(`⚠ 짝 경고 ${parity.length}: ${parity.map(({ id }) => id).join(', ') || '없음'}`);
|
|
480
490
|
console.log(`${v}${result.codename ? ` (${result.codename})` : ''} · 공개 ${data.released || '없음'} · 개발 ${data.dev}`);
|
|
481
491
|
if (mode === 'list') {
|
|
482
492
|
for (const item of data.items) console.log(`${({ green: '🟢', yellow: '🟡', red: '🔴', done: '✅' } as const)[item.status]} ${item.id} ${item.title}${item.owner ? ` · ${item.owner}` : ''}`);
|
|
@@ -489,6 +499,26 @@ async function checklistOutput(v: string, codenames: Record<string, string>, mod
|
|
|
489
499
|
|
|
490
500
|
export function registerReleaseCommands(program: Command, releaseRunDeps: UnattendedReleaseDeps = {}): void {
|
|
491
501
|
const release = program.command('release').description('공개 배포 한 판 — prepare(로컬) → publish(--yes) → verify (docs/manual/MANUAL-versioning-and-release-2026-09-25.md)');
|
|
502
|
+
const schedule = release.command('schedule').description('판별 컷·착지 마감 조회·갱신');
|
|
503
|
+
const scheduleLedgerRoot = () => releaseRunDeps.ledgerRoot ?? releaseLedgerRoot();
|
|
504
|
+
schedule.command('list').option('--json', '결과 JSON').action((o: { json?: boolean }) => {
|
|
505
|
+
const rows = listSchedules(scheduleLedgerRoot());
|
|
506
|
+
if (o.json) console.log(JSON.stringify(rows));
|
|
507
|
+
else for (const row of rows) console.log(formatSchedule(row));
|
|
508
|
+
});
|
|
509
|
+
schedule.command('show').requiredOption('--version <v>', '조회할 판').option('--json', '결과 JSON')
|
|
510
|
+
.action((o: { version: string; json?: boolean }) => {
|
|
511
|
+
const row = getSchedule(o.version, scheduleLedgerRoot());
|
|
512
|
+
if (!row) throw new CliUserError(`없는 판 일정: ${o.version}`, '판 일정 등록: elanous release schedule set --version <v> --cut-at <iso> --land-by <iso>');
|
|
513
|
+
if (o.json) console.log(JSON.stringify(row)); else console.log(formatSchedule(row));
|
|
514
|
+
});
|
|
515
|
+
schedule.command('set').requiredOption('--version <v>', '갱신할 판')
|
|
516
|
+
.option('--cut-at <iso>', '오프셋 포함 컷 시각').option('--land-by <iso>', '오프셋 포함 착지 마감').option('--json', '결과 JSON')
|
|
517
|
+
.action((o: { version: string; cutAt?: string; landBy?: string; json?: boolean }) => {
|
|
518
|
+
if (o.cutAt === undefined && o.landBy === undefined) throw new CliUserError('갱신할 시각을 지정하라', '--cut-at <iso> 또는 --land-by <iso>');
|
|
519
|
+
const row = setSchedule(o.version, { cutAt: o.cutAt, landBy: o.landBy }, process.env.ELANOUS_TRACK || 'cli', scheduleLedgerRoot());
|
|
520
|
+
if (o.json) console.log(JSON.stringify(row)); else console.log(formatSchedule(row));
|
|
521
|
+
});
|
|
492
522
|
const checklist = release.command('checklist').description('판별 확인표 조회·갱신')
|
|
493
523
|
.option('--version <v>', '판 또는 별칭(기본: package.json 의 개발판에서 -dev.N 제거)')
|
|
494
524
|
.option('--json', '결과 JSON');
|
|
@@ -498,15 +528,15 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
|
|
|
498
528
|
return { version: checklistVersion(opts.version, codenames), codenames, json: opts.json };
|
|
499
529
|
};
|
|
500
530
|
const withContext = (cmd: Command) => cmd.option('--version <v>', '판 또는 별칭').option('--json', '결과 JSON');
|
|
501
|
-
withContext(checklist.command('list').description('모든 칸')).action(async (
|
|
531
|
+
withContext(checklist.command('list').description('모든 칸')).option('--owner <owner>', '자리 또는 하위 자리로 거르기').action(async (opts: { owner?: string }, cmd: Command) => {
|
|
502
532
|
const { version, codenames, json } = context(cmd);
|
|
503
|
-
await checklistOutput(version, codenames, 'list', json);
|
|
533
|
+
await checklistOutput(version, codenames, 'list', json, opts.owner);
|
|
504
534
|
});
|
|
505
|
-
withContext(checklist.command('status').description('상태 요약')).action(async (
|
|
535
|
+
withContext(checklist.command('status').description('상태 요약')).option('--owner <owner>', '자리 또는 하위 자리로 거르기').action(async (opts: { owner?: string }, cmd: Command) => {
|
|
506
536
|
const { version, codenames, json } = context(cmd);
|
|
507
|
-
await checklistOutput(version, codenames, 'status', json);
|
|
537
|
+
await checklistOutput(version, codenames, 'status', json, opts.owner);
|
|
508
538
|
});
|
|
509
|
-
withContext(checklist.command('add <id> <title>').description('칸 추가')).option('--owner <owner>', '담당').option('--kind <kind>', 'screen = 다섯 화면 짝 칸')
|
|
539
|
+
withContext(checklist.command('add <id> <title>').description('칸 추가')).option('--owner <owner>', '담당').option('--kind <kind>', 'screen = 다섯 화면 짝 칸 · 근거에 짝: PWA … · 데스크톱 … · 폴드 … · 아이폰 … · 아이패드 … 한 줄')
|
|
510
540
|
.action((id: string, title: string, opts: { owner?: string; kind?: string }, cmd: Command) => {
|
|
511
541
|
const { version, json } = context(cmd);
|
|
512
542
|
const data = addItem(version, { id, title, ...(opts.owner !== undefined ? { owner: opts.owner } : {}), ...(opts.kind !== undefined ? { kind: opts.kind as ChecklistKind } : {}) });
|
|
@@ -514,15 +544,28 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
|
|
|
514
544
|
});
|
|
515
545
|
withContext(checklist.command('set <id>').description('칸 상태·근거·담당·처분 갱신'))
|
|
516
546
|
.option('--status <status>', 'green|yellow|red|done').option('--evidence <evidence>', '근거').option('--owner <owner>', '담당')
|
|
517
|
-
.option('--disposition <disposition>', 'move|known-issue|block').option('--kind <kind>', 'screen = 다섯 화면 짝 칸')
|
|
547
|
+
.option('--disposition <disposition>', 'move|known-issue|block').option('--kind <kind>', 'screen = 다섯 화면 짝 칸 · 근거에 짝: PWA … · 데스크톱 … · 폴드 … · 아이폰 … · 아이패드 … 한 줄')
|
|
518
548
|
.action((id: string, opts: { status?: string; evidence?: string; owner?: string; disposition?: string; kind?: string }, cmd: Command) => {
|
|
519
549
|
const { version, json } = context(cmd);
|
|
520
|
-
if (opts.status !== undefined && !['green', 'yellow', 'red', 'done'].includes(opts.status)) throw new
|
|
521
|
-
if (opts.disposition !== undefined && !['move', 'known-issue', 'block'].includes(opts.disposition)) throw new
|
|
522
|
-
if (opts.status === undefined && opts.evidence === undefined && opts.owner === undefined && opts.disposition === undefined && opts.kind === undefined) throw new
|
|
550
|
+
if (opts.status !== undefined && !['green', 'yellow', 'red', 'done'].includes(opts.status)) throw new CliUserError(`잘못된 상태: ${opts.status}`, 'green|yellow|red|done');
|
|
551
|
+
if (opts.disposition !== undefined && !['move', 'known-issue', 'block'].includes(opts.disposition)) throw new CliUserError(`잘못된 처분: ${opts.disposition}`, 'move|known-issue|block');
|
|
552
|
+
if (opts.status === undefined && opts.evidence === undefined && opts.owner === undefined && opts.disposition === undefined && opts.kind === undefined) throw new CliUserError('갱신할 칸을 지정하라', '--status · --evidence · --owner · --disposition · --kind 중 하나');
|
|
523
553
|
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 } : {}) }, process.env.ELANOUS_TRACK || 'cli');
|
|
554
|
+
const item = data.items.find((entry) => entry.id === id);
|
|
555
|
+
const why = item?.kind === 'screen' && item.status === 'green' ? parityGap(item.evidence) : null;
|
|
556
|
+
if (why) {
|
|
557
|
+
console.error(`⚠ 짝: ${why} — 근거에 «짝: PWA … · 데스크톱 … · 폴드 … · 아이폰 … · 아이패드 …» 한 줄`);
|
|
558
|
+
debug.log('release.checklist', 'parity-warning', { version, id, why });
|
|
559
|
+
}
|
|
524
560
|
if (json) console.log(JSON.stringify(data)); else console.log(`✅ ${id} 갱신`);
|
|
525
561
|
});
|
|
562
|
+
withContext(checklist.command('claim <id>').description('칸의 단일 주인을 잡는다'))
|
|
563
|
+
.requiredOption('--by <owner>', '잡을 자리 또는 하위 자리').option('--force', '기존 주인에서 강제로 바꾼다')
|
|
564
|
+
.action((id: string, opts: { by: string; force?: boolean }, cmd: Command) => {
|
|
565
|
+
const { version, json } = context(cmd);
|
|
566
|
+
const data = claimItem(version, id, opts.by, { force: opts.force });
|
|
567
|
+
if (json) console.log(JSON.stringify(data)); else console.log(`✅ ${id} 주인 ${opts.by}`);
|
|
568
|
+
});
|
|
526
569
|
withContext(checklist.command('rm <id>').description('칸 삭제')).action((id: string, _opts: unknown, cmd: Command) => {
|
|
527
570
|
const { version, json } = context(cmd);
|
|
528
571
|
const data = removeItem(version, id, process.env.ELANOUS_TRACK || 'cli');
|
|
@@ -540,8 +583,8 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
|
|
|
540
583
|
.action((id: string, opts: { from: string; to: string }, cmd: Command) => {
|
|
541
584
|
const { codenames, json } = context(cmd);
|
|
542
585
|
const from = checklistVersion(opts.from, codenames), to = checklistVersion(opts.to, codenames);
|
|
543
|
-
|
|
544
|
-
if (json) console.log(JSON.stringify(
|
|
586
|
+
features.move(id, from, to, actor());
|
|
587
|
+
if (json) console.log(JSON.stringify(listChecklist(to))); else console.log(`✅ ${id} ${from} → ${to}`);
|
|
545
588
|
});
|
|
546
589
|
withContext(checklist.command('retitle <id> <title>').description('칸 제목 수정')).action((id: string, title: string, _opts: unknown, cmd: Command) => {
|
|
547
590
|
const { version, json } = context(cmd);
|
|
@@ -554,11 +597,11 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
|
|
|
554
597
|
const { version, json } = context(cmd);
|
|
555
598
|
const snapshot = listChecklist(version);
|
|
556
599
|
features.evidenceAdd(id, version, ref, actor(), snapshot.released, snapshot.dev);
|
|
557
|
-
if (json) console.log(JSON.stringify(features.history(id))); else console.log(`✅ ${id} 근거 ${ref}`);
|
|
600
|
+
if (json) console.log(JSON.stringify(features.history(id).map(decodeClaimHistoryEntry))); else console.log(`✅ ${id} 근거 ${ref}`);
|
|
558
601
|
});
|
|
559
602
|
withContext(checklist.command('history <id>').description('칸의 판 이동·변경 이력')).action((id: string, _opts: unknown, cmd: Command) => {
|
|
560
603
|
const { json } = context(cmd);
|
|
561
|
-
const rows = features.history(id);
|
|
604
|
+
const rows = features.history(id).map(decodeClaimHistoryEntry);
|
|
562
605
|
if (json) { console.log(JSON.stringify(rows)); return; }
|
|
563
606
|
const info = features.details(id);
|
|
564
607
|
if (info) {
|
|
@@ -570,8 +613,8 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
|
|
|
570
613
|
withContext(checklist.command('export').description('SQLite 에서 checklist.json 스냅샷 생성')).action((_opts: unknown, cmd: Command) => {
|
|
571
614
|
const { version, json } = context(cmd);
|
|
572
615
|
const snapshot = listChecklist(version);
|
|
573
|
-
|
|
574
|
-
if (json) console.log(JSON.stringify(
|
|
616
|
+
features.exportJson(version, snapshot.released, snapshot.dev);
|
|
617
|
+
if (json) console.log(JSON.stringify(listChecklist(version))); else console.log(`✅ ${version} checklist.json 내보냄`);
|
|
575
618
|
});
|
|
576
619
|
release.command('prepare')
|
|
577
620
|
.description('깨끗한 원본 → 공개본 → 공개 저장소 이력 위 커밋 → PWA → 묶음·체크섬 → 로컬 끝까지 (네트워크 쓰기 없음)')
|
package/src/cli/self-update.ts
CHANGED
|
@@ -343,6 +343,14 @@ function pruneReleaseVersions(plan: { current: string; previous: string; keep: n
|
|
|
343
343
|
return { removed, kept };
|
|
344
344
|
}
|
|
345
345
|
|
|
346
|
+
/** The build identity of an install.json — version, version folder and commit; empty when it cannot be read. */
|
|
347
|
+
function installedBuild(metadata: unknown): string {
|
|
348
|
+
if (typeof metadata !== 'object' || metadata === null) return '';
|
|
349
|
+
const { version, versionDir, commit } = metadata as { version?: unknown; versionDir?: unknown; commit?: unknown };
|
|
350
|
+
if (typeof version !== 'string' || !version || typeof versionDir !== 'string' || !versionDir) return '';
|
|
351
|
+
return JSON.stringify([version, versionDir, typeof commit === 'string' ? commit : '']);
|
|
352
|
+
}
|
|
353
|
+
|
|
346
354
|
/** A release update never packs the checkout: the downloaded installer verifies its own release tarball. */
|
|
347
355
|
export async function runReleaseUpdate(options: ReleaseUpdateOptions = {}, deps: ReleaseUpdateDeps = {}): Promise<SelfUpdateResult> {
|
|
348
356
|
const out = deps.out ?? console;
|
|
@@ -390,8 +398,10 @@ export async function runReleaseUpdate(options: ReleaseUpdateOptions = {}, deps:
|
|
|
390
398
|
return fail(1, `설치기 다운로드 실패 (${url}): ${String(error)}`);
|
|
391
399
|
}
|
|
392
400
|
let previous = '';
|
|
401
|
+
let previousBuild = '';
|
|
393
402
|
try {
|
|
394
403
|
const metadata: unknown = JSON.parse(readFileSync(join(prefix, 'install.json'), 'utf8'));
|
|
404
|
+
previousBuild = installedBuild(metadata);
|
|
395
405
|
if (typeof metadata === 'object' && metadata !== null && 'versionDir' in metadata && typeof metadata.versionDir === 'string') {
|
|
396
406
|
const dir = metadata.versionDir;
|
|
397
407
|
if (/^versions\/[a-zA-Z0-9._-]+$/.test(dir) && !dir.includes('..')) previous = basename(dir);
|
|
@@ -421,6 +431,10 @@ export async function runReleaseUpdate(options: ReleaseUpdateOptions = {}, deps:
|
|
|
421
431
|
prune = { removed: [], kept: [], skipped: `정리 실패: ${String(error)}` };
|
|
422
432
|
}
|
|
423
433
|
if (!options.restart) return finish({ exitCode: 0, installedVersion, decision: null, restarted: false, prune, reason: '--restart 없음: 재시작하지 않음' });
|
|
434
|
+
// REL6: the same build again (version · folder · commit unchanged) leaves the running daemon as it is.
|
|
435
|
+
if (previousBuild && previousBuild === installedBuild(metadata)) {
|
|
436
|
+
return finish({ exitCode: 0, installedVersion, decision: null, restarted: false, prune, reason: `already at ${installedVersion} · restart skipped` });
|
|
437
|
+
}
|
|
424
438
|
const os = deps.os ?? platform();
|
|
425
439
|
const command = os === 'darwin' ? 'launchctl' : os === 'linux' ? 'systemctl' : null;
|
|
426
440
|
if (!command) return finish({ exitCode: 1, installedVersion, decision: null, restarted: false, prune, reason: `지원하지 않는 플랫폼: ${os}` });
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join, relative } from 'node:path';
|
|
3
|
+
import { parse } from 'yaml';
|
|
4
|
+
import { debug } from '../debug/log.js';
|
|
5
|
+
import { files, lint, render, STORYBOARD_ROOT, type Storyboard } from '../storyboard/storyboard.js';
|
|
6
|
+
|
|
7
|
+
export function runStoryboard(cmd: 'lint' | 'render', args: string[], json = false): number {
|
|
8
|
+
const reports: Array<{ file: string; errors: string[]; warnings: string[] }> = [];
|
|
9
|
+
let errorCount = 0, warningCount = 0;
|
|
10
|
+
for (const f of files(args)) {
|
|
11
|
+
const file = relative(STORYBOARD_ROOT, f);
|
|
12
|
+
let sb: Storyboard | undefined;
|
|
13
|
+
let errors: string[], warnings: string[];
|
|
14
|
+
try {
|
|
15
|
+
sb = parse(readFileSync(f, 'utf8')) as Storyboard;
|
|
16
|
+
({ errors, warnings } = lint(sb, file));
|
|
17
|
+
} catch (cause) {
|
|
18
|
+
errors = [cause instanceof Error ? cause.message : String(cause)];
|
|
19
|
+
warnings = [];
|
|
20
|
+
}
|
|
21
|
+
reports.push({ file, errors, warnings });
|
|
22
|
+
errorCount += errors.length;
|
|
23
|
+
warningCount += warnings.length;
|
|
24
|
+
if (json) continue;
|
|
25
|
+
for (const e of errors) console.log(`✗ ${file}: ${e}`);
|
|
26
|
+
for (const w of warnings) console.log(`⚠ ${file}: ${w}`);
|
|
27
|
+
if (errors.length || !sb) continue;
|
|
28
|
+
if (cmd === 'render') {
|
|
29
|
+
const out = join(STORYBOARD_ROOT, 'docs', 'marketing', `STORYBOARD-${sb.id}-v${sb.version}.md`);
|
|
30
|
+
writeFileSync(out, render(sb, file) + '\n');
|
|
31
|
+
console.log(`✓ ${file} → ${relative(STORYBOARD_ROOT, out)}`);
|
|
32
|
+
} else console.log(`✓ ${file} · v${sb.version} · ${sb.status} · ${sb.kind === 'site' ? `사이트 ${sb.sites?.length ?? 0}` : `샷 ${sb.shots.length}`}`);
|
|
33
|
+
}
|
|
34
|
+
if (cmd === 'lint') debug.log('storyboard.lint', 'checked', { files: reports.length, errorCount, warningCount });
|
|
35
|
+
if (json) console.log(JSON.stringify({ files: reports, errorCount, warningCount }));
|
|
36
|
+
return errorCount ? 1 : 0;
|
|
37
|
+
}
|
|
@@ -71,7 +71,8 @@ export function applyTestStateDirFlagFromArgv(): string | undefined {
|
|
|
71
71
|
* 뒤에만 있어 사람이 부를 입구가 없었고, 그래서 문서 174곳에 수동 주문이 화석으로 남았다.
|
|
72
72
|
*
|
|
73
73
|
* 하는 일: nexus state root ⊕ ELANOUS_STATE_DIR ⊕ config-dir 을 **한 뿌리로** 세우고,
|
|
74
|
-
* config
|
|
74
|
+
* 운영 config 가 있으면 test-safe 사본을 물질화하고, 없으면 격리 기본값을 사용하며,
|
|
75
|
+
* config 경로가 test 루트 밖이면 **기동을 거부**한다. */
|
|
75
76
|
export function applyIsolatedRoot(dir: string): void {
|
|
76
77
|
const { setTestStateRoot } = require('../nexus/paths.js') as typeof import('../nexus/paths.js');
|
|
77
78
|
setTestStateRoot(dir);
|
|
@@ -98,7 +99,7 @@ export function applyIsolatedRoot(dir: string): void {
|
|
|
98
99
|
}
|
|
99
100
|
cfgDir.setElanousConfigDir(dir);
|
|
100
101
|
}
|
|
101
|
-
// config
|
|
102
|
+
// config 사본이 없고 운영 원본이 있으면 물질화하고, 있으면 drift 경고만
|
|
102
103
|
// (자동 덮어쓰기 금지 — 명시적 sync 원칙). 유닛 테스트(NODE_ENV=test)는
|
|
103
104
|
// 실 운영 config/secrets 를 temp 로 복사하지 않도록 sync 스킵.
|
|
104
105
|
if (process.env.NODE_ENV !== 'test') {
|
|
@@ -106,8 +107,14 @@ export function applyIsolatedRoot(dir: string): void {
|
|
|
106
107
|
const { existsSync } = require('node:fs') as typeof import('node:fs');
|
|
107
108
|
const { join } = require('node:path') as typeof import('node:path');
|
|
108
109
|
if (!existsSync(join(dir, 'config.json'))) {
|
|
109
|
-
|
|
110
|
-
|
|
110
|
+
// A fresh build/test environment may have no operating config to copy.
|
|
111
|
+
// Keep the isolated config root and use the ordinary safe defaults there.
|
|
112
|
+
if (existsSync(join(sync.prodConfigDir(), 'config.json'))) {
|
|
113
|
+
const r = sync.syncTestConfig(dir);
|
|
114
|
+
console.error(`[test-isolation] 운영 config 물질화 → ${r.testConfigPath} (telegram=${r.telegramMode})`);
|
|
115
|
+
} else {
|
|
116
|
+
console.error(`[test-isolation] 운영 config 없음 — ${dir} 의 기본 설정 사용`);
|
|
117
|
+
}
|
|
111
118
|
} else if (sync.isTestConfigStale(dir)) {
|
|
112
119
|
console.error(`[test-isolation] ⚠️ 운영 config 가 테스트 사본보다 최신 — 'elanous config sync-test' 로 갱신 권장`);
|
|
113
120
|
}
|