elanous 0.2.25 → 0.2.28
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/{1454-37fd16f479f5554a.js → 1454-29d1d773d5996a49.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{599-a914cc0468f56143.js → 599-56f2c0a5cc4a7295.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-5ab1e47978577c5e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-24453f6a302816b4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-6c131d30bb1cf871.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-b12149c51f7d04b1.js → page-fa46d4588e8442c2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-dc0772b11861af51.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-66ad46ed0279feb9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-5159dc8752c6cd29.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-c5a2478861c08b87.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-f722a9dec084e08b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/decisions/{page-305cd294e754906e.js → page-256ffd365d32e100.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/{page-dea4b795741126fd.js → page-e0de78ae7b99f01c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-cfbdc44b708d9801.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-45889e7070d11126.js → page-4c741eb760f2f77c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-16a2e893f83ae4d0.js → page-e9ba98f5f1440a21.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-dafc901bb1b72b5f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-56888bc744e08dac.js → page-2f6f303fa114ec24.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-1634081c5272006c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/{page-952c6ceec6d9de38.js → page-ced0bcfc6542b3fc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live-run/{page-6d0202af568f4c63.js → page-bb1f05cd51c38944.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-646b50f63b6fe732.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-db069b48a59c8c00.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-4fb6fc34e20c07b7.js → page-f736324afa2117c1.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-e9502921f4f01b1f.js → page-6f131b51193a36f9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{not-found-b6169ea2f09cf287.js → not-found-435f5e2faf02bc01.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/{page-6a14333d1b1b3967.js → page-497e0eedf20e7890.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-8c91a9a66c9ec90a.js → page-a8d28fdf995e16ea.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-8966452228753c5e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fd5d18656cb432b6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-5663779cb1a37609.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/{page-3d3fc73dd187ed52.js → page-66a67eb19ebd976b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-3b492aa78cf3c4a6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-9c04dac0831c2bbb.js → page-c71267e7681da982.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-a21d1e1797172692.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-04de9ca6d52a3893.js → page-cb249e81ed339318.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-62527677bbc2fdc4.js → page-781cfc5114e097da.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-9a2e09c4a311764c.js → page-478157214db6a023.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/{page-bfeb35d69e8b24bb.js → page-86141f12cf090b7f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/share/{page-89cb33bced824e4a.js → page-7256c73bb1c36751.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-5c738ca7adef3de5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-0138c8091ec17224.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-cc2f24f16d85e735.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/{page-6445ac4cbb8dd869.js → page-979d318a534e92da.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-182d394f7e6e096b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-21050928a697ed7a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-44b9d807005a9030.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/{page-20162174b77f96cf.js → page-601fb77340e63c9a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-75d3fb5d47af0edf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/{page-05a4b2c9b91034aa.js → page-11bf9e5725a72c22.js} +1 -1
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +13 -13
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +13 -13
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +13 -13
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +13 -13
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +13 -13
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +13 -13
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +13 -13
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +13 -13
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +13 -13
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +13 -13
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +13 -13
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +13 -13
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +13 -13
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +13 -13
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +13 -13
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +13 -13
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +13 -13
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +13 -13
- package/apps/pwa/out/live-run/index.html +1 -1
- package/apps/pwa/out/live-run/index.txt +13 -13
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +13 -13
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +13 -13
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +13 -13
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +13 -13
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +13 -13
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +13 -13
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +13 -13
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +13 -13
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +13 -13
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +14 -14
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +13 -13
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +13 -13
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +13 -13
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +13 -13
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +13 -13
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +13 -13
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +13 -13
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +13 -13
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +13 -13
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +13 -13
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +13 -13
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +13 -13
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +13 -13
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +13 -13
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +13 -13
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +13 -13
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +13 -13
- package/graphs/implement-loop.yaml +38 -1
- package/graphs/steward/recipes.yaml +6 -0
- package/graphs/steward/release-day.yaml +23 -0
- package/graphs/steward/steward.yaml +9 -1
- package/graphs/work/code-pr.yaml +23 -0
- package/package.json +1 -1
- package/scripts/browser-observe-cycle.ts +76 -2
- package/scripts/ci-typecheck-changed.ts +119 -7
- package/scripts/coord-msg-bridge.ts +209 -0
- package/scripts/coord-post.sh +40 -7
- package/scripts/coord-requests-bridge.ts +10 -5
- package/scripts/detect-test-interference.ts +31 -1
- package/scripts/expose-rubric-suggest.ts +1 -1
- package/scripts/measure-tsc-affected-roots.ts +10 -1
- package/scripts/release-loop/docs-node.ts +51 -22
- package/scripts/release-loop/export-check-node.ts +8 -5
- package/scripts/release-loop/gate-node.ts +37 -4
- package/scripts/release-loop/node-verdict.ts +10 -3
- package/scripts/release-loop/publish-node.ts +3 -0
- package/scripts/release-loop/tui-node-checks.ts +5 -2
- package/scripts/skills/public-check.ts +5 -2
- package/scripts/test-deterministic.ts +520 -37
- package/scripts/ux-sim.ts +7 -1
- package/scripts/verify-kpack6-live.ts +230 -0
- package/src/acp/codex-plugins.ts +17 -0
- package/src/agent/task-notification.ts +32 -2
- package/src/agent-mission/in-agent-plugin.ts +8 -3
- package/src/agent-substrate/review-observation.ts +9 -2
- package/src/agent-substrate/self-review-cli.ts +62 -16
- package/src/boot/daemon-tools/self-implement.ts +72 -4
- package/src/briefing/brief-items.ts +151 -35
- package/src/budget/llm-cost.ts +30 -3
- package/src/budget/usage-outcomes.ts +32 -0
- package/src/claims/claims-ledger.ts +23 -0
- package/src/claims/claims-render.ts +22 -2
- package/src/cli/brief-cli.ts +131 -10
- package/src/cli/claims-cli.ts +24 -4
- package/src/cli/control-cli.ts +29 -0
- package/src/cli/doctor-cli.ts +90 -8
- package/src/cli/msg-cli.ts +177 -11
- package/src/cli/plugin-cli.ts +11 -3
- package/src/cli/pr-cli.ts +133 -18
- package/src/cli/pr-land-overlap-evidence.ts +18 -10
- package/src/cli/pr-land-refusal.ts +20 -0
- package/src/cli/release-cli.ts +34 -5
- package/src/cli/rfc-cli.ts +24 -0
- package/src/cli/seat-cli.ts +121 -0
- package/src/cli/self-utterances-cli.ts +140 -0
- package/src/cli/tailscale-serve.ts +5 -8
- package/src/cli/tasks-cli.ts +528 -31
- package/src/cli/usage-cli.ts +9 -4
- package/src/context-card/cell.ts +40 -0
- package/src/dashboard/boot-timing.ts +35 -0
- package/src/dashboard/first-screen-band.ts +63 -13
- package/src/dashboard/index.ts +94 -27
- package/src/dashboard/input/chat-main-plain-turn-runtime.ts +26 -0
- package/src/dashboard/skill-detection-runtime.ts +24 -4
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +6 -0
- package/src/debug/log.ts +1 -1
- package/src/domains/morning-report.ts +3 -1
- package/src/domains/release-tool.ts +29 -12
- package/src/domains/surface-events.ts +16 -0
- package/src/elanous-config-dir.ts +2 -2
- package/src/fleet/host-doctor.ts +73 -2
- package/src/github/pr-labels.ts +10 -4
- package/src/graph-runner/runner.ts +104 -15
- package/src/harness/harness-clean.ts +21 -3
- package/src/harness/harness-cli-command.ts +68 -10
- package/src/harness/harness-pod-dispatch.ts +11 -4
- package/src/harness/harness-stop.ts +11 -7
- package/src/hq/seats.ts +17 -3
- package/src/index.ts +69 -9
- package/src/instance/current.ts +2 -6
- package/src/instance/resolve.ts +5 -6
- package/src/intelligence-map/model-bench-scores.ts +102 -0
- package/src/llm/openai-sampling-fields.ts +89 -0
- package/src/llm/runtime-model-family.ts +61 -0
- package/src/llm/zai-coding-plan.ts +27 -1
- package/src/llm.ts +13 -89
- package/src/loops/orchestrator/inbox.ts +73 -0
- package/src/loops/orchestrator/resync-shadow.ts +100 -12
- package/src/loops/orchestrator/tick.ts +73 -2
- package/src/machines/machine-ledger.ts +31 -3
- package/src/maturity/cli-maturity.ts +13 -3
- package/src/maturity/feature-maturity.ts +1 -1
- package/src/msg/msg-sse-server.ts +141 -0
- package/src/msg/msg-store.ts +149 -17
- package/src/ops/overview-collect.ts +3 -2
- package/src/ops/overview-owner.ts +29 -0
- package/src/patents/ledger.ts +128 -0
- package/src/plugin-hooks/shell-hook.ts +17 -2
- package/src/plugin-missions/evaluator-runner.ts +18 -2
- package/src/plugins/install/market-fetch.ts +32 -1
- package/src/plugins/install/plugin-install.ts +11 -1
- package/src/pod-lease/admission.ts +116 -41
- package/src/prompt-enhance/enhance.ts +11 -0
- package/src/release-loop/checklist.ts +52 -4
- package/src/release-loop/feature-store.ts +10 -9
- package/src/release-loop/merged-pr-checklist.ts +24 -5
- package/src/release-loop/rubric.ts +70 -0
- package/src/rfc-registry/registry.ts +88 -0
- package/src/seat-dispatch/seat-request-ledger.ts +4 -0
- package/src/seat-loop/knowhow-lint.ts +233 -0
- package/src/seat-loop/seat-loop.ts +5 -1
- package/src/seat-loop/seat-pack.ts +16 -4
- package/src/seat-loop/traffic-signal.ts +7 -0
- package/src/self-dev/ask-launch-flow.ts +48 -8
- package/src/self-dev/ask-path-candidates.ts +69 -14
- package/src/self-dev/decompose-proposal.ts +43 -1
- package/src/self-dev/draft-sweep.ts +23 -14
- package/src/self-dev/graph-journey-nodes.ts +2 -0
- package/src/self-dev/launch-preflight-text.ts +5 -1
- package/src/self-dev/launch-preflight.ts +143 -10
- package/src/self-dev/orchestrate.ts +6 -2
- package/src/self-dev/run-supervisor.ts +35 -1
- package/src/self-implement/gate-cli.ts +6 -2
- package/src/self-implement/goal-author.ts +5 -28
- package/src/self-implement/goal-command-execution.ts +26 -0
- package/src/self-implement/goal-new-paths.ts +4 -5
- package/src/self-implement/graph-authority.ts +17 -3
- package/src/self-implement/graph-yaml.ts +15 -7
- package/src/self-implement/headless-elanous-driver.ts +16 -1
- package/src/self-implement/host-regate.ts +44 -9
- package/src/self-implement/orchestrator.ts +5 -11
- package/src/self-implement/pwa-import-graph.ts +194 -7
- package/src/self-implement/rework-policy.ts +80 -2
- package/src/self-implement/self-implement-runtime.ts +126 -21
- package/src/self-implement/speed-claims.ts +145 -6
- package/src/session/index.ts +14 -4
- package/src/session-runtime/index.ts +55 -11
- package/src/skills/tools/persistent-grounding.ts +59 -4
- package/src/skills/tools/shell.ts +79 -1
- package/src/steward/rejudge-tick.ts +12 -1
- package/src/steward/release-day.ts +158 -0
- package/src/steward/resync-tick.ts +123 -0
- package/src/task-agent/actions.ts +1 -0
- package/src/task-agent/adopt-pr.ts +140 -0
- package/src/task-agent/board.ts +11 -1
- package/src/task-agent/card-evidence.ts +37 -5
- package/src/task-agent/cover.ts +105 -19
- package/src/task-agent/failure-notify.ts +61 -0
- package/src/task-agent/green-sweep.ts +170 -0
- package/src/task-agent/land-worktree.ts +52 -5
- package/src/task-agent/live-move-history.ts +1 -0
- package/src/task-agent/live-moves.ts +56 -17
- package/src/task-agent/parent-host.ts +1 -1
- package/src/task-agent/postland-card.ts +139 -0
- package/src/task-agent/rejudge-on-head.ts +321 -25
- package/src/task-agent/remote-rejudge-bind.ts +62 -0
- package/src/task-agent/repair-branch-cas.ts +182 -0
- package/src/task-agent/repair-cycle.ts +275 -0
- package/src/task-agent/repair-next-move.ts +31 -0
- package/src/task-agent/repair-review.ts +24 -0
- package/src/task-agent/repair-worker.ts +87 -0
- package/src/task-agent/review-result-capture.ts +66 -2
- package/src/task-agent/shadow.ts +150 -29
- package/src/task-agent/task-hand.ts +133 -8
- package/src/task-orchestrator/surfaces/pod-repair-commit.ts +96 -0
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +71 -12
- package/src/telegram.ts +30 -1
- package/src/tool-runtime/tool-search-runtime.ts +25 -0
- package/src/tui.ts +20 -1
- package/src/user-config.ts +11 -60
- package/src/ux-sim/fault-sqlite-busy.ts +50 -0
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-9360b1285b2a346e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f08f6529d09ffae1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-f000d54d258f9ef6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-22f674e7e4c2c9d5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-8888733e40cea6d1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-9717ec6964b92276.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-44eaad486bc98a45.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-97fa6cf3d34c7eb9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-35e05d7450461a67.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-b2638013c0bf7dfd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-c44f93a41167536f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-1b810474507f3702.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-74366ae2693a7d95.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-4e634c5b98fd4e93.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-ed3070d637850548.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-b9cc8a10bc6078b4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-128458c8d86fbb5f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-cf0c2b0183991d09.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-b008aff96be56654.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-814394dcbae789b1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-6a87325c87f09dd2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-852170303c6cd755.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-087ceeb975ef272b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-97c7361cc2a19319.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-8c6901833f897496.js +0 -1
- /package/apps/pwa/out/_next/static/{dRS6lEhSRRBr1abbzp7fJ → y1ltt7oCZxVpC6zVPhZ2O}/_buildManifest.js +0 -0
- /package/apps/pwa/out/_next/static/{dRS6lEhSRRBr1abbzp7fJ → y1ltt7oCZxVpC6zVPhZ2O}/_ssgManifest.js +0 -0
|
@@ -4,7 +4,15 @@
|
|
|
4
4
|
// 기본 MCP 전송은 기존 McpClient 의 initialize + Mcp-Session-Id 수명주기를 탄다.
|
|
5
5
|
// 스케줄러 진입점 배선은 다음 조각의 몫이다. 이 파일은 호출 가능한 함수만 내보낸다.
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import {
|
|
10
|
+
McpClient,
|
|
11
|
+
McpConnectionError,
|
|
12
|
+
McpServerError,
|
|
13
|
+
type McpHttpFetch,
|
|
14
|
+
} from '../src/mcp/client.js';
|
|
15
|
+
import { getElanousConfigDir } from '../src/elanous-config-dir.js';
|
|
8
16
|
import { debug } from '../src/debug/log.js';
|
|
9
17
|
|
|
10
18
|
export const ELANOUS_PWA_URL = 'http://127.0.0.1:31415/app';
|
|
@@ -21,6 +29,10 @@ export const BROWSER_OBSERVE_OK = 'ok';
|
|
|
21
29
|
export const BROWSER_OBSERVE_WRONG_SCREEN = 'wrong-screen';
|
|
22
30
|
/** 관측하지 못했다 */
|
|
23
31
|
export const BROWSER_OBSERVE_UNOBSERVED = 'unobserved';
|
|
32
|
+
/** `unobserved` 사유 — 루프백 owner 토큰을 못 읽어 Authorization 헤더를 못 붙였다 */
|
|
33
|
+
export const BROWSER_OBSERVE_REASON_AUTH_MISSING = 'auth-missing';
|
|
34
|
+
/** `unobserved` 사유 — 루프백 owner 토큰을 붙였는데 서버가 401 로 거부했다 */
|
|
35
|
+
export const BROWSER_OBSERVE_REASON_AUTH_REJECTED = 'auth-rejected';
|
|
24
36
|
|
|
25
37
|
export type BrowserObserveStatus =
|
|
26
38
|
| typeof BROWSER_OBSERVE_OK
|
|
@@ -50,6 +62,39 @@ export interface BrowserObserveResult extends BrowserObserveClassification {
|
|
|
50
62
|
request: BrowserObserveMcpRequest;
|
|
51
63
|
}
|
|
52
64
|
|
|
65
|
+
/** 루프백 owner 토큰을 어디서 읽는지 — `src/cli/mcp-reload.ts` 의 기존 꼴과 같은 자리다. */
|
|
66
|
+
export function browserObserveTokenPath(): string {
|
|
67
|
+
return join(getElanousConfigDir(), 'acp-token');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 루프백 ACP 토큰을 **읽기 전용**으로 얻는다(실패는 null).
|
|
72
|
+
* ⛔ `ensureAuthToken`(src/auth/acp-token.ts) 을 쓰지 않는다 — 그것은 «없으면 만든다».
|
|
73
|
+
* 이 조각은 새 토큰 생성 0: 파일이 없거나 비면 그대로 auth-missing 이다.
|
|
74
|
+
*/
|
|
75
|
+
export function readBrowserObserveToken(): string | null {
|
|
76
|
+
const tokenPath = browserObserveTokenPath();
|
|
77
|
+
try {
|
|
78
|
+
if (!existsSync(tokenPath)) return null;
|
|
79
|
+
const token = readFileSync(tokenPath, 'utf8').trim();
|
|
80
|
+
return token.length > 0 ? token : null;
|
|
81
|
+
} catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** MCP POST 전부에 `Authorization: Bearer <token>` 을 얹는 전송 래퍼. */
|
|
87
|
+
export function withBrowserObserveAuthorization(
|
|
88
|
+
fetchImpl: BrowserObserveHttpFetch,
|
|
89
|
+
token: string,
|
|
90
|
+
): BrowserObserveHttpFetch {
|
|
91
|
+
return (url, init) =>
|
|
92
|
+
fetchImpl(url, {
|
|
93
|
+
...init,
|
|
94
|
+
headers: { ...init.headers, authorization: `Bearer ${token}` },
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
53
98
|
export type BrowserObserveMcpCall = (request: BrowserObserveMcpRequest) => Promise<unknown>;
|
|
54
99
|
export type BrowserObserveLog = (category: string, event: string, data?: unknown) => void;
|
|
55
100
|
export type BrowserObserveHttpFetch = McpHttpFetch;
|
|
@@ -58,6 +103,8 @@ export interface BrowserObserveDeps {
|
|
|
58
103
|
mcpCall?: BrowserObserveMcpCall;
|
|
59
104
|
fetch?: BrowserObserveHttpFetch;
|
|
60
105
|
log?: BrowserObserveLog;
|
|
106
|
+
/** 토큰 획득 주입 자리 — 시험이 파일 시스템 없이 결정성을 얻는다. 생략하면 `readBrowserObserveToken` */
|
|
107
|
+
readToken?: () => string | null;
|
|
61
108
|
}
|
|
62
109
|
|
|
63
110
|
/**
|
|
@@ -328,6 +375,15 @@ function emit(
|
|
|
328
375
|
}
|
|
329
376
|
|
|
330
377
|
function unobservedFromCaught(caught: unknown): BrowserObserveClassification {
|
|
378
|
+
// 루프백 토큰을 붙였는데 서버가 401 로 거부한 경우 — McpClient 가
|
|
379
|
+
// McpConnectionError('auth-required') 로 던진다. 긴 오류 문자열 대신 이유 한 칸으로 가른다.
|
|
380
|
+
if (caught instanceof McpConnectionError && caught.reason === 'auth-required') {
|
|
381
|
+
return {
|
|
382
|
+
status: BROWSER_OBSERVE_UNOBSERVED,
|
|
383
|
+
observedUrl: null,
|
|
384
|
+
reason: BROWSER_OBSERVE_REASON_AUTH_REJECTED,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
331
387
|
if (caught instanceof McpServerError) {
|
|
332
388
|
return {
|
|
333
389
|
status: BROWSER_OBSERVE_UNOBSERVED,
|
|
@@ -342,14 +398,32 @@ function unobservedFromCaught(caught: unknown): BrowserObserveClassification {
|
|
|
342
398
|
};
|
|
343
399
|
}
|
|
344
400
|
|
|
401
|
+
/** 기본 전송(McpClient) 에 루프백 토큰을 얹는다. 토큰이 없으면 null — 401 이 보장된 호출은 아예 안 한다. */
|
|
402
|
+
function defaultAuthorizedMcpCall(deps: BrowserObserveDeps): BrowserObserveMcpCall | null {
|
|
403
|
+
const token = deps.readToken ? deps.readToken() : readBrowserObserveToken();
|
|
404
|
+
if (token === null) return null;
|
|
405
|
+
const base: BrowserObserveHttpFetch = deps.fetch ?? ((url, init) => fetch(url, init));
|
|
406
|
+
const authorized = withBrowserObserveAuthorization(base, token);
|
|
407
|
+
return (request) => callAsideReplViaMcpClient(request, authorized);
|
|
408
|
+
}
|
|
409
|
+
|
|
345
410
|
/** MCP 로 브라우저에서 값을 가져와 공개 분류 함수에 넘긴다. 의존성 주입으로 실 데몬 없이 검증한다. */
|
|
346
411
|
export async function observeBrowserCycle(
|
|
347
412
|
deps: BrowserObserveDeps = {},
|
|
348
413
|
): Promise<BrowserObserveResult> {
|
|
349
414
|
if (!deps.log) await ensureBrowserObserveLogSink();
|
|
350
415
|
const log = deps.log ?? defaultLog;
|
|
351
|
-
const mcpCall = deps.mcpCall ?? ((request) => callAsideReplViaMcpClient(request, deps.fetch));
|
|
352
416
|
const request = buildBrowserObserveMcpRequest();
|
|
417
|
+
const mcpCall = deps.mcpCall ?? defaultAuthorizedMcpCall(deps);
|
|
418
|
+
if (mcpCall === null) {
|
|
419
|
+
const classified: BrowserObserveClassification = {
|
|
420
|
+
status: BROWSER_OBSERVE_UNOBSERVED,
|
|
421
|
+
observedUrl: null,
|
|
422
|
+
reason: BROWSER_OBSERVE_REASON_AUTH_MISSING,
|
|
423
|
+
};
|
|
424
|
+
emit(log, classified);
|
|
425
|
+
return { ...classified, request };
|
|
426
|
+
}
|
|
353
427
|
try {
|
|
354
428
|
const response = await mcpCall(request);
|
|
355
429
|
const rpcError = jsonRpcErrorMessage(response);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Changed-file tsc gate with a visible ratchet for pre-existing test-only debt. */
|
|
2
2
|
import { execFileSync, execSync } from 'node:child_process';
|
|
3
|
-
import { existsSync, mkdtempSync, readFileSync, realpathSync, rmSync, symlinkSync } from 'node:fs';
|
|
3
|
+
import { existsSync, mkdtempSync, readFileSync, realpathSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
import ts from 'typescript';
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
type TypecheckError,
|
|
14
14
|
} from '../src/typecheck-ratchet.js';
|
|
15
15
|
import { debug } from '../src/debug/log.js';
|
|
16
|
+
import { reverseImportAffectedRoots, type AffectedRoots } from '../src/self-implement/pwa-import-graph.js';
|
|
16
17
|
import {
|
|
17
18
|
ROOT_PLACEHOLDER,
|
|
18
19
|
baselineCacheEnabled,
|
|
@@ -758,7 +759,7 @@ export function exportedLiteralUnionMembersInChanges(
|
|
|
758
759
|
pathsResult: ChangedPathResult = changedPathPairs(base),
|
|
759
760
|
readBase: GitFileReader = readGitFile,
|
|
760
761
|
readHead = (file: string) => readFileSync(file, 'utf8'),
|
|
761
|
-
sources?: RepositorySources,
|
|
762
|
+
sources?: RepositorySources | (() => RepositorySources),
|
|
762
763
|
): LiteralUnionMemberCheck {
|
|
763
764
|
if (pathsResult.failure) return { additions: [], failure: pathsResult.failure };
|
|
764
765
|
const additions: LiteralUnionAddition[] = [];
|
|
@@ -779,7 +780,7 @@ export function exportedLiteralUnionMembersInChanges(
|
|
|
779
780
|
}
|
|
780
781
|
if (removals.length === 0) return { additions };
|
|
781
782
|
try {
|
|
782
|
-
return { additions, removals: literalUnionReferencesInSource(removals, changed, sources ?? repositoryTypeScriptSources()) };
|
|
783
|
+
return { additions, removals: literalUnionReferencesInSource(removals, changed, typeof sources === 'function' ? sources() : sources ?? repositoryTypeScriptSources()) };
|
|
783
784
|
} catch (error: unknown) {
|
|
784
785
|
return { additions, failure: `저장소 리터럴 참조 탐색 실패: ${error instanceof Error ? error.message : String(error)}` };
|
|
785
786
|
}
|
|
@@ -872,10 +873,15 @@ export type GateIo = {
|
|
|
872
873
|
literalUnionMembers?: (changed: ReadonlySet<string>, base: string) => LiteralUnionMember[] | LiteralUnionMemberCheck;
|
|
873
874
|
readBaselineDiagnostics?: (base: string, command: string) => BaselineDiagnostics;
|
|
874
875
|
runTscCmd: (cmd: string) => TscRun;
|
|
876
|
+
resolveTscScript?: (cwd: string) => string | undefined;
|
|
875
877
|
/** Landing tree. Git, existsSync, tsc, and the test baseline are measured here — not in process.cwd(). */
|
|
876
878
|
cwd?: string;
|
|
877
879
|
/** Explicit base revision. Same rule as TSC_BASE_REF, without reading the process environment. */
|
|
878
880
|
baseRef?: string;
|
|
881
|
+
/** Source of TSC_GATE_ROOTS / ELANOUS_TSC_AFFECTED_ROOTS. Defaults to process.env. */
|
|
882
|
+
env?: NodeJS.ProcessEnv;
|
|
883
|
+
/** Changed gate sources → themselves plus their direct importers. Defaults to the repository import-graph reader. */
|
|
884
|
+
affectedRoots?: (cwd: string, changed: ReadonlySet<string>) => AffectedRoots;
|
|
879
885
|
log: (message: string) => void;
|
|
880
886
|
warn: (message: string) => void;
|
|
881
887
|
error: (message: string) => void;
|
|
@@ -944,8 +950,63 @@ function inspectTscResult(tsc: TscRun, changed: ReadonlySet<string>, baseline: R
|
|
|
944
950
|
return false;
|
|
945
951
|
}
|
|
946
952
|
|
|
953
|
+
/** Above this many roots a narrowed program stops being much smaller than the whole one. */
|
|
954
|
+
export const TSC_GATE_ROOT_LIMIT = 400;
|
|
955
|
+
/** Files tsconfig.gate.json compiles as roots (its `include`); other changed .ts files are never part of that program. */
|
|
956
|
+
const GATE_ROOT_SOURCE = /^(?:src|scripts|test)\/.*\.ts$/;
|
|
957
|
+
|
|
958
|
+
export type RootPlan =
|
|
959
|
+
| { mode: 'affected'; roots: string[]; changedCount: number }
|
|
960
|
+
| { mode: 'full'; fallbackReason: string; roots?: number };
|
|
961
|
+
|
|
962
|
+
/** Narrow the root-config check to changed files ∪ direct importers, or say why the whole program must run. */
|
|
963
|
+
export function planRootCheck(input: {
|
|
964
|
+
wholeRepository: boolean;
|
|
965
|
+
rootChanged: ReadonlySet<string>;
|
|
966
|
+
env: NodeJS.ProcessEnv;
|
|
967
|
+
cwd: string;
|
|
968
|
+
affectedRoots: (cwd: string, changed: ReadonlySet<string>) => AffectedRoots;
|
|
969
|
+
}): RootPlan {
|
|
970
|
+
if (input.wholeRepository) return { mode: 'full', fallbackReason: '승격(저장소 전체 검사)' };
|
|
971
|
+
if (input.env.ELANOUS_TSC_AFFECTED_ROOTS === '0') return { mode: 'full', fallbackReason: 'ELANOUS_TSC_AFFECTED_ROOTS=0' };
|
|
972
|
+
if (input.env.TSC_GATE_ROOTS === 'full') return { mode: 'full', fallbackReason: 'TSC_GATE_ROOTS=full' };
|
|
973
|
+
const gateChanged = new Set([...input.rootChanged].filter((file) => GATE_ROOT_SOURCE.test(file)));
|
|
974
|
+
if (gateChanged.size === 0) return { mode: 'full', fallbackReason: 'tsconfig.gate.json 루트에 드는 변경 파일 없음' };
|
|
975
|
+
let computed: AffectedRoots;
|
|
976
|
+
try { computed = input.affectedRoots(input.cwd, gateChanged); }
|
|
977
|
+
catch (cause) { return { mode: 'full', fallbackReason: `루트 계산 실패: ${cause instanceof Error ? cause.message : String(cause)}` }; }
|
|
978
|
+
if (computed.failure !== undefined) return { mode: 'full', fallbackReason: `루트 계산 실패: ${computed.failure}` };
|
|
979
|
+
if (!computed.roots || [...gateChanged].some((file) => !computed.roots?.has(file))) {
|
|
980
|
+
return { mode: 'full', fallbackReason: '루트 계산 실패: 변경 파일 누락' };
|
|
981
|
+
}
|
|
982
|
+
const roots = [...computed.roots].filter((file) => GATE_ROOT_SOURCE.test(file)).sort();
|
|
983
|
+
if (roots.length > TSC_GATE_ROOT_LIMIT) return { mode: 'full', fallbackReason: `루트 ${roots.length}개 > 상한 ${TSC_GATE_ROOT_LIMIT}`, roots: roots.length };
|
|
984
|
+
return { mode: 'affected', roots, changedCount: gateChanged.size };
|
|
985
|
+
}
|
|
986
|
+
/** Target checkout first, then the installed CLI's top-level and nested dependencies. */
|
|
987
|
+
export function resolveTscScript(cwd: string, scriptsDir = import.meta.dir, present: (path: string) => boolean = existsSync): string | undefined {
|
|
988
|
+
return [
|
|
989
|
+
join(cwd, 'node_modules/typescript/bin/tsc'),
|
|
990
|
+
join(scriptsDir, '../../typescript/bin/tsc'),
|
|
991
|
+
join(scriptsDir, '../node_modules/typescript/bin/tsc'),
|
|
992
|
+
].find(present);
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
/** The installed compiler cannot resolve declarations from a checkout without its own dependencies. */
|
|
996
|
+
export function missingCheckoutTypeDependencies(cwd: string, needsPwa = false, present: (path: string) => boolean = existsSync): string[] {
|
|
997
|
+
const missing: string[] = [];
|
|
998
|
+
if (!present(join(cwd, 'node_modules'))) missing.push('node_modules');
|
|
999
|
+
if (!present(join(cwd, 'node_modules/bun-types'))) missing.push('bun-types');
|
|
1000
|
+
if (needsPwa && !present(join(cwd, 'apps/pwa/node_modules'))) missing.push('apps/pwa/node_modules');
|
|
1001
|
+
return missing;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
947
1004
|
export function runGate(io: Partial<GateIo> = {}): void {
|
|
948
1005
|
const cwd = io.cwd ?? process.cwd();
|
|
1006
|
+
const targetGit: GitRunner = (command, args, options) => execFileSync(command, args, { ...options, cwd, env: gitEnv() });
|
|
1007
|
+
const targetPaths = (base: string) => changedPathPairs(base, targetGit);
|
|
1008
|
+
const targetFile = (ref: string, path: string) => readGitFile(ref, path, targetGit);
|
|
1009
|
+
const targetSource = (file: string) => readFileSync(join(cwd, file), 'utf8');
|
|
949
1010
|
const defaultChanged = () => changedTsFiles((cmd) => sh(cmd, cwd), process.env, cwd, io.baseRef);
|
|
950
1011
|
const defaultRunTsc = (cmd: string) => runTsc(cmd, ((command, options) => execSync(command, { ...options, cwd, env: tscExecEnv(options?.env) })) as typeof execSync);
|
|
951
1012
|
const { changedTsFiles: getChanged = defaultChanged, runTscCmd = defaultRunTsc, log = (message: string) => console.log(message), warn = (message: string) => console.warn(message), error = (message: string) => console.error(message), exit = ((code: number) => process.exit(code)) as GateIo['exit'] } = io;
|
|
@@ -953,10 +1014,10 @@ export function runGate(io: Partial<GateIo> = {}): void {
|
|
|
953
1014
|
// Set 주입은 기존 테스트 심이며 기준 revision이 없다. 실제 ChangedTsFiles 경로만 Git 관측을 요구한다.
|
|
954
1015
|
const requiredExportFields = io.requiredExportFields ?? ((changed: ReadonlySet<string>, base: string) => base === 'injected test seam'
|
|
955
1016
|
? { fields: [], removedSymbols: [], parameterIncreases: [] }
|
|
956
|
-
: requiredExportFieldsInChanges(changed, base));
|
|
1017
|
+
: requiredExportFieldsInChanges(changed, base, targetPaths(base), targetFile, targetSource));
|
|
957
1018
|
const literalUnionMembers = io.literalUnionMembers ?? ((changed: ReadonlySet<string>, base: string) => base === 'injected test seam'
|
|
958
1019
|
? { additions: [] }
|
|
959
|
-
: exportedLiteralUnionMembersInChanges(changed, base));
|
|
1020
|
+
: exportedLiteralUnionMembersInChanges(changed, base, targetPaths(base), targetFile, targetSource, () => repositoryTypeScriptSources(cwd)));
|
|
960
1021
|
const changedResult = getChanged();
|
|
961
1022
|
const { files: changed, base, baseSource, head, diffFiles, warnings = [], failures = [] } = changedResult instanceof Set
|
|
962
1023
|
? { files: changedResult, base: 'injected test seam', baseSource: 'injected test seam', head: 'injected test seam', diffFiles: new Set<string>(), warnings: [], failures: [] }
|
|
@@ -1053,7 +1114,33 @@ export function runGate(io: Partial<GateIo> = {}): void {
|
|
|
1053
1114
|
}, { level: 'info' });
|
|
1054
1115
|
log(`[tsc-gate] root 설정 범위 ${rootScope.size}개 관측 (apps/pwa/** 제외).`);
|
|
1055
1116
|
if (runsPwa) log(`[tsc-gate] PWA 설정 범위 ${pwaScope.size}개 관측.`);
|
|
1056
|
-
|
|
1117
|
+
// K-OP-039: PWA checks without app dependencies produce fake type errors.
|
|
1118
|
+
// The target checkout's declarations are required even when a compiler is
|
|
1119
|
+
// supplied by the installed CLI or injected by another gate caller.
|
|
1120
|
+
const missing = missingCheckoutTypeDependencies(cwd, runsPwa || wholeRepository);
|
|
1121
|
+
if (missing.length > 0) {
|
|
1122
|
+
const quotedCwd = `'${cwd.replaceAll("'", "'\\''")}'`;
|
|
1123
|
+
const install = `cd ${quotedCwd} && bun install --frozen-lockfile${missing.includes('apps/pwa/node_modules') ? ' && (cd apps/pwa && bun install --frozen-lockfile)' : ''}`;
|
|
1124
|
+
error(`[tsc-gate] ⛔ 대상 트리 타입 의존성 없음: ${missing.join(', ')} — ${install}`);
|
|
1125
|
+
error('[tsc-gate] FAIL — 대상 의존성을 확인하지 못했다(설치본 tsc 존재 ≠ 대상 타입 검사 가능).');
|
|
1126
|
+
exit(1);
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
// The .bin shim uses `#!/usr/bin/env node`. A LaunchAgent can have Bun but no
|
|
1130
|
+
// node on PATH, so invoke the installed compiler with this process's absolute
|
|
1131
|
+
// runtime instead. A missing dependency must fail the gate, never fetch via npx.
|
|
1132
|
+
// Preserve the target checkout's compiler version when available. The
|
|
1133
|
+
// installed CLI may resolve TypeScript from a sibling top-level node_modules
|
|
1134
|
+
// or its own nested node_modules; verify each path before starting a child.
|
|
1135
|
+
const tscScript = (io.resolveTscScript ?? resolveTscScript)(cwd);
|
|
1136
|
+
if (!tscScript) {
|
|
1137
|
+
error('[tsc-gate] ⛔ TypeScript 실행 파일 없음 — 대상 트리 또는 설치본에 bun install --frozen-lockfile 뒤 재실행하십시오.');
|
|
1138
|
+
error('[tsc-gate] FAIL — tsc 를 실행하지 못했다(의존성 없음 ≠ 진단 0건).');
|
|
1139
|
+
exit(1);
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
const shellArg = (value: string) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
1143
|
+
const tscBin = `${shellArg(process.execPath)} ${shellArg(tscScript)}`;
|
|
1057
1144
|
const baseline = readTestTypecheckBaseline(cwd);
|
|
1058
1145
|
const rootCommand = `${tscBin} --noEmit -p ${TYPECHECK_GATE_CONFIG}`;
|
|
1059
1146
|
const pwaCommand = `${tscBin} --noEmit -p apps/pwa/tsconfig.json`;
|
|
@@ -1061,7 +1148,32 @@ export function runGate(io: Partial<GateIo> = {}): void {
|
|
|
1061
1148
|
const rootPromotionBaseline = wholeRepository ? readBaselineDiagnostics(base, rootCommand) : undefined;
|
|
1062
1149
|
const pwaPromotionBaseline = wholeRepository && runsPwa ? readBaselineDiagnostics(base, pwaCommand) : undefined;
|
|
1063
1150
|
const pwaEnvPromotionBaseline = wholeRepository && runsPwaEnv ? readBaselineDiagnostics(base, pwaEnvCommand) : undefined;
|
|
1064
|
-
|
|
1151
|
+
let rootPlan = planRootCheck({ wholeRepository, rootChanged, env: io.env ?? process.env, cwd, affectedRoots: io.affectedRoots ?? reverseImportAffectedRoots });
|
|
1152
|
+
let rootsConfig: string | undefined;
|
|
1153
|
+
if (rootPlan.mode === 'affected') {
|
|
1154
|
+
rootsConfig = `tsconfig.gate-roots.${process.pid}.${Date.now()}.json`;
|
|
1155
|
+
try {
|
|
1156
|
+
// `include` is replaced (not merged) so only the listed roots plus ambient declarations enter the program.
|
|
1157
|
+
writeFileSync(join(cwd, rootsConfig), JSON.stringify({ extends: `./${TYPECHECK_GATE_CONFIG}`, include: ['src/**/*.d.ts', 'scripts/**/*.d.ts', 'test/**/*.d.ts'], files: rootPlan.roots }, null, 2), { flag: 'wx' });
|
|
1158
|
+
} catch (cause) {
|
|
1159
|
+
if (rootsConfig && (cause as NodeJS.ErrnoException).code !== 'EEXIST') {
|
|
1160
|
+
try { rmSync(join(cwd, rootsConfig), { force: true }); }
|
|
1161
|
+
catch { /* The full-program fallback still runs if cleanup is unavailable. */ }
|
|
1162
|
+
}
|
|
1163
|
+
rootsConfig = undefined;
|
|
1164
|
+
rootPlan = { mode: 'full', fallbackReason: `임시 tsconfig 쓰기 실패: ${cause instanceof Error ? cause.message : String(cause)}`, roots: rootPlan.roots.length };
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
if (rootPlan.mode === 'affected') {
|
|
1168
|
+
log(`[tsc-gate] 영향 루트 ${rootPlan.roots.length}개 (변경 ${rootPlan.changedCount} + importer·전역 선언 ${rootPlan.roots.length - rootPlan.changedCount}) — 임시 tsconfig 로 root 검사.`);
|
|
1169
|
+
debug.log('typecheck.gate', 'roots', { mode: 'affected', roots: rootPlan.roots.length, fallbackReason: null }, { level: 'info' });
|
|
1170
|
+
} else {
|
|
1171
|
+
log(`[tsc-gate] 영향 루트 ${rootPlan.roots ?? '?'}개(전체로 폴백: ${rootPlan.fallbackReason})`);
|
|
1172
|
+
debug.log('typecheck.gate', 'roots', { mode: 'full', roots: rootPlan.roots ?? null, fallbackReason: rootPlan.fallbackReason }, { level: 'info' });
|
|
1173
|
+
}
|
|
1174
|
+
let rootTsc: TscRun;
|
|
1175
|
+
try { rootTsc = runTscCmd(rootsConfig ? `${tscBin} --noEmit -p ${rootsConfig}` : rootCommand); }
|
|
1176
|
+
finally { if (rootsConfig) rmSync(join(cwd, rootsConfig), { force: true }); }
|
|
1065
1177
|
let unmeasured = !rootTsc.ran;
|
|
1066
1178
|
let passed = inspectTscResult(rootTsc, rootScope, baseline, error, warn, wholeRepository, ['apps/pwa/'], [], rootPromotionBaseline);
|
|
1067
1179
|
if (runsPwa) {
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/** One channel fetch, one recipient's durable cursor, and idempotent msg insertion. */
|
|
3
|
+
import { Database } from 'bun:sqlite';
|
|
4
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs';
|
|
5
|
+
import { dirname, join } from 'node:path';
|
|
6
|
+
import { createHash } from 'node:crypto';
|
|
7
|
+
import { effectiveInstanceRoot } from '../src/instance/resolve.js';
|
|
8
|
+
import { SEAT_ALIASES, canonicalSeatId, defaultMsgStorePath, openMsgStore } from '../src/msg/msg-store.js';
|
|
9
|
+
import trackData from './coord-tracks.json';
|
|
10
|
+
import { fetchCoordChannelComments, type CoordChannelComment } from './coord-requests-bridge.js';
|
|
11
|
+
|
|
12
|
+
const seats = new Set(trackData.tracks.map((track) => track.id));
|
|
13
|
+
const allSeats = [...seats];
|
|
14
|
+
type Options = { seat: string; channel: string; since?: string; dryRun: boolean };
|
|
15
|
+
type Cursor = { lastId: number; createdAt: string; sinceFloor?: string; parserHash?: string };
|
|
16
|
+
type Routed = { sender: string; recipients: string[] };
|
|
17
|
+
|
|
18
|
+
function parseOptions(args: string[]): Options {
|
|
19
|
+
let seat = '', channel = '26146', since: string | undefined;
|
|
20
|
+
let dryRun = false;
|
|
21
|
+
for (let i = 0; i < args.length; i++) {
|
|
22
|
+
const arg = args[i];
|
|
23
|
+
if (arg === '--seat' && args[i + 1]) seat = canonicalSeatId(args[++i]!);
|
|
24
|
+
else if (arg === '--channel' && args[i + 1]) channel = args[++i]!;
|
|
25
|
+
else if (arg === '--since' && args[i + 1]) since = args[++i]!;
|
|
26
|
+
else if (arg === '--dry-run') dryRun = true;
|
|
27
|
+
else if (arg === '--json') continue; // output formatting belongs to the CLI entry point
|
|
28
|
+
else throw new Error(`unknown option or missing value: ${arg}`);
|
|
29
|
+
}
|
|
30
|
+
if (!seats.has(seat)) throw new Error('--seat must name a channel seat');
|
|
31
|
+
if (!/^[1-9]\d*$/.test(channel)) throw new Error('--channel must be a positive integer');
|
|
32
|
+
if (since && (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(since)
|
|
33
|
+
|| !Number.isFinite(Date.parse(since)))) throw new Error('--since must be an ISO timestamp');
|
|
34
|
+
return { seat, channel, ...(since ? { since } : {}), dryRun };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Only the address part of the first line and explicit body mentions route messages. */
|
|
38
|
+
export function routeCoordComment(body: string): Routed | null {
|
|
39
|
+
const [header = '', ...bodyLines] = body.split(/\r?\n/);
|
|
40
|
+
const match = /^\s*(?:#+\s*)?\*\*\[([^\]]+)\]\*\*[^\n]*?→\s*(.+)$/.exec(header);
|
|
41
|
+
if (!match) return null;
|
|
42
|
+
let sender: string;
|
|
43
|
+
try { sender = canonicalSeatId(match[1]!); } catch { return null; }
|
|
44
|
+
const recipients = new Set<string>();
|
|
45
|
+
// The channel also uses `MK(⊕ UX·TC)` for a primary addressee plus copied seats.
|
|
46
|
+
const address = match[2]!.replace(/\(⊕\s*([^)]*)\)/g, ' · cc $1');
|
|
47
|
+
const fields = address.split('·').map((part) => part.trim());
|
|
48
|
+
let inAddress = true;
|
|
49
|
+
for (const field of fields) {
|
|
50
|
+
const cc = /^cc\s+(.+)$/i.exec(field);
|
|
51
|
+
if (!inAddress && !cc) continue;
|
|
52
|
+
const tokens = (cc?.[1] ?? field).split(/[\s,]+/).filter(Boolean);
|
|
53
|
+
// A title may follow the addressee without a middle dot, for example
|
|
54
|
+
// `→ OP 🎓 확인:`. Only the leading seat tokens belong to the address.
|
|
55
|
+
const firstNonSeat = tokens.findIndex((name) => !seats.has(name)
|
|
56
|
+
&& name !== '전체' && name !== '전원');
|
|
57
|
+
const names = tokens.slice(0, firstNonSeat < 0 ? tokens.length : firstNonSeat);
|
|
58
|
+
if (!names.length) {
|
|
59
|
+
if (!cc) inAddress = false;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (names.includes('전체') || names.includes('전원')) {
|
|
63
|
+
for (const seat of allSeats) recipients.add(seat);
|
|
64
|
+
} else for (const name of names) recipients.add(name);
|
|
65
|
+
if (!cc && names.length < tokens.length) inAddress = false;
|
|
66
|
+
}
|
|
67
|
+
for (const match of bodyLines.join('\n').matchAll(/🙋\s*([A-Z][A-Z0-9_-]*)/g)) {
|
|
68
|
+
if (seats.has(match[1]!)) recipients.add(match[1]!);
|
|
69
|
+
}
|
|
70
|
+
recipients.delete(sender);
|
|
71
|
+
return { sender, recipients: [...recipients] };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Include the sender canonicalizer and its aliases: either can change which
|
|
75
|
+
// comments route. A harmless build-only hash change causes one safe replay.
|
|
76
|
+
const parserHash = createHash('sha256').update(routeCoordComment.toString())
|
|
77
|
+
.update(canonicalSeatId.toString()).update(JSON.stringify(SEAT_ALIASES))
|
|
78
|
+
.update(JSON.stringify(allSeats)).digest('hex');
|
|
79
|
+
const REPLAY_WINDOW_MS = 6 * 60 * 60 * 1000;
|
|
80
|
+
|
|
81
|
+
function validComment(value: CoordChannelComment): void {
|
|
82
|
+
if (!value || !Number.isSafeInteger(value.id) || value.id <= 0 || typeof value.body !== 'string'
|
|
83
|
+
|| typeof value.created_at !== 'string' || !Number.isFinite(Date.parse(value.created_at))
|
|
84
|
+
|| typeof value.html_url !== 'string') throw new Error('invalid channel comment');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function readCursor(path: string): Cursor | null {
|
|
88
|
+
if (!existsSync(path)) return null;
|
|
89
|
+
// Each run appends one small record. Concurrent runs cannot replace a newer cursor
|
|
90
|
+
// with an older snapshot; the highest comment ID remains the effective cursor.
|
|
91
|
+
let latest: Cursor | null = null;
|
|
92
|
+
// A process can stop mid-append. The next complete record begins with the
|
|
93
|
+
// stable key prefix and remains readable even when joined to a torn suffix.
|
|
94
|
+
const records = readFileSync(path, 'utf8').replaceAll('{"lastId":', '\n{"lastId":').split('\n').filter(Boolean);
|
|
95
|
+
for (const line of records) {
|
|
96
|
+
let value: Partial<Cursor>;
|
|
97
|
+
try { value = JSON.parse(line) as Partial<Cursor>; }
|
|
98
|
+
catch (error) {
|
|
99
|
+
if (error instanceof SyntaxError) continue;
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
if (!Number.isSafeInteger(value.lastId) || value.lastId! < 0 || typeof value.createdAt !== 'string'
|
|
103
|
+
|| !Number.isFinite(Date.parse(value.createdAt)) || (value.sinceFloor !== undefined
|
|
104
|
+
&& (typeof value.sinceFloor !== 'string' || !Number.isFinite(Date.parse(value.sinceFloor))))
|
|
105
|
+
|| (value.parserHash !== undefined && !/^[a-f0-9]{64}$/.test(value.parserHash))) {
|
|
106
|
+
throw new Error('invalid channel cursor');
|
|
107
|
+
}
|
|
108
|
+
// On equal IDs, keep the current parser marker even if an older overlapping
|
|
109
|
+
// process appends its stale cursor afterward. A current marker also replaces
|
|
110
|
+
// an older marker at the same ID after a replay with no newly fetched comment.
|
|
111
|
+
if (!latest || value.lastId! > latest.lastId || (value.lastId === latest.lastId
|
|
112
|
+
&& (value.parserHash === parserHash || latest.parserHash !== parserHash))) latest = {
|
|
113
|
+
lastId: value.lastId!, createdAt: value.createdAt,
|
|
114
|
+
...(value.sinceFloor ? { sinceFloor: value.sinceFloor } : {}),
|
|
115
|
+
...(value.parserHash ? { parserHash: value.parserHash } : {}),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
return latest;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function saveCursor(path: string, cursor: Cursor): void {
|
|
122
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
123
|
+
appendFileSync(path, `${JSON.stringify(cursor)}\n`, { mode: 0o600 });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function hasProvenance(db: Database, key: string): boolean {
|
|
127
|
+
return db.query('SELECT 1 FROM msg_provenance WHERE provenance_key = ?').get(key) !== null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function runBridge(args: string[], fetch = fetchCoordChannelComments): {
|
|
131
|
+
seat: string; read: number; picked: number; added: number; existing: number; cursor: number;
|
|
132
|
+
} {
|
|
133
|
+
return runBridgeWithClock(args, fetch, Date.now);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function runBridgeWithClock(args: string[], fetch: typeof fetchCoordChannelComments, now: () => number): {
|
|
137
|
+
seat: string; read: number; picked: number; added: number; existing: number; cursor: number;
|
|
138
|
+
} {
|
|
139
|
+
const options = parseOptions(args);
|
|
140
|
+
const cursorPath = join(effectiveInstanceRoot(), 'msg', `coord-bridge-${options.channel}-${options.seat}.json`);
|
|
141
|
+
const cursor = readCursor(cursorPath);
|
|
142
|
+
const replayFloor = cursor && cursor.parserHash !== parserHash ? now() - REPLAY_WINDOW_MS : undefined;
|
|
143
|
+
// GitHub filters `since` by updated_at; the creation-time check below limits
|
|
144
|
+
// parser replay to comments created in the last six hours, even if older
|
|
145
|
+
// comments were edited recently. Numeric IDs decide normal progress.
|
|
146
|
+
// GitHub's `since` is exclusive. Fetch one second before the replay floor
|
|
147
|
+
// so a comment created exactly on the six-hour boundary remains visible.
|
|
148
|
+
const since = cursor ? new Date(Math.min(Date.parse(cursor.createdAt) - 1000,
|
|
149
|
+
replayFloor === undefined ? Infinity : replayFloor - 1000)).toISOString() : options.since;
|
|
150
|
+
const comments = fetch(options.channel, since);
|
|
151
|
+
for (const comment of comments) validComment(comment); // no writes if fetching/parsing fails mid-page
|
|
152
|
+
// GitHub's `since` filters updated_at, so a caller's creation-time floor persists across runs.
|
|
153
|
+
const unseen = comments.filter((comment) => comment.id > (cursor?.lastId ?? 0));
|
|
154
|
+
const sinceFloor = cursor?.sinceFloor ?? (cursor ? undefined : options.since);
|
|
155
|
+
const pending = comments.filter((comment) =>
|
|
156
|
+
(comment.id > (cursor?.lastId ?? 0) || (replayFloor !== undefined
|
|
157
|
+
&& Date.parse(comment.created_at) >= replayFloor))
|
|
158
|
+
&& (!sinceFloor || Date.parse(comment.created_at) >= Date.parse(sinceFloor)));
|
|
159
|
+
const candidate = unseen.reduce<CoordChannelComment | null>((last, comment) =>
|
|
160
|
+
!last || comment.id > last.id ? comment : last, null);
|
|
161
|
+
let picked = 0, added = 0, existing = 0;
|
|
162
|
+
const dbPath = defaultMsgStorePath();
|
|
163
|
+
if (options.dryRun) {
|
|
164
|
+
const db = existsSync(dbPath) ? new Database(dbPath, { readonly: true }) : null;
|
|
165
|
+
try {
|
|
166
|
+
for (const comment of pending) {
|
|
167
|
+
const route = routeCoordComment(comment.body);
|
|
168
|
+
if (!route?.recipients.includes(options.seat)) continue;
|
|
169
|
+
picked++;
|
|
170
|
+
if (db && hasProvenance(db, `coord:${comment.id}:${options.seat}`)) existing++;
|
|
171
|
+
else added++;
|
|
172
|
+
}
|
|
173
|
+
} finally { db?.close(); }
|
|
174
|
+
} else {
|
|
175
|
+
const store = openMsgStore();
|
|
176
|
+
try {
|
|
177
|
+
store.db.transaction(() => {
|
|
178
|
+
for (const comment of pending) {
|
|
179
|
+
const route = routeCoordComment(comment.body);
|
|
180
|
+
if (!route?.recipients.includes(options.seat)) continue;
|
|
181
|
+
picked++;
|
|
182
|
+
const key = `coord:${comment.id}:${options.seat}`;
|
|
183
|
+
if (hasProvenance(store.db, key)) existing++;
|
|
184
|
+
else added++;
|
|
185
|
+
store.appendWithProvenance({ from: route.sender, to: options.seat, body: comment.body, kind: 'coord' }, key);
|
|
186
|
+
}
|
|
187
|
+
}).immediate();
|
|
188
|
+
} finally { store.close(); }
|
|
189
|
+
if (candidate || (!cursor && sinceFloor) || replayFloor !== undefined) {
|
|
190
|
+
const createdAt = candidate?.created_at ?? cursor?.createdAt ?? sinceFloor!;
|
|
191
|
+
saveCursor(cursorPath, { lastId: candidate?.id ?? cursor?.lastId ?? 0,
|
|
192
|
+
createdAt: sinceFloor && Date.parse(createdAt) < Date.parse(sinceFloor) ? sinceFloor : createdAt,
|
|
193
|
+
...(sinceFloor ? { sinceFloor } : {}), parserHash });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return { seat: options.seat, read: comments.length, picked, added, existing,
|
|
197
|
+
cursor: options.dryRun ? cursor?.lastId ?? 0 : readCursor(cursorPath)?.lastId ?? 0 };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (import.meta.main) {
|
|
201
|
+
try {
|
|
202
|
+
const result = runBridge(process.argv.slice(2));
|
|
203
|
+
console.log(process.argv.includes('--json') ? JSON.stringify(result)
|
|
204
|
+
: `${result.seat}: read ${result.read}, picked ${result.picked}, added ${result.added}, existing ${result.existing}, cursor ${result.cursor}`);
|
|
205
|
+
} catch (error) {
|
|
206
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
207
|
+
process.exitCode = 1;
|
|
208
|
+
}
|
|
209
|
+
}
|
package/scripts/coord-post.sh
CHANGED
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
# coord-post.sh — 조율 채널 발신 «한 자리». 시각과 명시적 값을 본문에 안전하게 넣는다.
|
|
3
3
|
# 사용: scripts/coord-post.sh [--set KEY=VALUE]... <본문파일>
|
|
4
4
|
set -u
|
|
5
|
-
|
|
5
|
+
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) || { echo '⛔ 스크립트 위치를 못 읽었다' >&2; exit 3; }
|
|
6
|
+
REPO_ROOT=$(dirname -- "$SCRIPT_DIR")
|
|
7
|
+
CLI="$REPO_ROOT/bin/elanous.mjs"
|
|
8
|
+
CANON="$REPO_ROOT/docs/manual/MANUAL-multi-agent-coordination-channel-2026-07-28.md"
|
|
6
9
|
|
|
7
10
|
resolve_pr() {
|
|
8
11
|
[ -f "$CANON" ] || return 0
|
|
@@ -40,7 +43,7 @@ BODY="${1:-}"
|
|
|
40
43
|
grep -q '{{TS}}' "$BODY" || echo "⚠️ 본문에 {{TS}} 자리표시가 «없다» — 시각을 손으로 적지 않았는지 확인하라" >&2
|
|
41
44
|
|
|
42
45
|
# 트랙 신원은 «정본 한 곳»에서 읽는다 — scripts/coord-tracks.json. 못 읽으면 추측하지 않고 선다.
|
|
43
|
-
TRACKS_FILE="$
|
|
46
|
+
TRACKS_FILE="$SCRIPT_DIR/coord-tracks.json"
|
|
44
47
|
# 신원 = 2글자 id(OP·MK·TC·UX…) ⊕ 옛 한 글자 alias(S·T·O·F — 전환기엔 받되 경고). 목록은 정본에서만.
|
|
45
48
|
IDS=$(grep -o '"id": *"[A-Z][A-Z]*"' "$TRACKS_FILE" 2>/dev/null | sed 's/.*"\([A-Z]*\)"$/\1/' | tr '\n' ' ')
|
|
46
49
|
ALIASES=$(grep -o '"alias": *"[A-Z][A-Z]*"' "$TRACKS_FILE" 2>/dev/null | sed 's/.*"\([A-Z]*\)"$/\1/' | tr '\n' ' ')
|
|
@@ -192,7 +195,7 @@ if [ "${COORD_DRY_RUN:-}" = "1" ]; then
|
|
|
192
195
|
exit 0
|
|
193
196
|
fi
|
|
194
197
|
|
|
195
|
-
POST_OUTPUT=$(bun
|
|
198
|
+
POST_OUTPUT=$(bun "$CLI" gh pr comment "$PR" --body-file "$TMP" 2>&1)
|
|
196
199
|
RC=$?
|
|
197
200
|
printf '%s\n' "$POST_OUTPUT" | tail -2
|
|
198
201
|
VIA=""
|
|
@@ -204,11 +207,11 @@ if [ "$RC" -ne 0 ] && printf '%s' "$POST_OUTPUT" | grep -qiE 'rate limit|Somethi
|
|
|
204
207
|
FIRSTOUT=$(head -1 "$TMP")
|
|
205
208
|
# issues 코멘트 목록은 오래된 순이고 정렬 인자를 안 받는다 ⇒ 최근 10분만 since 로 자른다.
|
|
206
209
|
SINCE10=$(date -u -v-10M +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -d '-10 min' +%Y-%m-%dT%H:%M:%SZ)
|
|
207
|
-
if bun
|
|
210
|
+
if bun "$CLI" gh api "repos/{owner}/{repo}/issues/$PR/comments?since=$SINCE10&per_page=100" --jq '.[].body | split("\n")[0]' 2>/dev/null | grep -qxF -- "$FIRSTOUT"; then
|
|
208
211
|
echo "[coord-post] 같은 첫 줄이 이미 채널에 있다 — GraphQL 이 실제로는 올렸다(이중 발신 안 함)" >&2
|
|
209
212
|
exit 0
|
|
210
213
|
fi
|
|
211
|
-
POST_OUTPUT=$(bun
|
|
214
|
+
POST_OUTPUT=$(bun "$CLI" gh api -X POST "repos/{owner}/{repo}/issues/$PR/comments" -F "body=@$TMP" -q .html_url 2>&1)
|
|
212
215
|
RC=$?
|
|
213
216
|
printf '%s\n' "$POST_OUTPUT" | tail -2
|
|
214
217
|
VIA=" · REST 폴백"
|
|
@@ -224,13 +227,43 @@ POST_URL=$(printf '%s\n' "$POST_OUTPUT" | grep -oE 'https://github\.com/[^[:spac
|
|
|
224
227
|
# test universe (OP 10-04 18:00). An explicit ELANOUS_STATE_DIR is the caller's chosen universe and wins;
|
|
225
228
|
# otherwise prefer the installed CLI (resolves prod from any tree), else pin the source CLI to prod.
|
|
226
229
|
if [ -n "${ELANOUS_STATE_DIR:-}" ]; then
|
|
227
|
-
RECORD=(bun
|
|
230
|
+
RECORD=(bun "$CLI" coord event record)
|
|
231
|
+
MESSAGE=(bun "$CLI" msg post)
|
|
228
232
|
elif command -v eln >/dev/null 2>&1; then
|
|
229
233
|
RECORD=(eln coord event record)
|
|
234
|
+
MESSAGE=(eln msg post)
|
|
230
235
|
else
|
|
231
|
-
RECORD=(env ELANOUS_STATE_DIR="$HOME/.elanous" bun
|
|
236
|
+
RECORD=(env ELANOUS_STATE_DIR="$HOME/.elanous" bun "$CLI" --config-dir "$HOME/.elanous" coord event record)
|
|
237
|
+
MESSAGE=(env ELANOUS_STATE_DIR="$HOME/.elanous" bun "$CLI" --config-dir "$HOME/.elanous" msg post)
|
|
232
238
|
fi
|
|
233
239
|
if ! "${RECORD[@]}" --seat "$ID" --header "$(head -1 "$TMP")" --url "$POST_URL" >/dev/null 2>&1; then
|
|
234
240
|
echo '⚠️ 맥락 원장 기록 실패(발신은 됐다)' >&2
|
|
235
241
|
fi
|
|
242
|
+
# S1's channel reader and this sender must agree on recipients. Keep the complete sent body
|
|
243
|
+
# for msg, while the provenance key makes a later S1 scan (or a partial retry) idempotent.
|
|
244
|
+
COMMENT_ID=${POST_URL##*#issuecomment-}
|
|
245
|
+
if [ "$COMMENT_ID" = "$POST_URL" ] || ! [[ "$COMMENT_ID" =~ ^[0-9]+$ ]]; then
|
|
246
|
+
echo '⚠️ 채널 댓글 ID를 못 읽어 msg 반영을 건너뛴다(발신은 됐다 · S1 다리가 회수)' >&2
|
|
247
|
+
exit 0
|
|
248
|
+
fi
|
|
249
|
+
ROUTES=$(bun -e '
|
|
250
|
+
import { readFileSync } from "node:fs";
|
|
251
|
+
import { join } from "node:path";
|
|
252
|
+
import { pathToFileURL } from "node:url";
|
|
253
|
+
const { routeCoordComment } = await import(pathToFileURL(join(process.argv[3], "coord-msg-bridge.ts")).href);
|
|
254
|
+
const { canonicalSeatId } = await import(pathToFileURL(join(process.argv[3], "../src/msg/msg-store.ts")).href);
|
|
255
|
+
const route = routeCoordComment(readFileSync(process.argv[2], "utf8"));
|
|
256
|
+
if (!route || route.sender !== canonicalSeatId(process.argv[1])) process.exit(2);
|
|
257
|
+
console.log(route.recipients.join("\n"));
|
|
258
|
+
' "$ID" "$TMP" "$SCRIPT_DIR") || {
|
|
259
|
+
echo '⚠️ msg 수신자 해석 실패(발신은 됐다 · S1 다리가 회수)' >&2
|
|
260
|
+
exit 0
|
|
261
|
+
}
|
|
262
|
+
while IFS= read -r RECIPIENT; do
|
|
263
|
+
[ -n "$RECIPIENT" ] || continue
|
|
264
|
+
if ! "${MESSAGE[@]}" --from "$ID" --to "$RECIPIENT" --kind coord \
|
|
265
|
+
--origin-key "coord:$COMMENT_ID:$RECIPIENT" < "$TMP" >/dev/null; then
|
|
266
|
+
echo "⚠️ msg 원장 기록 실패: $RECIPIENT(발신은 됐다 · S1 다리가 회수)" >&2
|
|
267
|
+
fi
|
|
268
|
+
done <<< "$ROUTES"
|
|
236
269
|
exit 0
|