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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
4
|
import { Database } from 'bun:sqlite';
|
|
5
5
|
import { releaseLedgerRoot } from '../instance/resolve.js';
|
|
@@ -17,8 +17,8 @@ function legacyVersions(): string[] {
|
|
|
17
17
|
if (!existsSync(root)) return [];
|
|
18
18
|
return readdirSync(root).filter((version) => /^\d+\.\d+\.\d+(?:-(?:rc|alpha|beta)\.\d+)?$/.test(version) && existsSync(jsonPath(version)));
|
|
19
19
|
}
|
|
20
|
-
export function releasedVersion(): string {
|
|
21
|
-
const root = join(
|
|
20
|
+
export function releasedVersion(ledgerRoot = releaseLedgerRoot()): string {
|
|
21
|
+
const root = join(ledgerRoot, 'release');
|
|
22
22
|
if (!existsSync(root)) return '';
|
|
23
23
|
return readdirSync(root).filter((version) => /^\d+\.\d+\.\d+$/.test(version) && existsSync(join(root, version, 'release.json')))
|
|
24
24
|
.filter((version) => {
|
|
@@ -30,8 +30,8 @@ export function releasedVersion(): string {
|
|
|
30
30
|
})[0] ?? '';
|
|
31
31
|
}
|
|
32
32
|
const walWait = new Int32Array(new SharedArrayBuffer(4));
|
|
33
|
-
function open(): Database {
|
|
34
|
-
const path = join(
|
|
33
|
+
function open(root = releaseLedgerRoot()): Database {
|
|
34
|
+
const path = join(root, 'release', 'features.sqlite');
|
|
35
35
|
mkdirSync(dirname(path), { recursive: true });
|
|
36
36
|
const db = new Database(path, { create: true, strict: true });
|
|
37
37
|
try {
|
|
@@ -52,11 +52,61 @@ function open(): Database {
|
|
|
52
52
|
CREATE TABLE IF NOT EXISTS assignments (feature_id TEXT NOT NULL REFERENCES features(id), version TEXT NOT NULL, status TEXT NOT NULL, disposition TEXT, evidence TEXT, title_override TEXT, owner TEXT, kind TEXT, updated_at TEXT NOT NULL, updated_by TEXT NOT NULL, PRIMARY KEY(feature_id, version));
|
|
53
53
|
CREATE TABLE IF NOT EXISTS events (seq INTEGER PRIMARY KEY AUTOINCREMENT, at TEXT NOT NULL, by TEXT NOT NULL, feature_id TEXT NOT NULL, version TEXT NOT NULL, field TEXT NOT NULL, "from" TEXT, "to" TEXT, released TEXT NOT NULL, dev TEXT NOT NULL);
|
|
54
54
|
CREATE TABLE IF NOT EXISTS evidence (feature_id TEXT NOT NULL, version TEXT NOT NULL, ref TEXT NOT NULL, at TEXT NOT NULL, by TEXT NOT NULL);
|
|
55
|
-
CREATE TABLE IF NOT EXISTS imported_versions (version TEXT PRIMARY KEY);`);
|
|
55
|
+
CREATE TABLE IF NOT EXISTS imported_versions (version TEXT PRIMARY KEY, json_hash TEXT, imported_at TEXT);`);
|
|
56
|
+
const columns = db.query('PRAGMA table_info(imported_versions)').all() as Array<{ name: string }>;
|
|
57
|
+
if (!columns.some((column) => column.name === 'json_hash')) db.exec('ALTER TABLE imported_versions ADD COLUMN json_hash TEXT');
|
|
58
|
+
if (!columns.some((column) => column.name === 'imported_at')) db.exec('ALTER TABLE imported_versions ADD COLUMN imported_at TEXT');
|
|
59
|
+
db.exec('CREATE TABLE IF NOT EXISTS release_schedules (version TEXT PRIMARY KEY, cut_at TEXT NOT NULL, land_by TEXT, updated_at TEXT NOT NULL, updated_by TEXT NOT NULL)');
|
|
56
60
|
return db;
|
|
57
61
|
} catch (error) { db.close(); throw error; }
|
|
58
62
|
}
|
|
59
63
|
|
|
64
|
+
export interface ScheduleRow { version: string; cutAt: string; landBy: string | null; updatedAt: string; updatedBy: string }
|
|
65
|
+
type StoredSchedule = { version: string; cut_at: string; land_by: string | null; updated_at: string; updated_by: string };
|
|
66
|
+
function scheduleRow(row: StoredSchedule): ScheduleRow {
|
|
67
|
+
return { version: row.version, cutAt: row.cut_at, landBy: row.land_by, updatedAt: row.updated_at, updatedBy: row.updated_by };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function readSchedule(version: string, root?: string): ScheduleRow | null {
|
|
71
|
+
validateVersion(version);
|
|
72
|
+
if (!existsSync(join(root ?? releaseLedgerRoot(), 'release', 'features.sqlite'))) return null;
|
|
73
|
+
const db = open(root);
|
|
74
|
+
try {
|
|
75
|
+
const row = db.query('SELECT * FROM release_schedules WHERE version = ?').get(version) as StoredSchedule | null;
|
|
76
|
+
return row ? scheduleRow(row) : null;
|
|
77
|
+
} finally { db.close(); }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function readSchedules(root?: string): ScheduleRow[] {
|
|
81
|
+
const db = open(root);
|
|
82
|
+
try { return (db.query('SELECT * FROM release_schedules ORDER BY version').all() as StoredSchedule[]).map(scheduleRow); }
|
|
83
|
+
finally { db.close(); }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function writeSchedule(version: string, patch: { cutAt?: string; landBy?: string }, by: string, root?: string): ScheduleRow {
|
|
87
|
+
validateVersion(version);
|
|
88
|
+
const db = open(root);
|
|
89
|
+
try {
|
|
90
|
+
return transaction(db, () => {
|
|
91
|
+
const previous = db.query('SELECT * FROM release_schedules WHERE version = ?').get(version) as StoredSchedule | null;
|
|
92
|
+
const cutAt = patch.cutAt ?? previous?.cut_at;
|
|
93
|
+
if (!cutAt) throw new CliUserError('새 판에는 --cut-at 이 필요하다');
|
|
94
|
+
const landBy = patch.landBy ?? previous?.land_by ?? null;
|
|
95
|
+
if (previous && previous.cut_at === cutAt && previous.land_by === landBy) return scheduleRow(previous);
|
|
96
|
+
const at = new Date().toISOString();
|
|
97
|
+
db.query(`INSERT INTO release_schedules (version, cut_at, land_by, updated_at, updated_by) VALUES (?, ?, ?, ?, ?)
|
|
98
|
+
ON CONFLICT(version) DO UPDATE SET cut_at=excluded.cut_at, land_by=excluded.land_by, updated_at=excluded.updated_at, updated_by=excluded.updated_by`)
|
|
99
|
+
.run(version, cutAt, landBy, at, by);
|
|
100
|
+
for (const [field, from, to] of [
|
|
101
|
+
['cut_at', previous?.cut_at ?? null, cutAt], ['land_by', previous?.land_by ?? null, landBy],
|
|
102
|
+
] as const) {
|
|
103
|
+
if (from !== to) record(db, version, { at, by, id: '@version', field, from, to, released: releasedVersion(root), dev: devVersion() });
|
|
104
|
+
}
|
|
105
|
+
return { version, cutAt, landBy, updatedAt: at, updatedBy: by };
|
|
106
|
+
});
|
|
107
|
+
} finally { db.close(); }
|
|
108
|
+
}
|
|
109
|
+
|
|
60
110
|
function transaction<T>(db: Database, work: () => T): T {
|
|
61
111
|
db.exec('BEGIN IMMEDIATE');
|
|
62
112
|
try { const result = work(); db.exec('COMMIT'); return result; }
|
|
@@ -105,14 +155,32 @@ function putItem(db: Database, version: string, item: ChecklistItem): void {
|
|
|
105
155
|
ON CONFLICT(feature_id, version) DO UPDATE SET status=excluded.status, disposition=excluded.disposition, evidence=excluded.evidence, owner=excluded.owner, kind=excluded.kind, updated_at=excluded.updated_at, updated_by=excluded.updated_by`)
|
|
106
156
|
.run(item.id, version, item.status, item.disposition ?? null, item.evidence ?? null, feature && feature.title !== item.title ? item.title : null, item.owner ?? null, item.kind ?? null, item.updatedAt, item.updatedBy);
|
|
107
157
|
}
|
|
158
|
+
function jsonHash(contents: string | Buffer): string { return createHash('sha256').update(contents).digest('hex'); }
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* When did the ledger take this item away from `version`? A `move` out of it, a `remove` at it, or (carry-forward
|
|
162
|
+
* without a move event) the item now living in another version. null = never left — a JSON-only item is new.
|
|
163
|
+
* OP 10-02: without this, an old build's checklist.json re-added items the ledger had moved or removed (duplicates).
|
|
164
|
+
*/
|
|
165
|
+
function ledgerLeftAt(db: Database, id: string, version: string): string | null {
|
|
166
|
+
const event = db.query(`SELECT MAX(at) AS at FROM events WHERE feature_id = ? AND
|
|
167
|
+
((field = 'move' AND "from" = ?) OR (field = 'remove' AND version = ?))`).get(id, JSON.stringify(version), version) as { at: string | null };
|
|
168
|
+
const elsewhere = db.query('SELECT MAX(updated_at) AS at FROM assignments WHERE feature_id = ? AND version <> ?').get(id, version) as { at: string | null };
|
|
169
|
+
const times = [event.at, elsewhere.at].filter((t): t is string => typeof t === 'string');
|
|
170
|
+
return times.length ? times.sort().at(-1)! : null;
|
|
171
|
+
}
|
|
172
|
+
|
|
108
173
|
function importOnDb(db: Database, version: string): boolean {
|
|
109
|
-
|
|
174
|
+
const previous = db.query('SELECT json_hash FROM imported_versions WHERE version = ?').get(version) as { json_hash: string | null } | null;
|
|
110
175
|
const path = jsonPath(version);
|
|
111
176
|
if (!existsSync(path)) {
|
|
112
|
-
db.query('INSERT INTO imported_versions (version) VALUES (?)').run(version);
|
|
177
|
+
if (!previous) db.query('INSERT INTO imported_versions (version) VALUES (?)').run(version);
|
|
113
178
|
return false;
|
|
114
179
|
}
|
|
115
|
-
const
|
|
180
|
+
const contents = readFileSync(path);
|
|
181
|
+
const hash = jsonHash(contents);
|
|
182
|
+
if (previous && previous.json_hash === hash) return false;
|
|
183
|
+
const data = JSON.parse(contents.toString('utf8')) as Checklist;
|
|
116
184
|
if (data.version !== version) throw new CliUserError(`체크리스트 판 불일치: ${path}`);
|
|
117
185
|
if (!Array.isArray(data.items) || !Array.isArray(data.history)) throw new CliUserError(`잘못된 체크리스트: ${path}`);
|
|
118
186
|
const duplicate = new Set<string>();
|
|
@@ -120,25 +188,83 @@ function importOnDb(db: Database, version: string): boolean {
|
|
|
120
188
|
if (!item.id?.trim() || !item.title?.trim() || duplicate.has(item.id) || !['green', 'yellow', 'red', 'done'].includes(item.status)) throw new CliUserError(`잘못된 체크리스트 칸: ${version} ${item.id}`);
|
|
121
189
|
duplicate.add(item.id);
|
|
122
190
|
}
|
|
191
|
+
if (!previous) {
|
|
192
|
+
for (const item of data.items) {
|
|
193
|
+
const existing = db.query('SELECT title FROM features WHERE id = ?').get(item.id) as { title: string } | null;
|
|
194
|
+
// Keep a legacy per-version title until an explicit retitle unifies the feature identity.
|
|
195
|
+
if (!existing) putItem(db, version, item);
|
|
196
|
+
else {
|
|
197
|
+
db.query('INSERT OR IGNORE INTO assignments (feature_id, version, status, disposition, evidence, title_override, owner, kind, updated_at, updated_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')
|
|
198
|
+
.run(item.id, version, item.status, item.disposition ?? null, item.evidence ?? null, item.title === existing.title ? null : item.title, item.owner ?? null, item.kind ?? null, item.updatedAt, item.updatedBy);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
for (const entry of data.history) record(db, version, entry);
|
|
202
|
+
db.query('INSERT INTO imported_versions (version, json_hash, imported_at) VALUES (?, ?, ?)').run(version, hash, new Date().toISOString());
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const ledger = new Map(items(db, version).map((item) => [item.id, item]));
|
|
207
|
+
const changed: string[] = [];
|
|
208
|
+
let added = 0;
|
|
209
|
+
const ledgerWonIds: string[] = [];
|
|
210
|
+
const leftIds: string[] = [];
|
|
123
211
|
for (const item of data.items) {
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
212
|
+
const current = ledger.get(item.id);
|
|
213
|
+
if (!current) {
|
|
214
|
+
// Moved or removed in the ledger: only a JSON edit made after that may bring it back.
|
|
215
|
+
const leftAt = ledgerLeftAt(db, item.id, version);
|
|
216
|
+
if (leftAt && !(Date.parse(item.updatedAt) > Date.parse(leftAt))) { leftIds.push(item.id); continue; }
|
|
217
|
+
const feature = db.query('SELECT title FROM features WHERE id = ?').get(item.id) as { title: string } | null;
|
|
218
|
+
putItem(db, version, item);
|
|
219
|
+
if (feature && feature.title !== item.title) db.query('UPDATE assignments SET title_override = ? WHERE feature_id = ? AND version = ?').run(item.title, item.id, version);
|
|
220
|
+
added++;
|
|
221
|
+
changed.push(item.id);
|
|
222
|
+
record(db, version, { at: new Date().toISOString(), by: item.updatedBy, id: item.id, field: 'reimport', from: null, to: item, released: data.released, dev: data.dev });
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
const fields = ['title', 'status', 'evidence', 'owner', 'disposition', 'kind'] as const;
|
|
226
|
+
const baseEvidence = (db.query('SELECT evidence FROM assignments WHERE feature_id = ? AND version = ?').get(item.id, version) as { evidence: string | null }).evidence ?? undefined;
|
|
227
|
+
const preserveRefs = item.evidence === baseEvidence || item.evidence === current.evidence;
|
|
228
|
+
const differs = fields.some((field) => field === 'evidence'
|
|
229
|
+
? (!preserveRefs && current.evidence !== item.evidence)
|
|
230
|
+
: current[field] !== item[field]);
|
|
231
|
+
if (!differs) continue;
|
|
232
|
+
if (!(Date.parse(item.updatedAt) > Date.parse(current.updatedAt))) {
|
|
233
|
+
ledgerWonIds.push(item.id);
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
const incoming: ChecklistItem = { ...current, status: item.status, updatedAt: item.updatedAt, updatedBy: item.updatedBy };
|
|
237
|
+
for (const field of fields) {
|
|
238
|
+
if (field === 'status') continue;
|
|
239
|
+
if (field === 'evidence' && preserveRefs) continue;
|
|
240
|
+
if (item[field] === undefined) delete (incoming as unknown as Record<string, unknown>)[field];
|
|
241
|
+
else (incoming as unknown as Record<string, unknown>)[field] = item[field];
|
|
242
|
+
}
|
|
243
|
+
if (!preserveRefs) db.query('DELETE FROM evidence WHERE feature_id = ? AND version = ?').run(item.id, version);
|
|
244
|
+
const persisted = preserveRefs ? { ...incoming, evidence: baseEvidence } : incoming;
|
|
245
|
+
putItem(db, version, persisted);
|
|
246
|
+
if (incoming.title !== current.title) {
|
|
247
|
+
db.query('UPDATE assignments SET title_override = ? WHERE feature_id = ? AND version = ?')
|
|
248
|
+
.run(incoming.title === (db.query('SELECT title FROM features WHERE id = ?').get(item.id) as { title: string }).title ? null : incoming.title, item.id, version);
|
|
130
249
|
}
|
|
250
|
+
changed.push(item.id);
|
|
251
|
+
record(db, version, { at: new Date().toISOString(), by: item.updatedBy, id: item.id, field: 'reimport', from: current, to: incoming, released: data.released, dev: data.dev });
|
|
131
252
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
253
|
+
db.query('UPDATE imported_versions SET json_hash = ?, imported_at = ? WHERE version = ?').run(hash, new Date().toISOString(), version);
|
|
254
|
+
const ledgerWon = ledgerWonIds.length;
|
|
255
|
+
debug.log('release.features', 'reimported', { version, changed: changed.length - added, added, ledgerWon, notReadded: leftIds.length });
|
|
256
|
+
// Two separate lines: what was taken from the old build's JSON, and where the JSON differs but the ledger is newer
|
|
257
|
+
// (a conflict a person should know about even though nothing changed in the ledger).
|
|
258
|
+
if (changed.length) console.error(`⚠ checklist ${version}: 옛 판이 checklist.json 에 쓴 ${changed.length}칸을 다시 들였다(${changed.slice(0, 5).join(', ')})`);
|
|
259
|
+
if (leftIds.length) console.error(`⚠ checklist ${version}: 원장에서 옮기거나 지운 칸 ${leftIds.length}은 옛 판 checklist.json 에 남아 있어도 다시 넣지 않았다(${leftIds.slice(0, 5).join(', ')})`);
|
|
260
|
+
if (ledgerWon) console.error(`⚠ checklist ${version}: 옛 판 checklist.json 과 원장이 다르다 — 원장이 더 새로워 원장 값 유지 ${ledgerWon}칸(${ledgerWonIds.slice(0, 5).join(', ')})`);
|
|
261
|
+
return false;
|
|
135
262
|
}
|
|
136
263
|
export function importJson(version: string): boolean {
|
|
137
264
|
validateVersion(version);
|
|
138
265
|
if (!existsSync(jsonPath(version))) return false;
|
|
139
266
|
const db = open();
|
|
140
267
|
try {
|
|
141
|
-
if (db.query('SELECT 1 FROM imported_versions WHERE version = ?').get(version)) return false;
|
|
142
268
|
const imported = transaction(db, () => importOnDb(db, version));
|
|
143
269
|
if (imported) debug.log('release.features', 'imported', { version });
|
|
144
270
|
return imported;
|
|
@@ -315,13 +441,20 @@ export function exportJson(version: string, released = releasedVersion(), dev =
|
|
|
315
441
|
const path = jsonPath(version);
|
|
316
442
|
mkdirSync(dirname(path), { recursive: true });
|
|
317
443
|
const temp = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
444
|
+
const contents = `${JSON.stringify(data, null, 2)}\n`;
|
|
318
445
|
try {
|
|
319
|
-
writeFileSync(temp,
|
|
446
|
+
writeFileSync(temp, contents, { mode: 0o600 });
|
|
320
447
|
chmodSync(temp, 0o600);
|
|
321
448
|
renameSync(temp, path);
|
|
322
449
|
} finally { if (existsSync(temp)) rmSync(temp); }
|
|
323
450
|
const db = open();
|
|
324
|
-
try { transaction(db, () => {
|
|
451
|
+
try { transaction(db, () => {
|
|
452
|
+
// Track only our bytes: a legacy replacement after rename must remain detectable.
|
|
453
|
+
const hash = jsonHash(contents);
|
|
454
|
+
db.query(`INSERT INTO imported_versions (version, json_hash, imported_at) VALUES (?, ?, ?)
|
|
455
|
+
ON CONFLICT(version) DO UPDATE SET json_hash = excluded.json_hash, imported_at = excluded.imported_at`)
|
|
456
|
+
.run(version, hash, new Date().toISOString());
|
|
457
|
+
}); }
|
|
325
458
|
finally { db.close(); }
|
|
326
459
|
return data;
|
|
327
460
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { CliUserError } from '../cli/cli-user-error.js';
|
|
2
|
+
import { debug } from '../debug/log.js';
|
|
3
|
+
import { readSchedule, readSchedules, writeSchedule, type ScheduleRow } from './feature-store.js';
|
|
4
|
+
|
|
5
|
+
export type ReleaseSchedule = ScheduleRow;
|
|
6
|
+
|
|
7
|
+
function utcIso(input: string): string {
|
|
8
|
+
const match = /^(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2})(?::(\d{2})(?:\.\d{1,3})?)?(Z|([+-])(\d{2}):(\d{2}))$/.exec(input);
|
|
9
|
+
if (!match) throw new CliUserError(`시각에는 ISO 오프셋 필요: ${input}`);
|
|
10
|
+
const hours = Number(match[6] ?? 0), minutes = Number(match[7] ?? 0);
|
|
11
|
+
const offset = (match[5] === '-' ? -1 : 1) * (hours * 60 + minutes);
|
|
12
|
+
const millis = Date.parse(input);
|
|
13
|
+
if (hours > 23 || minutes > 59 || !Number.isFinite(millis) ||
|
|
14
|
+
new Date(millis + offset * 60_000).toISOString().slice(0, 19) !== `${match[1]}T${match[2]}:${match[3] ?? '00'}`) {
|
|
15
|
+
throw new CliUserError(`잘못된 ISO 시각: ${input}`);
|
|
16
|
+
}
|
|
17
|
+
return new Date(millis).toISOString();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function formatKst(utc: string): string {
|
|
21
|
+
const parts = new Intl.DateTimeFormat('ko-KR', { timeZone: 'Asia/Seoul', month: '2-digit', day: '2-digit', weekday: 'short', hour: '2-digit', minute: '2-digit', hourCycle: 'h23' }).formatToParts(new Date(utc));
|
|
22
|
+
const get = (type: Intl.DateTimeFormatPartTypes) => parts.find((part) => part.type === type)!.value;
|
|
23
|
+
return `${get('month')}-${get('day')}(${get('weekday')}) ${get('hour')}:${get('minute')} KST`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function getSchedule(version: string, root?: string): ReleaseSchedule | null {
|
|
27
|
+
const schedule = readSchedule(version, root);
|
|
28
|
+
debug.log('release.schedule', 'read', { version, cutAt: schedule?.cutAt ?? null, landBy: schedule?.landBy ?? null });
|
|
29
|
+
return schedule;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function listSchedules(root?: string): ReleaseSchedule[] {
|
|
33
|
+
const schedules = readSchedules(root);
|
|
34
|
+
for (const { version, cutAt, landBy } of schedules) debug.log('release.schedule', 'read', { version, cutAt, landBy });
|
|
35
|
+
return schedules;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function setSchedule(version: string, patch: { cutAt?: string; landBy?: string }, by: string, root?: string): ReleaseSchedule {
|
|
39
|
+
const schedule = writeSchedule(version, {
|
|
40
|
+
...(patch.cutAt !== undefined ? { cutAt: utcIso(patch.cutAt) } : {}),
|
|
41
|
+
...(patch.landBy !== undefined ? { landBy: utcIso(patch.landBy) } : {}),
|
|
42
|
+
}, by, root);
|
|
43
|
+
debug.log('release.schedule', 'set', { version, cutAt: schedule.cutAt, landBy: schedule.landBy });
|
|
44
|
+
return schedule;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function formatSchedule(schedule: ReleaseSchedule): string {
|
|
48
|
+
const cut = formatKst(schedule.cutAt);
|
|
49
|
+
const deadline = schedule.landBy ? formatKst(schedule.landBy) : null;
|
|
50
|
+
const sameDay = schedule.landBy && new Date(Date.parse(schedule.landBy) + 9 * 60 * 60_000).toISOString().slice(0, 10)
|
|
51
|
+
=== new Date(Date.parse(schedule.cutAt) + 9 * 60 * 60_000).toISOString().slice(0, 10);
|
|
52
|
+
return `${schedule.version} 컷 ${cut}${deadline ? ` · 착지 마감 ${sameDay ? deadline.split(' ')[1] : deadline}` : ''}`;
|
|
53
|
+
}
|
package/src/repl/index.ts
CHANGED
|
@@ -21,6 +21,7 @@ import { createInterface, type Interface as ReadlineInterface } from 'node:readl
|
|
|
21
21
|
import { existsSync, readFileSync } from 'node:fs';
|
|
22
22
|
import { resolve as resolvePath, isAbsolute, basename } from 'node:path';
|
|
23
23
|
import * as ui from '../ui.js';
|
|
24
|
+
import { suggestProjectForFolder } from '../project/project-store.js';
|
|
24
25
|
import { runTurn } from '../session/chat.js';
|
|
25
26
|
import {
|
|
26
27
|
ensureCliSession,
|
|
@@ -450,6 +451,12 @@ export async function runRepl(opts: ReplOpts): Promise<number> {
|
|
|
450
451
|
const session = ensureCliSession(cfg, initialId);
|
|
451
452
|
setActiveSessionId(session.id);
|
|
452
453
|
setAmbientSessionId(session.id);
|
|
454
|
+
if (!opts.jsonOutput && (!initialId || session.id !== initialId)) {
|
|
455
|
+
try {
|
|
456
|
+
const project = suggestProjectForFolder(process.cwd());
|
|
457
|
+
if (project) ui.info(`Project suggestion: ${project.name} (${project.id})`);
|
|
458
|
+
} catch { /* suggestions must not interrupt the REPL */ }
|
|
459
|
+
}
|
|
453
460
|
const sessionIdRef = { current: session.id };
|
|
454
461
|
|
|
455
462
|
const tools = opts.enableTools ? buildReplTools() : undefined;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// The owner hands a seat a task from Telegram: /coo /cto /cmo /cxo → one «[대표] → <seat>» line on the coordination PR,
|
|
2
|
-
// one 'ceo-task' message in the seat store, and a «받음» reply.
|
|
2
|
+
// one 'ceo-task' message in the seat store, and a «받음» reply. The Telegram entrypoint writes only for the owner in a private chat.
|
|
3
3
|
// Goal: 내부 문서 `ASK-the-ceo-cannot-hand-a-seat-a-task-from-telegram-2026-10-02` (C2 phase 1).
|
|
4
4
|
import { debug } from '../debug/log.js';
|
|
5
5
|
import type { MessageEnvelope } from '../msg/msg-store.js';
|
|
@@ -16,6 +16,8 @@ export interface CeoCommandDeps {
|
|
|
16
16
|
replyTarget: string | null;
|
|
17
17
|
runGh: (args: string[], stdin: string) => Promise<number>;
|
|
18
18
|
append: (message: MessageEnvelope) => unknown;
|
|
19
|
+
/** Whether the seat inbox already holds a message carrying this request ref (retry after a crash must not deliver twice). */
|
|
20
|
+
hasMessage?: (seat: string, ref: string) => boolean;
|
|
19
21
|
now?: () => Date;
|
|
20
22
|
}
|
|
21
23
|
|
|
@@ -43,21 +45,52 @@ export async function handleCeoSeatCommand(command: CeoSeatCommand, args: string
|
|
|
43
45
|
debug.log('seat.dispatch', 'refused', { seat, reason: 'empty' });
|
|
44
46
|
return `사용법: /${command} <할 일>`;
|
|
45
47
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
return (await dispatchCeoTask(seat, task, deps)).reply;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface CeoTaskExtra {
|
|
52
|
+
attachments?: Array<{ name: string; path: string }>;
|
|
53
|
+
/** Paths supplied by callers that do not have attachment names. */
|
|
54
|
+
attachmentPaths?: readonly string[];
|
|
55
|
+
via?: 'telegram' | 'discord' | 'pwa';
|
|
56
|
+
/** Request ref written into the message body; with `deps.hasMessage` it makes delivery idempotent per request. */
|
|
57
|
+
ref?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface CeoTaskResult { reply: string; channel: 'posted' | 'failed' | 'unknown'; channelError?: string }
|
|
61
|
+
|
|
62
|
+
/** Dispatch an already-authorized seat request; callers must authenticate before invoking this helper. */
|
|
63
|
+
export async function dispatchCeoTask(seat: string, text: string, deps: CeoCommandDeps, extra: CeoTaskExtra = {}): Promise<CeoTaskResult> {
|
|
64
|
+
const task = text.replace(/\s+/g, ' ').trim();
|
|
65
|
+
if (!task) {
|
|
66
|
+
debug.log('seat.dispatch', 'refused', { seat, reason: 'empty' });
|
|
67
|
+
return { reply: '할 일을 입력해 주세요.', channel: 'unknown' };
|
|
68
|
+
}
|
|
69
|
+
const attachments = extra.attachments ?? [];
|
|
70
|
+
const attachmentPaths = extra.attachmentPaths ?? [];
|
|
71
|
+
const body = `${task}${attachments.map(({ name, path }) => `\n첨부: ${name} — ${path}`).join('')}${attachmentPaths.length ? `\n\n첨부 파일:\n${attachmentPaths.join('\n')}` : ''}${extra.ref ? `\n요청: ${extra.ref}` : ''}`;
|
|
72
|
+
if (extra.ref && deps.hasMessage?.(seat, extra.ref)) {
|
|
73
|
+
// A retry after a crash between delivery and the journal outcome: the message is already there, and whether
|
|
74
|
+
// the channel line went out is unknown — say so instead of posting it again or claiming it was posted.
|
|
75
|
+
debug.log('seat.dispatch', 'ceo-task-already-delivered', { seat, via: extra.via });
|
|
76
|
+
return { reply: `받음 — ${seat} 메시지함에 이미 있습니다.`, channel: 'unknown' };
|
|
77
|
+
}
|
|
78
|
+
deps.append({ from: 'CEO', to: seat, body, kind: 'ceo-task' });
|
|
79
|
+
debug.log('seat.dispatch', 'ceo-task', { seat, ...(extra.via ? { via: extra.via, attachments: attachments.length + attachmentPaths.length } : {}) });
|
|
80
|
+
const channelError = await postToChannel(seat, task, deps, extra);
|
|
48
81
|
if (channelError) {
|
|
49
82
|
debug.log('seat.dispatch', 'channel-failed', { seat, reason: channelError }, { level: 'warn' });
|
|
50
|
-
return `받음 — ${seat} 메시지함에는 넣었습니다. 다만 조율 채널에 못 남겼습니다(${channelError})
|
|
83
|
+
return { reply: `받음 — ${seat} 메시지함에는 넣었습니다. 다만 조율 채널에 못 남겼습니다(${channelError}).`, channel: 'failed', channelError };
|
|
51
84
|
}
|
|
52
|
-
|
|
53
|
-
return `받음 — ${seat}에 전했습니다.`;
|
|
85
|
+
return { reply: `받음 — ${seat}에 전했습니다.`, channel: 'posted' };
|
|
54
86
|
}
|
|
55
87
|
|
|
56
|
-
async function postToChannel(seat: string, task: string, deps: CeoCommandDeps): Promise<string | null> {
|
|
88
|
+
async function postToChannel(seat: string, task: string, deps: CeoCommandDeps, extra: CeoTaskExtra): Promise<string | null> {
|
|
57
89
|
const target = /^([\w.-]+\/[\w.-]+)#(\d+)$/.exec(deps.replyTarget?.trim() ?? '');
|
|
58
90
|
if (!target) return deps.replyTarget ? 'decisions.replyGhPr 형식이 owner/repo#N 이 아님' : 'decisions.replyGhPr 미설정';
|
|
59
91
|
try {
|
|
60
|
-
const
|
|
92
|
+
const attachmentCount = (extra.attachments?.length ?? 0) + (extra.attachmentPaths?.length ?? 0);
|
|
93
|
+
const code = await deps.runGh(['pr', 'comment', target[2]!, '--repo', target[1]!, '--body-file', '-'], `${ceoTaskLine(seat, task, deps.now?.() ?? new Date())}${attachmentCount ? ` · 첨부 ${attachmentCount}` : ''}${extra.via === 'pwa' ? ' (PWA)' : ''}`);
|
|
61
94
|
return code === 0 ? null : `gh 종료 코드 ${code}`;
|
|
62
95
|
} catch (error) {
|
|
63
96
|
return error instanceof Error ? error.message.slice(0, 80) : 'gh 실행 실패';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { runGh } from '../decisions/decision-cards.js';
|
|
2
|
+
import { openMsgStore } from '../msg/msg-store.js';
|
|
3
|
+
import type { UserConfig } from '../user-config.js';
|
|
4
|
+
import type { CeoCommandDeps } from './ceo-commands.js';
|
|
5
|
+
|
|
6
|
+
/** Endings are ordered from explicit punctuation to Korean question endings. Every row has a test example. */
|
|
7
|
+
export const CEO_QUESTION_ENDINGS = [
|
|
8
|
+
/[??]$/,
|
|
9
|
+
/야$/,
|
|
10
|
+
/니$/,
|
|
11
|
+
/나요$/,
|
|
12
|
+
/까요$/,
|
|
13
|
+
/인가요$/,
|
|
14
|
+
/어때$/,
|
|
15
|
+
/알려\s?줘$/,
|
|
16
|
+
/보여줘$/,
|
|
17
|
+
/상황$/,
|
|
18
|
+
/어디까지$/,
|
|
19
|
+
] as const;
|
|
20
|
+
|
|
21
|
+
export function classifyCeoIntent(text: string): 'question' | 'task' {
|
|
22
|
+
const body = text.trim();
|
|
23
|
+
return CEO_QUESTION_ENDINGS.some((ending) => ending.test(body)) ? 'question' : 'task';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** The same channel + seat inbox delivery dependencies used by /coo, /cto, /cmo and /cxo. */
|
|
27
|
+
export function ceoTaskDeps(cfg: UserConfig, ownerId: string | null): CeoCommandDeps {
|
|
28
|
+
const replyTarget = (cfg.raw?.decisions as { replyGhPr?: unknown } | undefined)?.replyGhPr;
|
|
29
|
+
return {
|
|
30
|
+
ownerId,
|
|
31
|
+
replyTarget: typeof replyTarget === 'string' ? replyTarget : null,
|
|
32
|
+
runGh,
|
|
33
|
+
append: (message) => {
|
|
34
|
+
const store = openMsgStore();
|
|
35
|
+
try { return store.append(message); } finally { store.close(); }
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|