elanous 0.2.18 → 0.2.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/chunks/{1307-30dff22c9e1f3073.js → 1307-77fad80cf54477c6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1612-f60c0c93ec773f81.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2116-6429c188eb846ab8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/224-e7be8f762fe74986.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2489-ff55a35b202c0f94.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2549-89c518a4c8ed4d00.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3857-995ea9f5ef33d71e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4144-fad095b48a1862ee.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5755-ea7ed88eb956f47b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5977-94ed6f78e9814b2b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7748-6e51bfe927019f5c.js +25 -0
- package/apps/pwa/out/_next/static/chunks/8401-35e94e9091f5eb8d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9247-c1753a059e7bac08.js +1 -0
- package/apps/pwa/out/_next/static/chunks/a9f2b54d-33c0689b70ed8cf3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/{page-d92f7ee3994e84c7.js → page-c855c24de7db74fe.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-4733c967b56a4277.js → page-85b316bb4235980b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/{page-d22fd849cc22f104.js → page-9b2b26a33cb2cd42.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-bb742f995c6769e7.js → page-c9955fde67b1bcb5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/{page-02b073a2ac793b83.js → page-7a852956c74015a3.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-c5a5c621be0e04ae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-8a9b3594bc23289b.js → page-4ba4abe096b6c2a8.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-575d4a8b4f7becb2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/{page-308111abadf35670.js → page-f5d56ff5f45ad164.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-2260e9eef0357421.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/{page-5d97b2901e0d1ea6.js → page-7c058069c5e7fa63.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-dc5da0b8a2edb8a4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-8c3d8e63ae2e1c98.js → page-466bf0ee16ad1432.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-9b72a07d424701c6.js → page-a23bc71713bcc08f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-ed4a6f799f005d09.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-a572f58cd3ca5864.js → page-9cd565005b93aca6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-55f0f40c6db95f15.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/{page-be5ea965a07fbc41.js → page-474cb5888ea0e8f3.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-35bfec2e631d3eae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/{page-fa244bd146a00ae8.js → page-9ca21f9ad3208a61.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-44d56d6391965a6a.js → page-f9ccb1b1adbea14d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-83a700d4c754d802.js → page-ec102c9b8b691e37.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{not-found-7b3170f374f77b9d.js → not-found-a9c2cfa8dda922a5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-6da35144878feb6d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-f6055495e9ed7ea8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-25204694fa9984a5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-a60e035f151d84d3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-189c587f36874538.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-c56634bde705a65d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/{page-1467047104141297.js → page-db03ae3589a81c9e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-4a6463b97f0ad94f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/{page-102310930791197a.js → page-75bffecc9aa1004e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-a8d2fe0c669e0de4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-6ed0b73bae8351d8.js → page-31d5b875760bb62d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-c26fa10aac6c21c4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/{page-43336055451f6e37.js → page-68793c6e10331d66.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/share/{page-3cc47dca0922ab9a.js → page-46d5201d2207a511.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/{page-a77ee355a2304ff0.js → page-d81c07a7b4f618ae.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/{page-b17cd46cf0e9b8cf.js → page-be1c4e8697f22360.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-cf0a4b4abead71ea.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-a4717e2c4bf7a28f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/{page-7bf54c9bb9274af8.js → page-0fd895fab9b62c1b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-8b6c1454a8e8f306.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-c6e4364d9de8f717.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-0acc8e198f7b0dfb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/{page-134da468538baf48.js → page-34882c84f84021c5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-0506a1e0e4bdb2f5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{webpack-c2782b330447ffb6.js → webpack-c260571513bf43c2.js} +1 -1
- package/apps/pwa/out/_next/static/css/{e99e3cff94ced78a.css → 599369d853c61df7.css} +1 -1
- package/apps/pwa/out/_next/static/css/c23d959d3ee15e57.css +1 -0
- package/apps/pwa/out/_next/static/css/d3427996da11e480.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +14 -14
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +14 -14
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +14 -14
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +14 -14
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +14 -14
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +14 -14
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +14 -14
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +14 -14
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +14 -14
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +14 -14
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +14 -14
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +16 -15
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +14 -14
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +14 -14
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +14 -14
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +16 -15
- 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/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +24 -23
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +14 -14
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +14 -14
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +14 -14
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +14 -14
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +14 -14
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +14 -14
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +14 -14
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +14 -14
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +15 -15
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +14 -14
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +14 -14
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +14 -14
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +14 -14
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +14 -14
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +14 -14
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +14 -14
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +14 -14
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +14 -14
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +14 -14
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +14 -14
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +14 -14
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +14 -14
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +14 -14
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +14 -14
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +14 -14
- package/graphs/demo/editor-recipes.yaml +22 -0
- package/graphs/ops/guardian.yaml +26 -0
- package/graphs/ops/recipes.yaml +3 -0
- package/graphs/retro/daily.yaml +25 -0
- package/graphs/retro/recipes.yaml +24 -0
- package/package.json +1 -1
- package/scripts/ask-marker-check.ts +3 -1
- package/scripts/coord-channel-watch.sh +2 -2
- package/scripts/discord-seat-probe.ts +3 -1
- package/scripts/land-global-checks.ts +89 -0
- package/scripts/lib/pch-regress-map.ts +24 -0
- package/scripts/morning-report.ts +18 -4
- package/scripts/pch-regress.ts +88 -0
- package/scripts/release-loop/gate-diff.ts +18 -0
- package/scripts/release-loop/gate-node.ts +628 -65
- package/scripts/release-loop/gate-partial.ts +106 -0
- package/scripts/release-loop/gate-timing.ts +229 -0
- package/scripts/release-loop/preflight.ts +6 -4
- package/scripts/release-loop/publish-window.ts +141 -0
- package/scripts/release-loop/release-docs.ts +1 -4
- package/scripts/release-loop/release-universe.ts +38 -0
- package/scripts/release-loop/resume-release.ts +67 -5
- package/scripts/release-loop/shard-plan.ts +65 -0
- package/scripts/release-loop/unattended-release.ts +12 -2
- package/scripts/release-story/draft.ts +5 -1
- package/scripts/retro/shadow-compare.ts +71 -0
- package/scripts/rhythm/daily.ts +6 -1
- package/scripts/video/scene-table.ts +159 -0
- package/src/acp/core-turn-bridge.ts +39 -2
- package/src/agent-mission/driver.ts +34 -6
- package/src/autopilot/build/llm-conflict-merge.ts +132 -7
- package/src/autopilot/build/merge-intent.ts +135 -7
- package/src/away/presence.ts +40 -0
- package/src/away/release-watch.ts +272 -0
- package/src/chat/index.ts +2 -1
- package/src/chat/short-question.ts +3 -40
- package/src/cli/away-cli.ts +68 -0
- package/src/cli/config-drift.ts +513 -0
- package/src/cli/guardian-cli.ts +49 -0
- package/src/cli/intake-cli.ts +13 -7
- package/src/cli/log-event-names.ts +5 -0
- package/src/cli/pod-cli.ts +5 -1
- package/src/cli/pr-cli.ts +20 -0
- package/src/cli/pty-drive-cli.ts +14 -3
- package/src/cli/release-cli.ts +52 -12
- package/src/cli/retro-cli.ts +78 -0
- package/src/cli/setup-cli.ts +31 -1
- package/src/cli/tasks-cli.ts +193 -14
- package/src/context-bus/context-now-public.ts +1 -1
- package/src/context-bus/context-now-surfaces.ts +16 -6
- package/src/context-bus/context-now.ts +97 -3
- package/src/core-turn/short-question.ts +100 -0
- package/src/dashboard/dashboard-status-lines.ts +24 -4
- package/src/dashboard/index.ts +50 -5
- package/src/dashboard/input/slash-executor.ts +6 -2
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +68 -40
- package/src/dashboard/slash-runtime/decide-slash.ts +60 -0
- package/src/dashboard/slash-runtime/help-from-registry.ts +62 -23
- package/src/dashboard/slash-runtime/help-groups.ts +44 -0
- package/src/dashboard/slash-runtime/model-choices.ts +125 -0
- package/src/domains/guardian-tick.ts +138 -0
- package/src/domains/outbound-alert.ts +44 -13
- package/src/domains/outbound-log-probe.ts +6 -1
- package/src/domains/schedule-registry.ts +23 -0
- package/src/domains/telegram-kind-route.ts +39 -0
- package/src/domains/watch-default-scheduler.ts +95 -0
- package/src/esc-abort-gate.ts +44 -0
- package/src/graph-runner/graph-run-api.ts +205 -0
- package/src/graph-runner/runner.ts +36 -6
- package/src/harness/harness-cli-command.ts +420 -51
- package/src/harness/harness-queue.ts +315 -25
- package/src/harness/harness-salvage-cli.ts +140 -0
- package/src/harness/harness-worktree-add.ts +68 -0
- package/src/harness/ledger-write-guard.ts +6 -16
- package/src/hq/fence-outcomes.ts +63 -0
- package/src/hq/fence-wrapper.ts +33 -0
- package/src/hq/hq.ts +17 -2
- package/src/index.ts +118 -6
- package/src/instance/test-write-guard-spy-first.fixture.ts +13 -0
- package/src/instance/test-write-guard.ts +200 -0
- package/src/intake-plane/digest.ts +39 -15
- package/src/intake-plane/harness-target.ts +4 -3
- package/src/intake-plane/intake-sources.ts +1 -0
- package/src/intake-plane/items.ts +1 -0
- package/src/loops/orchestrator/degradation.ts +57 -0
- package/src/loops/orchestrator/finish-rate.ts +33 -3
- package/src/loops/orchestrator/tick.ts +166 -45
- package/src/maturity/feature-maturity.ts +6 -4
- package/src/maturity/tui-slash-maturity.ts +1 -0
- package/src/mss/logging/log-store.ts +3 -0
- package/src/nexus/api/graph-kinds.ts +10 -4
- package/src/nexus/api/graphs-api.ts +178 -6
- package/src/nexus/api/http-server.ts +54 -8
- package/src/nexus/api/loop-edges.ts +292 -17
- package/src/nexus/api/ops-api.ts +112 -29
- package/src/nexus/api/ops-board-route.ts +48 -0
- package/src/nexus/index.ts +7 -0
- package/src/onboarding/entry-hints.ts +5 -0
- package/src/onboarding.ts +24 -6
- package/src/release-loop/checklist.ts +2 -0
- package/src/release-loop/feature-store.ts +68 -18
- package/src/release-loop/gate-shards.ts +158 -0
- package/src/release-loop/light-rc.ts +173 -0
- package/src/release-loop/placement.ts +51 -17
- package/src/release-loop/release-schedule.ts +6 -2
- package/src/release-loop/rubric.ts +57 -1
- package/src/retro/daily.ts +372 -0
- package/src/seat-loop/op-decide.ts +173 -0
- package/src/seat-loop/seat-loop.ts +60 -3
- package/src/seat-loop/seat-report.ts +58 -3
- package/src/self-dev/ask-launch-flow.ts +5 -4
- package/src/self-dev/ask-launch-io.ts +49 -1
- package/src/self-dev/ask-path-candidates.ts +40 -2
- package/src/self-dev/dev-cli.ts +36 -0
- package/src/self-dev/draft-sweep.ts +371 -16
- package/src/self-dev/draft-triage-rules.ts +19 -4
- package/src/self-dev/orchestrate.ts +1 -0
- package/src/self-dev/pr-preview.ts +247 -0
- package/src/self-dev/shard-run.ts +23 -7
- package/src/self-implement/blocked-draft-policy.ts +101 -0
- package/src/self-implement/execution-clarification.ts +11 -0
- package/src/self-implement/gate-cli.ts +29 -8
- package/src/self-implement/gate-env-retry.ts +83 -0
- package/src/self-implement/goal-author-clarification.ts +88 -13
- package/src/self-implement/goal-clarification-escalation.ts +2 -0
- package/src/self-implement/orchestrator.ts +220 -6
- package/src/self-implement/run-stop.ts +123 -0
- package/src/self-implement/run-ttl.ts +258 -0
- package/src/self-implement/salvage-branches.ts +254 -0
- package/src/self-implement/salvage-retention.ts +123 -0
- package/src/self-implement/seams.ts +4 -0
- package/src/self-implement/self-implement-cli.ts +2 -0
- package/src/self-implement/sibling-resync.ts +223 -0
- package/src/self-implement/stop-autoheal.ts +204 -0
- package/src/task-agent/actions.ts +63 -2
- package/src/task-agent/board.ts +132 -0
- package/src/task-agent/card-evidence.ts +292 -0
- package/src/task-agent/completion-evidence.ts +126 -0
- package/src/task-agent/judge.ts +25 -0
- package/src/task-agent/live-moves.ts +276 -0
- package/src/task-agent/mission.ts +33 -7
- package/src/task-agent/shadow.ts +192 -15
- package/src/task-agent/task-hand.ts +124 -17
- package/src/task-orchestrator/surfaces/pod-command-job.ts +82 -8
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +4 -0
- package/src/task-orchestrator/surfaces/pod-install-slots.ts +89 -0
- package/src/task-orchestrator/surfaces/pod-lease.ts +233 -21
- package/src/task-orchestrator/surfaces/pod-pool.ts +155 -34
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +21 -5
- package/src/telegram-away-command.ts +38 -0
- package/src/telegram-commands.ts +52 -0
- package/src/telegram-report-log-probe.ts +21 -0
- package/src/telegram-report.ts +27 -3
- package/src/testing/real-cli-spawn.ts +106 -0
- package/src/tui-client/dashboard-session.ts +1 -0
- package/src/user-config.ts +190 -5
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3857-4f936cdb30322aa6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4554-5761612531d0146b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5977-a11f37254196b659.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8401-8615a660c84cdb03.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +0 -25
- package/apps/pwa/out/_next/static/chunks/9247-269e4464cc4b491c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/a9f2b54d-7865aba0f4d2ee49.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-60c3da708f9a0ccc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-a0eb02638b1e6f31.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-6eaf7bac37830c21.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-c39554c321576982.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-bd5d586429167fea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-59b7834d497c2255.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-c130b63862437814.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-5f23d0c66a3b4793.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-a3c58f5c72a5f3f9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-a2c4625596f8d69c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-0e9099531d239f11.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-e8e1ecaea7cd71d2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-a1b7ffed710a93e0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-2da6f9b8a38c6978.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-f331e38fefc00393.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-44d5f767491692bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-61553851aa606146.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-a1f585e154e61c59.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-5150ef17b5383a57.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b9d8ce70792c1672.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-a5a3de231f2cc7d5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-31ac4afa2dcd915c.js +0 -1
- package/apps/pwa/out/_next/static/css/7fa358fec7da7c47.css +0 -1
- /package/apps/pwa/out/_next/static/{P6YTVgzuZz5oTZhY4Din_ → Bam1YziK2iH9bN4K0utGk}/_buildManifest.js +0 -0
- /package/apps/pwa/out/_next/static/{P6YTVgzuZz5oTZhY4Din_ → Bam1YziK2iH9bN4K0utGk}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
// ── CFG-STORE1 · 읽기 전용 «유효 config ⊕ 출처» · 드리프트 표 (0.2.20 · 2026-10-08) ──
|
|
2
|
+
//
|
|
3
|
+
// RFC-universe-by-house-not-tree-and-config-store-2026-10-06 §2·§3.
|
|
4
|
+
//
|
|
5
|
+
// S: 집(운영 `~/.elanous`)과 파생 시험 우주(`<트리>/.elanous-test` · 10-06 실측 161)가 각자
|
|
6
|
+
// `config.json` 사본을 «물질화»해 갖는다. 운영 키를 바꿔도 사본은 그대로다.
|
|
7
|
+
// C: 10-08 밤 — 운영 `harness.podPool` 에서 node-c 를 뺐는데 자리·작업 트리의 시험 우주 사본 36개가
|
|
8
|
+
// 옛 podPool(node-c 포함)을 들고 있어, 그 트리에서 쏜 발사가 계속 node-c 에 Pod 를 놓았다.
|
|
9
|
+
// Q: 「어느 키가 어느 우주에서 운영과 다른가」를 한 명령으로 볼 수 있나.
|
|
10
|
+
// A: `elanous config drift` · `elanous config explain <key>` — 이 모듈이 그 계산이다.
|
|
11
|
+
//
|
|
12
|
+
// ⛔ 불변식
|
|
13
|
+
// • 읽기만 한다 — 레지스트리 prune(readLogInstances)·XDG 이주(userConfigPath)도 부르지 않는다.
|
|
14
|
+
// • 못 읽은 config 는 «못 읽음»으로 따로 센다 — 절대 「같다」로 접지 않는다.
|
|
15
|
+
// • 비밀 값은 찍지 않는다 — 경로의 한 마디라도 token/secret/key/password 면 값을 가린다.
|
|
16
|
+
// • config.json 이 «없는» 우주는 비교에서 빼되 수를 낸다(그 우주는 코드 기본값으로 돈다).
|
|
17
|
+
|
|
18
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
19
|
+
import { homedir } from 'node:os';
|
|
20
|
+
import { join, resolve } from 'node:path';
|
|
21
|
+
import { redactSecretText } from '../debug/log.js';
|
|
22
|
+
import { resolveInstanceKind, type LogInstanceEntry } from '../mss/logging/instance-registry.js';
|
|
23
|
+
import { buildTestSafeRawConfig } from './config-test-sync.js';
|
|
24
|
+
import { prodInstanceRoot } from '../instance/resolve.js';
|
|
25
|
+
|
|
26
|
+
export type UniverseOrigin = 'registry' | 'tree-scan';
|
|
27
|
+
export type UniverseStatus = 'ok' | 'no-config' | 'unreadable' | 'gone';
|
|
28
|
+
|
|
29
|
+
export interface UniverseConfig {
|
|
30
|
+
configDir: string;
|
|
31
|
+
configPath: string;
|
|
32
|
+
origins: UniverseOrigin[];
|
|
33
|
+
status: UniverseStatus;
|
|
34
|
+
/** status=unreadable 일 때 사유. */
|
|
35
|
+
error?: string;
|
|
36
|
+
/** status=ok 일 때 파일 원문(raw) — 겹 «파일» 층. */
|
|
37
|
+
raw?: Record<string, unknown>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface DiscoverOptions {
|
|
41
|
+
prodRoot?: string;
|
|
42
|
+
/** 인스턴스 레지스트리(`~/.elanous/logs/instances.json`) — 읽기만. null 이면 안 읽는다. */
|
|
43
|
+
registryPath?: string | null;
|
|
44
|
+
/** 명시 트리 뿌리 — 각 자식 디렉토리의 `.elanous-test` 를 본다. */
|
|
45
|
+
treeScanRoots?: string[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 비밀 «이름» — 대소문자 무시 낱말 ⊕ 「auth」「pass」는 낱말 경계로만(authorOnPod·bypass 는 아니다).
|
|
49
|
+
const SECRET_NAME = /token|secret|key|password|passphrase|credential|authorization|oauth|bearer|cookie|session|dsn/i;
|
|
50
|
+
const SECRET_NAME_CAMEL = /(?:(?:^|[^a-zA-Z])(?:auth|AUTH|pass|PASS)|(?:Auth|Pass))(?![a-z])/;
|
|
51
|
+
// 비밀 «값» — 이름이 무해해도(notify.url) 값 모양으로 가린다.
|
|
52
|
+
const SECRET_VALUE_PATTERNS: readonly RegExp[] = [
|
|
53
|
+
/[a-z][a-z0-9+.-]*:\/\/[^/\s@]+@/i, // URL userinfo (https://user:pass@host)
|
|
54
|
+
/discord(?:app)?\.com\/api\/webhooks|hooks\.slack\.com/i, // Discord·Slack 웹훅
|
|
55
|
+
/^\s*(?:ghp_|gho_|ghs_|github_pat_|sk-|xoxb-|xoxp-|Bearer\s)/, // 토큰 접두
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
/** 키 한 마디가 비밀 이름인가. */
|
|
59
|
+
export function isSecretName(segment: string): boolean {
|
|
60
|
+
return SECRET_NAME.test(segment) || SECRET_NAME_CAMEL.test(segment);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** 문자열 값이 비밀 모양인가(이름과 무관). */
|
|
64
|
+
export function isSecretValue(value: unknown): boolean {
|
|
65
|
+
return typeof value === 'string' && SECRET_VALUE_PATTERNS.some((re) => re.test(value));
|
|
66
|
+
}
|
|
67
|
+
export const MASK = '<masked>';
|
|
68
|
+
|
|
69
|
+
export function defaultProdRoot(): string {
|
|
70
|
+
return prodInstanceRoot(); // 집 = 운영 뿌리(우주 리졸버 경유 · 격리 게이트)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function defaultRegistryPath(prodRoot: string = defaultProdRoot()): string {
|
|
74
|
+
return join(prodRoot, 'logs', 'instances.json');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** 레지스트리 밖에서 시험 우주가 사는 «알려진» 트리 뿌리(10-08 사건의 세 곳). 명시 목록 — 홈 전체를 훑지 않는다. */
|
|
78
|
+
export function defaultTreeScanRoots(home: string = homedir()): string[] {
|
|
79
|
+
return [join(home, 'elanous-hq', 'seats'), join(home, 'elanous-hq', 'work'), join(home, 'source', 'pilot')];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** 읽기 실패 사유 — ⛔ 파서 메시지는 파일 «내용» 조각을 실을 수 있어 그대로 내지 않는다(비밀 노출). 종류·errno 코드만. */
|
|
83
|
+
export function safeReadError(err: unknown): string {
|
|
84
|
+
if (err instanceof SyntaxError) return 'JSON 파싱 실패';
|
|
85
|
+
const code = (err as { code?: unknown } | null)?.code;
|
|
86
|
+
if (typeof code === 'string' && /^[A-Z0-9_]+$/.test(code)) return `읽기 실패(${code})`;
|
|
87
|
+
return '읽기 실패';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function norm(p: string): string {
|
|
91
|
+
return resolve(p.trim().replace(/\/+$/, ''));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** 레지스트리를 «읽기만» 한다(prune 쓰기 없음). 실패는 {ok:false}. */
|
|
95
|
+
export function readRegistryReadOnly(path: string): { ok: boolean; entries: LogInstanceEntry[]; error?: string } {
|
|
96
|
+
try {
|
|
97
|
+
const parsed = JSON.parse(readFileSync(path, 'utf-8')) as { instances?: unknown };
|
|
98
|
+
if (!Array.isArray(parsed.instances)) return { ok: false, entries: [], error: 'instances 배열 없음' };
|
|
99
|
+
const entries = parsed.instances.filter((e): e is LogInstanceEntry => !!e && typeof e === 'object'
|
|
100
|
+
&& typeof (e as LogInstanceEntry).stateDir === 'string');
|
|
101
|
+
return { ok: true, entries };
|
|
102
|
+
} catch (err) {
|
|
103
|
+
return { ok: false, entries: [], error: safeReadError(err) };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function readUniverseConfig(configDir: string, origins: UniverseOrigin[]): UniverseConfig {
|
|
108
|
+
const configPath = join(configDir, 'config.json');
|
|
109
|
+
if (!existsSync(configDir)) return { configDir, configPath, origins, status: 'gone' };
|
|
110
|
+
if (!existsSync(configPath)) return { configDir, configPath, origins, status: 'no-config' };
|
|
111
|
+
try {
|
|
112
|
+
const parsed = JSON.parse(readFileSync(configPath, 'utf-8')) as unknown;
|
|
113
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
114
|
+
return { configDir, configPath, origins, status: 'unreadable', error: '최상위가 객체가 아님' };
|
|
115
|
+
}
|
|
116
|
+
return { configDir, configPath, origins, status: 'ok', raw: parsed as Record<string, unknown> };
|
|
117
|
+
} catch (err) {
|
|
118
|
+
return { configDir, configPath, origins, status: 'unreadable', error: safeReadError(err) };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface DiscoveryResult {
|
|
123
|
+
prod: UniverseConfig;
|
|
124
|
+
derived: UniverseConfig[];
|
|
125
|
+
registry: { path: string | null; ok: boolean; entries: number; testEntries: number; error?: string };
|
|
126
|
+
treeScanRoots: string[];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** 집 ⊕ 파생 시험 우주를 모은다 — 레지스트리(test 항목) ∪ 명시 트리 뿌리. configDir 로 중복 제거. */
|
|
130
|
+
export function discoverUniverses(opts: DiscoverOptions = {}): DiscoveryResult {
|
|
131
|
+
const prodRoot = norm(opts.prodRoot ?? defaultProdRoot());
|
|
132
|
+
const registryPath = opts.registryPath === undefined ? defaultRegistryPath(prodRoot) : opts.registryPath;
|
|
133
|
+
const treeScanRoots = (opts.treeScanRoots ?? defaultTreeScanRoots()).map(norm);
|
|
134
|
+
const found = new Map<string, Set<UniverseOrigin>>();
|
|
135
|
+
const add = (dir: string, origin: UniverseOrigin) => {
|
|
136
|
+
const d = norm(dir);
|
|
137
|
+
if (d === prodRoot) return;
|
|
138
|
+
const set = found.get(d) ?? new Set<UniverseOrigin>();
|
|
139
|
+
set.add(origin);
|
|
140
|
+
found.set(d, set);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
let registry: DiscoveryResult['registry'] = { path: registryPath, ok: false, entries: 0, testEntries: 0 };
|
|
144
|
+
if (registryPath) {
|
|
145
|
+
const r = readRegistryReadOnly(registryPath);
|
|
146
|
+
let testEntries = 0;
|
|
147
|
+
for (const e of r.entries) {
|
|
148
|
+
if (resolveInstanceKind(e) !== 'test') continue;
|
|
149
|
+
testEntries++;
|
|
150
|
+
add(e.configDir ?? e.stateDir, 'registry');
|
|
151
|
+
}
|
|
152
|
+
registry = { path: registryPath, ok: r.ok, entries: r.entries.length, testEntries, ...(r.error ? { error: r.error } : {}) };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
for (const root of treeScanRoots) {
|
|
156
|
+
let children: string[] = [];
|
|
157
|
+
try { children = readdirSync(root); } catch { continue; }
|
|
158
|
+
for (const child of children) {
|
|
159
|
+
const tree = join(root, child);
|
|
160
|
+
try { if (!statSync(tree).isDirectory()) continue; } catch { continue; }
|
|
161
|
+
const candidate = join(tree, '.elanous-test');
|
|
162
|
+
if (existsSync(candidate)) add(candidate, 'tree-scan');
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const derived = [...found.entries()]
|
|
167
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
168
|
+
.map(([dir, origins]) => readUniverseConfig(dir, [...origins].sort()));
|
|
169
|
+
return { prod: readUniverseConfig(prodRoot, []), derived, registry, treeScanRoots };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ── 값 평탄화 · 가림 ──
|
|
173
|
+
|
|
174
|
+
export function isSecretPath(key: string): boolean {
|
|
175
|
+
return key.split('.').some(isSecretName);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** 객체는 내려가고, 배열·원시값·빈 객체는 잎으로 둔다. */
|
|
179
|
+
export function flattenConfig(raw: Record<string, unknown>, prefix = '', out = new Map<string, unknown>()): Map<string, unknown> {
|
|
180
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
181
|
+
const key = prefix ? `${prefix}.${k}` : k;
|
|
182
|
+
if (v && typeof v === 'object' && !Array.isArray(v) && Object.keys(v).length > 0) {
|
|
183
|
+
flattenConfig(v as Record<string, unknown>, key, out);
|
|
184
|
+
} else {
|
|
185
|
+
out.set(key, v);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return out;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function stableStringify(v: unknown): string {
|
|
192
|
+
if (Array.isArray(v)) return `[${v.map(stableStringify).join(',')}]`;
|
|
193
|
+
if (v && typeof v === 'object') {
|
|
194
|
+
return `{${Object.keys(v).sort().map((k) => `${JSON.stringify(k)}:${stableStringify((v as Record<string, unknown>)[k])}`).join(',')}}`;
|
|
195
|
+
}
|
|
196
|
+
return JSON.stringify(v) ?? 'undefined';
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** 잎 안의 구조(배열 속 객체 등)도 내려가 비밀 이름 키의 값을 가린다 — `telegram.channels[].botToken` 같은 자리. */
|
|
200
|
+
export function maskDeep(value: unknown): unknown {
|
|
201
|
+
if (isSecretValue(value)) return MASK;
|
|
202
|
+
if (Array.isArray(value)) return value.map(maskDeep);
|
|
203
|
+
if (value && typeof value === 'object') {
|
|
204
|
+
return Object.fromEntries(Object.entries(value as Record<string, unknown>)
|
|
205
|
+
.map(([k, v]) => [k, isSecretName(k) ? MASK : maskDeep(v)]));
|
|
206
|
+
}
|
|
207
|
+
return value;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** 표시용 값 — 비밀 경로면 가린다, 아니면 구조 안 비밀 키 가림 ⊕ 텍스트 축 가림. */
|
|
211
|
+
export function displayValue(key: string, value: unknown, present = true): string {
|
|
212
|
+
if (!present) return '(없음)';
|
|
213
|
+
if (isSecretPath(key) || isSecretValue(value)) return MASK;
|
|
214
|
+
const s = typeof value === 'string' ? JSON.stringify(value) : stableStringify(maskDeep(value));
|
|
215
|
+
const red = redactSecretText(s);
|
|
216
|
+
return red.length > 160 ? `${red.slice(0, 157)}...` : red;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function matchesKey(key: string, filter?: string): boolean {
|
|
220
|
+
if (!filter) return true;
|
|
221
|
+
return key === filter || key.startsWith(`${filter}.`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ── 드리프트 ──
|
|
225
|
+
|
|
226
|
+
export interface DriftValueGroup {
|
|
227
|
+
/** 표시값(가림 적용). 없으면 '(없음)'. */
|
|
228
|
+
value: string;
|
|
229
|
+
present: boolean;
|
|
230
|
+
count: number;
|
|
231
|
+
examples: string[];
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export interface DriftRow {
|
|
235
|
+
key: string;
|
|
236
|
+
prodPresent: boolean;
|
|
237
|
+
prodValue: string;
|
|
238
|
+
/** 운영과 다른 파생 우주 수(비교 대상 중). */
|
|
239
|
+
differing: number;
|
|
240
|
+
/** 운영엔 있는데 파생엔 없음. */
|
|
241
|
+
missingInDerived: number;
|
|
242
|
+
/** 운영엔 없는데 파생엔 있음. */
|
|
243
|
+
extraInDerived: number;
|
|
244
|
+
/** 둘 다 있는데 값이 다름. */
|
|
245
|
+
valueDiffers: number;
|
|
246
|
+
groups: DriftValueGroup[];
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export interface DriftReport {
|
|
250
|
+
prodConfigPath: string;
|
|
251
|
+
prodStatus: UniverseStatus;
|
|
252
|
+
prodError?: string;
|
|
253
|
+
keyFilter?: string;
|
|
254
|
+
/** false(기본)면 «의도된 차이»를 드리프트로 안 센다 — `config sync-test` 의 test-safe 변환 결과와 같은 값 ⊕ `_test*` 메타 키. */
|
|
255
|
+
includeExpected: boolean;
|
|
256
|
+
universes: { total: number; compared: number; noConfig: number; unreadable: number; gone: number };
|
|
257
|
+
registry: DiscoveryResult['registry'];
|
|
258
|
+
treeScanRoots: string[];
|
|
259
|
+
unreadable: Array<{ configPath: string; error: string }>;
|
|
260
|
+
rows: DriftRow[];
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const EXAMPLES = 3;
|
|
264
|
+
|
|
265
|
+
/** 사본 메타 키(`_testSyncedAt`·`_testSyncedFrom`·`_testSecretsStripped`) — 우주마다 다른 게 정상이다. */
|
|
266
|
+
export function isSyncMetaKey(key: string): boolean {
|
|
267
|
+
return key.startsWith('_test');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function computeDrift(d: DiscoveryResult, opts: { key?: string; includeExpected?: boolean } = {}): DriftReport {
|
|
271
|
+
const includeExpected = opts.includeExpected === true;
|
|
272
|
+
const compared = d.derived.filter((u) => u.status === 'ok');
|
|
273
|
+
const unreadable = d.derived.filter((u) => u.status === 'unreadable');
|
|
274
|
+
const report: DriftReport = {
|
|
275
|
+
prodConfigPath: d.prod.configPath,
|
|
276
|
+
prodStatus: d.prod.status,
|
|
277
|
+
...(d.prod.error ? { prodError: d.prod.error } : {}),
|
|
278
|
+
...(opts.key ? { keyFilter: opts.key } : {}),
|
|
279
|
+
includeExpected,
|
|
280
|
+
universes: {
|
|
281
|
+
total: d.derived.length,
|
|
282
|
+
compared: compared.length,
|
|
283
|
+
noConfig: d.derived.filter((u) => u.status === 'no-config').length,
|
|
284
|
+
unreadable: unreadable.length,
|
|
285
|
+
gone: d.derived.filter((u) => u.status === 'gone').length,
|
|
286
|
+
},
|
|
287
|
+
registry: d.registry,
|
|
288
|
+
treeScanRoots: d.treeScanRoots,
|
|
289
|
+
unreadable: unreadable.map((u) => ({ configPath: u.configPath, error: u.error ?? '알 수 없음' })),
|
|
290
|
+
rows: [],
|
|
291
|
+
};
|
|
292
|
+
// ⛔ 운영을 못 읽으면 비교 자체를 하지 않는다 — 「드리프트 0」으로 보이면 안 된다.
|
|
293
|
+
if (d.prod.status !== 'ok' || !d.prod.raw) return report;
|
|
294
|
+
|
|
295
|
+
const prodFlat = flattenConfig(d.prod.raw);
|
|
296
|
+
// 「의도된 차이」의 자 — 오늘 운영 config 로 sync-test 를 돌리면 사본에 박힐 값.
|
|
297
|
+
const safeFlat = includeExpected ? null : flattenConfig(buildTestSafeRawConfig(d.prod.raw));
|
|
298
|
+
const derivedFlat = compared.map((u) => ({ u, flat: flattenConfig(u.raw!) }));
|
|
299
|
+
const keys = new Set<string>([...prodFlat.keys()].filter((k) => matchesKey(k, opts.key)));
|
|
300
|
+
for (const { flat } of derivedFlat) for (const k of flat.keys()) if (matchesKey(k, opts.key)) keys.add(k);
|
|
301
|
+
|
|
302
|
+
for (const key of [...keys].sort()) {
|
|
303
|
+
if (!includeExpected && isSyncMetaKey(key)) continue;
|
|
304
|
+
const prodPresent = prodFlat.has(key);
|
|
305
|
+
const prodSig = prodPresent ? stableStringify(prodFlat.get(key)) : undefined;
|
|
306
|
+
const safePresent = safeFlat?.has(key) ?? false;
|
|
307
|
+
const safeSig = safePresent ? stableStringify(safeFlat!.get(key)) : undefined;
|
|
308
|
+
const groups = new Map<string, DriftValueGroup>();
|
|
309
|
+
let missingInDerived = 0, extraInDerived = 0, valueDiffers = 0;
|
|
310
|
+
for (const { u, flat } of derivedFlat) {
|
|
311
|
+
const present = flat.has(key);
|
|
312
|
+
const sig = present ? stableStringify(flat.get(key)) : undefined;
|
|
313
|
+
if (present === prodPresent && sig === prodSig) continue;
|
|
314
|
+
if (safeFlat && present === safePresent && sig === safeSig) continue; // test-safe 변환 그대로 — 의도된 차이
|
|
315
|
+
if (!present) missingInDerived++;
|
|
316
|
+
else if (!prodPresent) extraInDerived++;
|
|
317
|
+
else valueDiffers++;
|
|
318
|
+
const groupKey = present ? `v:${sig}` : 'absent';
|
|
319
|
+
const g = groups.get(groupKey) ?? {
|
|
320
|
+
// 비밀 경로는 값 그룹마다 번호만 — 서로 다른지는 보이되 값은 안 보인다.
|
|
321
|
+
value: present ? (isSecretPath(key) ? `${MASK}#${groups.size + 1}` : displayValue(key, flat.get(key))) : '(없음)',
|
|
322
|
+
present, count: 0, examples: [],
|
|
323
|
+
};
|
|
324
|
+
g.count++;
|
|
325
|
+
if (g.examples.length < EXAMPLES) g.examples.push(u.configPath);
|
|
326
|
+
groups.set(groupKey, g);
|
|
327
|
+
}
|
|
328
|
+
const differing = missingInDerived + extraInDerived + valueDiffers;
|
|
329
|
+
if (differing === 0) continue;
|
|
330
|
+
report.rows.push({
|
|
331
|
+
key, prodPresent,
|
|
332
|
+
prodValue: displayValue(key, prodFlat.get(key), prodPresent),
|
|
333
|
+
differing, missingInDerived, extraInDerived, valueDiffers,
|
|
334
|
+
groups: [...groups.values()].sort((a, b) => b.count - a.count),
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
report.rows.sort((a, b) => b.differing - a.differing || a.key.localeCompare(b.key));
|
|
338
|
+
return report;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function walk(root: unknown, key: string): { present: boolean; value?: unknown } {
|
|
342
|
+
let cur: unknown = root;
|
|
343
|
+
for (const part of key.split('.').filter(Boolean)) {
|
|
344
|
+
if (cur === null || typeof cur !== 'object' || !(part in (cur as Record<string, unknown>))) return { present: false };
|
|
345
|
+
cur = (cur as Record<string, unknown>)[part];
|
|
346
|
+
}
|
|
347
|
+
return { present: true, value: cur };
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function shortHome(p: string): string {
|
|
351
|
+
const h = homedir();
|
|
352
|
+
return p.startsWith(`${h}/`) ? `~${p.slice(h.length)}` : p;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function universeSummaryLine(r: DriftReport): string {
|
|
356
|
+
const u = r.universes;
|
|
357
|
+
return `파생 우주 ${u.total} (비교 ${u.compared} · config 없음 ${u.noConfig} · 못 읽음 ${u.unreadable} · 사라짐 ${u.gone})`
|
|
358
|
+
+ ` · 레지스트리 ${r.registry.path === null ? '끔' : r.registry.ok ? `test ${r.registry.testEntries}/${r.registry.entries}` : `못 읽음${r.registry.error ? `(${r.registry.error})` : ''}`}`
|
|
359
|
+
+ ` · 트리 뿌리 ${r.treeScanRoots.length}`;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export function renderDrift(r: DriftReport, opts: { limit?: number } = {}): string {
|
|
363
|
+
const lines: string[] = [];
|
|
364
|
+
lines.push(`config drift — 집 ${shortHome(r.prodConfigPath)}${r.keyFilter ? ` · 키 ${r.keyFilter}` : ''}`
|
|
365
|
+
+ (r.includeExpected ? ' · 의도된 차이 포함' : ' · 의도된 차이(test-safe 변환·_test 메타) 제외 — --include-expected'));
|
|
366
|
+
lines.push(` ${universeSummaryLine(r)}`);
|
|
367
|
+
if (r.prodStatus !== 'ok') {
|
|
368
|
+
lines.push(` ⛔ 운영 config 못 읽음(${r.prodStatus}${r.prodError ? `: ${r.prodError}` : ''}) — 비교하지 않았다(「드리프트 0」이 아니다)`);
|
|
369
|
+
}
|
|
370
|
+
if (r.universes.compared === 0 && r.prodStatus === 'ok') lines.push(' ⚠️ 비교한 파생 우주 0 — 「드리프트 0」이 아니라 «잴 대상이 없었다»');
|
|
371
|
+
const limit = opts.limit ?? 50;
|
|
372
|
+
const shown = limit > 0 ? r.rows.slice(0, limit) : r.rows;
|
|
373
|
+
lines.push(` 드리프트 키 ${r.rows.length}`);
|
|
374
|
+
for (const row of shown) {
|
|
375
|
+
const parts: string[] = [];
|
|
376
|
+
if (row.missingInDerived) parts.push(`파생에 없음 ${row.missingInDerived}`);
|
|
377
|
+
if (row.extraInDerived) parts.push(`운영에 없음 ${row.extraInDerived}`);
|
|
378
|
+
if (row.valueDiffers) parts.push(`값 다름 ${row.valueDiffers}`);
|
|
379
|
+
lines.push(`\n ${row.key} ${row.differing}/${r.universes.compared} 우주 다름 (${parts.join(' · ')})`);
|
|
380
|
+
lines.push(` 운영: ${row.prodValue}`);
|
|
381
|
+
for (const g of row.groups.slice(0, 4)) {
|
|
382
|
+
lines.push(` ${g.value} × ${g.count} 예: ${g.examples.map(shortHome).join(', ')}`);
|
|
383
|
+
}
|
|
384
|
+
if (row.groups.length > 4) lines.push(` … 값 묶음 ${row.groups.length - 4}개 더 (--json)`);
|
|
385
|
+
}
|
|
386
|
+
if (shown.length < r.rows.length) lines.push(`\n … ${r.rows.length - shown.length}개 키 더 — --limit 0 또는 --json (⚠️ 잘린 표다)`);
|
|
387
|
+
if (r.unreadable.length) {
|
|
388
|
+
lines.push('\n 못 읽음 (같다고 보지 않음):');
|
|
389
|
+
for (const u of r.unreadable.slice(0, 20)) lines.push(` ${shortHome(u.configPath)} — ${u.error}`);
|
|
390
|
+
if (r.unreadable.length > 20) lines.push(` … ${r.unreadable.length - 20}개 더 (--json)`);
|
|
391
|
+
}
|
|
392
|
+
return lines.join('\n');
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// ── explain ──
|
|
396
|
+
|
|
397
|
+
export type ProdSourceLayer = 'prod-file' | 'llm-fallback' | 'code-default' | 'unset' | 'prod-unreadable';
|
|
398
|
+
|
|
399
|
+
export interface ExplainReport {
|
|
400
|
+
key: string;
|
|
401
|
+
prod: { configPath: string; layer: ProdSourceLayer; value: string; note?: string };
|
|
402
|
+
codeDefault: { present: boolean; value: string };
|
|
403
|
+
/** false 면 운영을 못 읽어 파생과 «비교하지 않았다» — groups 는 비고 「다름」으로 세지 않는다. */
|
|
404
|
+
comparable?: boolean;
|
|
405
|
+
universes: DriftReport['universes'];
|
|
406
|
+
registry: DiscoveryResult['registry'];
|
|
407
|
+
treeScanRoots: string[];
|
|
408
|
+
/** 파생 우주 값 묶음 — 운영과 같은 것도 포함(sameAsProd). */
|
|
409
|
+
groups: Array<DriftValueGroup & { sameAsProd: boolean }>;
|
|
410
|
+
unreadable: Array<{ configPath: string; error: string }>;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
export interface ExplainDeps {
|
|
414
|
+
/** 코드 기본값 객체(파일 없이 만든 유효 config). ⛔ 필수 — 없으면 「기본값 없음」과 「모름」이 섞인다. */
|
|
415
|
+
codeDefaults: () => unknown;
|
|
416
|
+
/** `<prodRoot>/llm-fallback.json` 원문. */
|
|
417
|
+
llmFallbackPath?: string;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/** `buildUserConfig` 의 llm-fallback 적용 조건과 같은 판정 — provider 가 없거나 auto/none 이면 «빈» llm 절. */
|
|
421
|
+
export function isLlmSectionEmptyRaw(llm: unknown): boolean {
|
|
422
|
+
if (!llm || typeof llm !== 'object' || Array.isArray(llm)) return true;
|
|
423
|
+
const provider = (llm as Record<string, unknown>).provider;
|
|
424
|
+
return typeof provider !== 'string' || provider.trim() === '' || provider === 'auto' || provider === 'none';
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
export function computeExplain(d: DiscoveryResult, key: string, deps: ExplainDeps): ExplainReport {
|
|
428
|
+
const defaults = walk(deps.codeDefaults(), key);
|
|
429
|
+
const codeDefault = { present: defaults.present, value: displayValue(key, defaults.value, defaults.present) };
|
|
430
|
+
|
|
431
|
+
let prod: ExplainReport['prod'];
|
|
432
|
+
if (d.prod.status !== 'ok' || !d.prod.raw) {
|
|
433
|
+
prod = { configPath: d.prod.configPath, layer: 'prod-unreadable', value: '(못 읽음)', note: d.prod.error ?? d.prod.status };
|
|
434
|
+
} else {
|
|
435
|
+
const fromFile = walk(d.prod.raw, key);
|
|
436
|
+
if (fromFile.present) {
|
|
437
|
+
prod = { configPath: d.prod.configPath, layer: 'prod-file', value: displayValue(key, fromFile.value) };
|
|
438
|
+
} else {
|
|
439
|
+
let fb: { present: boolean; value?: unknown } = { present: false };
|
|
440
|
+
const fbPath = deps.llmFallbackPath ?? join(d.prod.configDir, 'llm-fallback.json');
|
|
441
|
+
// 폴백 파일은 운영 llm 절이 «비었을 때만» 합쳐진다 — 그 조건이 아닐 땐 출처 층이 아니다.
|
|
442
|
+
if (key.startsWith('llm.') && isLlmSectionEmptyRaw(d.prod.raw.llm) && existsSync(fbPath)) {
|
|
443
|
+
try { fb = walk(JSON.parse(readFileSync(fbPath, 'utf-8')), key.slice(4)); } catch { /* 못 읽으면 폴백 층 아님 */ }
|
|
444
|
+
}
|
|
445
|
+
if (fb.present) {
|
|
446
|
+
prod = { configPath: fbPath, layer: 'llm-fallback', value: displayValue(key, fb.value), note: '운영 llm 절이 비어 폴백 파일이 쓰인다' };
|
|
447
|
+
} else if (defaults.present) {
|
|
448
|
+
prod = { configPath: d.prod.configPath, layer: 'code-default', value: codeDefault.value, note: '파일에 없음 — 코드 기본값' };
|
|
449
|
+
} else {
|
|
450
|
+
prod = { configPath: d.prod.configPath, layer: 'unset', value: '(없음)' };
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const prodRaw = d.prod.raw ? walk(d.prod.raw, key) : { present: false };
|
|
456
|
+
const prodSig = prodRaw.present ? stableStringify(prodRaw.value) : undefined;
|
|
457
|
+
const groups = new Map<string, DriftValueGroup & { sameAsProd: boolean }>();
|
|
458
|
+
const compared = d.derived.filter((u) => u.status === 'ok');
|
|
459
|
+
const comparable = d.prod.status === 'ok' && !!d.prod.raw;
|
|
460
|
+
for (const u of comparable ? compared : []) {
|
|
461
|
+
const w = walk(u.raw, key);
|
|
462
|
+
const sig = w.present ? stableStringify(w.value) : undefined;
|
|
463
|
+
const gk = w.present ? `v:${sig}` : 'absent';
|
|
464
|
+
const sameAsProd = w.present === prodRaw.present && sig === prodSig;
|
|
465
|
+
const g = groups.get(gk) ?? {
|
|
466
|
+
value: w.present
|
|
467
|
+
? (isSecretPath(key) ? `${MASK}#${groups.size + 1}` : displayValue(key, w.value))
|
|
468
|
+
: codeDefault.present ? `(없음 → 코드 기본값 ${codeDefault.value})` : '(없음 · 코드 기본값도 없음)',
|
|
469
|
+
present: w.present, count: 0, examples: [], sameAsProd,
|
|
470
|
+
};
|
|
471
|
+
g.count++;
|
|
472
|
+
if (g.examples.length < EXAMPLES) g.examples.push(u.configPath);
|
|
473
|
+
groups.set(gk, g);
|
|
474
|
+
}
|
|
475
|
+
const unreadable = d.derived.filter((u) => u.status === 'unreadable');
|
|
476
|
+
return {
|
|
477
|
+
key, prod, codeDefault, comparable,
|
|
478
|
+
universes: {
|
|
479
|
+
total: d.derived.length,
|
|
480
|
+
compared: compared.length,
|
|
481
|
+
noConfig: d.derived.filter((u) => u.status === 'no-config').length,
|
|
482
|
+
unreadable: unreadable.length,
|
|
483
|
+
gone: d.derived.filter((u) => u.status === 'gone').length,
|
|
484
|
+
},
|
|
485
|
+
registry: d.registry,
|
|
486
|
+
treeScanRoots: d.treeScanRoots,
|
|
487
|
+
groups: [...groups.values()].sort((a, b) => Number(a.sameAsProd) - Number(b.sameAsProd) || b.count - a.count),
|
|
488
|
+
unreadable: unreadable.map((u) => ({ configPath: u.configPath, error: u.error ?? '알 수 없음' })),
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
export function renderExplain(r: ExplainReport): string {
|
|
493
|
+
const lines: string[] = [];
|
|
494
|
+
lines.push(`config explain ${r.key}`);
|
|
495
|
+
lines.push(` 운영 값: ${r.prod.value}`);
|
|
496
|
+
lines.push(` 출처 층: ${r.prod.layer} (${shortHome(r.prod.configPath)})${r.prod.note ? ` — ${r.prod.note}` : ''}`);
|
|
497
|
+
lines.push(` 코드 기본값: ${r.codeDefault.value}`);
|
|
498
|
+
lines.push(` ${universeSummaryLine({ universes: r.universes, registry: r.registry, treeScanRoots: r.treeScanRoots } as DriftReport)}`);
|
|
499
|
+
if (r.comparable === false) {
|
|
500
|
+
lines.push(' ⛔ 운영 config 를 못 읽어 파생 우주와 비교하지 않았다(「다름」도 「같음」도 아니다)');
|
|
501
|
+
} else {
|
|
502
|
+
const differing = r.groups.filter((g) => !g.sameAsProd).reduce((s, g) => s + g.count, 0);
|
|
503
|
+
lines.push(` 운영과 다른 우주 ${differing}/${r.universes.compared}`);
|
|
504
|
+
}
|
|
505
|
+
for (const g of r.groups) {
|
|
506
|
+
lines.push(` ${g.sameAsProd ? '= 운영과 같음' : '≠'} ${g.value} × ${g.count} 예: ${g.examples.map(shortHome).join(', ')}`);
|
|
507
|
+
}
|
|
508
|
+
if (r.unreadable.length) {
|
|
509
|
+
lines.push(' 못 읽음 (같다고 보지 않음):');
|
|
510
|
+
for (const u of r.unreadable.slice(0, 20)) lines.push(` ${shortHome(u.configPath)} — ${u.error}`);
|
|
511
|
+
}
|
|
512
|
+
return lines.join('\n');
|
|
513
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import type { Database } from 'bun:sqlite';
|
|
3
|
+
import type { GuardianPlan } from '../domains/guardian-tick.js';
|
|
4
|
+
import { getUserConfig } from '../user-config.js';
|
|
5
|
+
import { writeStdoutJson } from './stdout-json.js';
|
|
6
|
+
|
|
7
|
+
// Domain modules (schedules.db · schedule-runner → node-cron) load lazily inside the actions,
|
|
8
|
+
// so registering `guardian` adds no static dependency to the CLI boot path.
|
|
9
|
+
async function guardianPlan(): Promise<{ db: Database; plan: GuardianPlan }> {
|
|
10
|
+
const { listSchedules, openSchedulesDb } = await import('../domains/schedule-registry.js');
|
|
11
|
+
const db = openSchedulesDb();
|
|
12
|
+
try {
|
|
13
|
+
return { db, plan: { jobs: listSchedules(db) } };
|
|
14
|
+
} catch (error) {
|
|
15
|
+
db.close();
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function registerGuardianCommands(program: Command): void {
|
|
21
|
+
const guardian = program.command('guardian').description('크론 수호자 그림자 관측(기존 크론·알림 무변경)');
|
|
22
|
+
guardian.command('tick').description('현재 시각에 발화했을 잡을 관측(잡은 실행하지 않음)')
|
|
23
|
+
.action(async () => {
|
|
24
|
+
const { guardianTick } = await import('../domains/guardian-tick.js');
|
|
25
|
+
const { db, plan } = await guardianPlan();
|
|
26
|
+
try {
|
|
27
|
+
const cfg = getUserConfig();
|
|
28
|
+
// Live activation (and crontab retirement) is a separate operational change.
|
|
29
|
+
if (cfg.guardian?.mode === 'live') throw new Error('guardian live activation is outside GUARD-ONE-a');
|
|
30
|
+
const result = await guardianTick(new Date(), plan, { config: cfg });
|
|
31
|
+
await writeStdoutJson(JSON.stringify(result, null, 2) + '\n');
|
|
32
|
+
} finally { db.close(); }
|
|
33
|
+
});
|
|
34
|
+
guardian.command('compare').description('하루의 예정 발화와 기록된 실제 발화 비교')
|
|
35
|
+
.requiredOption('--day <YYYY-MM-DD>', '비교할 달력 날짜')
|
|
36
|
+
.option('--json', 'JSON 출력')
|
|
37
|
+
.action(async (opts: { day: string; json?: boolean }) => {
|
|
38
|
+
const { guardianShadowDay } = await import('../domains/guardian-tick.js');
|
|
39
|
+
const { db, plan } = await guardianPlan();
|
|
40
|
+
try {
|
|
41
|
+
const result = guardianShadowDay(opts.day, { db, plan });
|
|
42
|
+
if (opts.json) await writeStdoutJson(JSON.stringify(result, null, 2) + '\n');
|
|
43
|
+
else {
|
|
44
|
+
console.log(`guardian ${result.day}: ${result.same ? 'same' : 'diff'} (현재 레지스트리 cron·활성 기준 재계산 · alertsActual=null: 발송 수 미측정)`);
|
|
45
|
+
for (const row of result.jobs) console.log(` ${row.job}: would=${row.would} actual=${row.actual} alertsActual=${row.alertsActual ?? '미측정'}`);
|
|
46
|
+
}
|
|
47
|
+
} finally { db.close(); }
|
|
48
|
+
});
|
|
49
|
+
}
|
package/src/cli/intake-cli.ts
CHANGED
|
@@ -46,7 +46,8 @@ export async function runIntakeCandidatesCli(
|
|
|
46
46
|
const limit = opts.limit === undefined ? undefined : parseCandidatesLimit(opts.limit);
|
|
47
47
|
const directives = deps.directives ?? (async () => {
|
|
48
48
|
const [{ Database }, { existsSync }, { directiveDbPath }] = await Promise.all([
|
|
49
|
-
|
|
49
|
+
// The directive index is excluded from the public export; load it at run time only (proact-meter · know-find pattern).
|
|
50
|
+
import('bun:sqlite'), import('node:fs'), import(['..', 'directives', 'directive-index.js'].join('/')) as Promise<{ directiveDbPath: () => string }>,
|
|
50
51
|
]);
|
|
51
52
|
const path = directiveDbPath();
|
|
52
53
|
if (!existsSync(path)) return [];
|
|
@@ -144,14 +145,15 @@ export async function runIntakeToTasksCli(
|
|
|
144
145
|
|
|
145
146
|
/** `--telegram` 다이제스트. 렌즈는 digest 를 만들기 전에 돈다. 렌즈 실패는 보고를 막지 않는다. */
|
|
146
147
|
export async function runIntakeDigestCli(
|
|
147
|
-
opts: { day?: string; json?: boolean; telegram?: boolean; vault?: string; note?: string },
|
|
148
|
+
opts: { day?: string; json?: boolean; telegram?: boolean; vault?: string; note?: string; seat?: string },
|
|
148
149
|
deps: IntakeDigestCliDeps = {},
|
|
149
150
|
): Promise<{ sent?: boolean; empty?: boolean }> {
|
|
150
|
-
const { buildIntakeDigest, renderDigestMarkdown, renderDigestTelegram } = await import('../intake-plane/digest.js');
|
|
151
|
+
const { buildIntakeDigest, recordSeatBriefed, renderDigestMarkdown, renderDigestTelegram } = await import('../intake-plane/digest.js');
|
|
151
152
|
const { effectiveInstanceRoot } = await import('../instance/resolve.js');
|
|
152
153
|
const root = deps.root ?? effectiveInstanceRoot();
|
|
153
154
|
const day = opts.day ?? new Date(Date.now() + 9 * 3600_000).toISOString().slice(0, 10);
|
|
154
|
-
|
|
155
|
+
// A seat briefing ignores lens rows (digest drops them), so it does not pay for a lens pass over other seats' items.
|
|
156
|
+
if (opts.telegram && opts.seat === undefined) {
|
|
155
157
|
const annotate = deps.annotateLens ?? (async (lensRoot, lensDay) => {
|
|
156
158
|
const { annotateIntakeLens } = await import('../intake-plane/lens.js');
|
|
157
159
|
return annotateIntakeLens(lensRoot, lensDay);
|
|
@@ -159,9 +161,10 @@ export async function runIntakeDigestCli(
|
|
|
159
161
|
try { await annotate(root, day); }
|
|
160
162
|
catch (error) { debug.log('intake.lens', 'annotate-failed', { day, reason: error instanceof Error ? error.message : String(error) }); }
|
|
161
163
|
}
|
|
162
|
-
const
|
|
164
|
+
const seat = opts.seat === undefined ? undefined : (await import('../intake-plane/intake-sources.js')).canonicalSeat(opts.seat);
|
|
165
|
+
const d = (deps.buildDigest ?? buildIntakeDigest)(root, day, undefined, undefined, seat);
|
|
163
166
|
if (opts.telegram) {
|
|
164
|
-
if (!d.absorbed.length && !d.news?.length) { console.log(`텔레그램: ${day} 흡수 0 — 보내지 않음`); return { empty: true }; }
|
|
167
|
+
if (!seat && !d.absorbed.length && !d.news?.length) { console.log(`텔레그램: ${day} 흡수 0 — 보내지 않음`); return { empty: true }; }
|
|
165
168
|
const text = renderDigestTelegram(d, { ...(opts.vault ? { vaultRoot: opts.vault } : {}), ...(opts.note ? { notePath: opts.note } : {}) });
|
|
166
169
|
const sent = deps.sendTelegram
|
|
167
170
|
? await deps.sendTelegram(text)
|
|
@@ -171,6 +174,8 @@ export async function runIntakeDigestCli(
|
|
|
171
174
|
return sendTelegramReport(getUserConfig(), text, { markdown: true, kind: 'intake' });
|
|
172
175
|
})();
|
|
173
176
|
debug.log('intake.digest', 'telegram', { day, absorbed: d.absorbed.length, goals: d.goals.length, sent });
|
|
177
|
+
if (seat && sent) recordSeatBriefed(root, seat, day, d.absorbed.map((entry) => entry.id));
|
|
178
|
+
if (seat === 'user') debug.log('watch.default', 'brief', { topics: (await import('../intake-plane/intake-sources.js')).listSources({ seat }, root).length, items: d.absorbed.length, sent });
|
|
174
179
|
console.log(sent ? `텔레그램 보고 채널로 보냈다 (${day} · 흡수 ${d.absorbed.length} · 골 후보 ${d.goals.length})` : '텔레그램 보고 채널 설정이 없다(telegram.reportChannel) — 보내지 않음');
|
|
175
180
|
if (!sent) process.exitCode = 3;
|
|
176
181
|
return { sent };
|
|
@@ -397,9 +402,10 @@ export function registerIntakeCommands(program: Command, candidateDeps: IntakeCa
|
|
|
397
402
|
.option('--day <YYYY-MM-DD>', 'KST 날짜 (기본 오늘)')
|
|
398
403
|
.option('--json', '구조화 출력')
|
|
399
404
|
.option('--telegram', '텔레그램 보고 채널(telegram.reportChannel)로 짧은 판을 보낸다')
|
|
405
|
+
.option('--seat <id>', '자리 id (user 아침 브리핑)')
|
|
400
406
|
.option('--vault <root>', '옵시디언 볼트 뿌리 — 텔레그램 판에 노트 열기 주소를 싣는다')
|
|
401
407
|
.option('--note <path>', '열기 주소가 가리킬 노트(그날 트렌드 노트)')
|
|
402
|
-
.action(async (opts: { day?: string; json?: boolean; telegram?: boolean; vault?: string; note?: string }) => {
|
|
408
|
+
.action(async (opts: { day?: string; json?: boolean; telegram?: boolean; vault?: string; note?: string; seat?: string }) => {
|
|
403
409
|
await runIntakeDigestCli(opts);
|
|
404
410
|
});
|
|
405
411
|
|
|
@@ -775,7 +775,9 @@ export const KNOWN_LOG_EVENT_NAMES: ReadonlySet<string> = new Set([
|
|
|
775
775
|
"manifest-frame-fail",
|
|
776
776
|
"manual-goal-evidence-requirement",
|
|
777
777
|
"map",
|
|
778
|
+
"mark-failed",
|
|
778
779
|
"mark-read",
|
|
780
|
+
"marked",
|
|
779
781
|
"marker-readback",
|
|
780
782
|
"marker-readback-exhausted",
|
|
781
783
|
"materialized",
|
|
@@ -1217,6 +1219,7 @@ export const KNOWN_LOG_EVENT_NAMES: ReadonlySet<string> = new Set([
|
|
|
1217
1219
|
"self-orchestrate-exposure",
|
|
1218
1220
|
"self-turn",
|
|
1219
1221
|
"send",
|
|
1222
|
+
"send-failed",
|
|
1220
1223
|
"send.encoder.error",
|
|
1221
1224
|
"send.error",
|
|
1222
1225
|
"send.pcm.error",
|
|
@@ -1249,6 +1252,7 @@ export const KNOWN_LOG_EVENT_NAMES: ReadonlySet<string> = new Set([
|
|
|
1249
1252
|
"showMenu.rejected",
|
|
1250
1253
|
"showMenu.resolved",
|
|
1251
1254
|
"shutdown",
|
|
1255
|
+
"sibling-resync-on-merge-failed",
|
|
1252
1256
|
"sigint",
|
|
1253
1257
|
"signal-cleared",
|
|
1254
1258
|
"signal-consumed-cleared",
|
|
@@ -1564,6 +1568,7 @@ export const KNOWN_LOG_EVENT_NAMES: ReadonlySet<string> = new Set([
|
|
|
1564
1568
|
"visibility-failed",
|
|
1565
1569
|
"visibility-noninteractive",
|
|
1566
1570
|
"visibility-preflight",
|
|
1571
|
+
"visible",
|
|
1567
1572
|
"voice-mode-active",
|
|
1568
1573
|
"warn",
|
|
1569
1574
|
"watch-error",
|