elanous 0.2.15 → 0.2.17
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-33ffcc49ab45123b.js → 1307-aac7332576c4d6fc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{6877-7c6208705c284124.js → 6877-fe94e76a5841ee9b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8401-4ce1c42c8d89903f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/899-f41be85e8a680651.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9103-7a3ae7f60af3b99f.js → 9103-60fbef33e66f0a46.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9247-269e4464cc4b491c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/{page-0e2c57fe6add4b2a.js → page-3143b07a817d1da9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-e05a77df7e005b04.js → page-17ba086ef82d339b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-7eb661ff23af7da2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-6b8444914da74ca6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/{page-b8c49f6b4fa2df91.js → page-6e5c3a867557298b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-7110f732f45bf944.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-976813c91d7394ea.js → page-9a41d65355fc25b9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-5e77cd1802567c89.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/{page-1d4901eb7c2d8822.js → page-41afec0b255ed2bb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/{page-72d3e7ff35a8c954.js → page-4187666132fc98c4.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/{page-60c7a8e1a4fe62ce.js → page-09328d499162bae4.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-2a52b316cb143d9c.js → page-29ff3a8f84f03b6a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-c3ce483c8d5db370.js → page-80ec7401bc83f511.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-ab93b240f687d69a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-96f074f6538e8a2c.js → page-fc7d86467291ae38.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{layout-e97be5334d83a9f5.js → layout-de43a186e5ba5683.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-8b08cafad27759c9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-0e2d6985fb42cd2f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-13e4108999f58d13.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-e8296ee284aa1293.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-c1343f9499d86f3f.js → page-043a9111e74f6b24.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{not-found-8739a2e0ae793130.js → not-found-23a4601ceb7f452e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-1ed1324b1dc36754.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-d23b12bb92242558.js → page-10d958605a77b3bd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-166745e51ac0c7b0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/{page-c7fea475488201a8.js → page-a04260b8ccc968d4.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-7a1d85e121065cc8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-1f50e3d4e13435f4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/{page-5efbf81124c474ae.js → page-12916918988ac97e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-d8a3347a9d0c3f4a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-a9117d2cd3093d36.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-06ee266953d9c879.js → page-5a64cb5208c6a8fd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-153c2b3bcc89e8de.js → page-1407ec83ea64c1f9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-1d824510df025d18.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-6e878ebbe1737d00.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/{page-0d6d23b40263a7ea.js → page-7f49fe645d5132f7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-81a4e7f8dbfe74cb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-27d71f131acd9320.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-3ab59d19913b200e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-bf85b2f5bf077925.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/{page-73735b64bfe1ae67.js → page-424659bacfb8cdd7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-ab32a8f522a60371.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-f7e631e9b4daf66e.js → page-fc2df261f8a2c7df.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-09461366af82ef13.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/{page-9817c7f9007466cc.js → page-5d4fef9ccd7d406b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ea90a8ac73ce2749.js +1 -0
- package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +1 -0
- package/apps/pwa/out/_next/static/css/{04dabcbcc4bfa6db.css → 085673d826b98b62.css} +1 -1
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +15 -15
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +15 -15
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +15 -15
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +15 -15
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +15 -15
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +15 -15
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +15 -15
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +15 -15
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +15 -15
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +15 -15
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +15 -15
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +15 -15
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +15 -15
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +15 -15
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +15 -15
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +15 -15
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +15 -15
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +15 -15
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +15 -15
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +15 -15
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +15 -15
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +15 -15
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +15 -15
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +15 -15
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +15 -15
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +15 -15
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +16 -16
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +15 -15
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +15 -15
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +15 -15
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +15 -15
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +15 -15
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +15 -15
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +15 -15
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +15 -15
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +15 -15
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +15 -15
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +15 -15
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +15 -15
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +15 -15
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +15 -15
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +15 -15
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +15 -15
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +15 -15
- package/graphs/doc-gen/doc-gen.yaml +24 -0
- package/graphs/doc-gen/recipes.yaml +3 -0
- package/graphs/intake/intake-daily.yaml +9 -2
- package/graphs/intake/news-sources.yaml +4 -0
- package/graphs/intake/recipes.yaml +6 -1
- package/graphs/orchestrator/orchestrator.yaml +7 -0
- package/graphs/orchestrator/recipes.yaml +3 -0
- package/graphs/steward/recipes.yaml +2 -1
- package/package.json +1 -1
- package/scripts/__fixtures__/intake-news.xml +7 -0
- package/scripts/ci-public-export-import-gate.ts +154 -0
- package/scripts/ci-test-interference-gate.ts +5 -1
- package/scripts/ci-typecheck-changed.ts +34 -15
- package/scripts/docs/doc-rot.ts +164 -0
- package/scripts/docs/generate-graph-anatomy.ts +131 -9
- package/scripts/docs-cli-check.ts +4 -4
- package/scripts/goals/goal-census.ts +343 -0
- package/scripts/intake-news.ts +169 -0
- package/scripts/measure/author-section-contribution.ts +84 -0
- package/scripts/release-loop/cut-branch.ts +43 -13
- package/scripts/release-loop/docs-land-node.ts +69 -10
- package/scripts/release-loop/gate-node.ts +3 -1
- package/scripts/release-loop/publish-node.ts +2 -2
- package/scripts/release-loop/release-docs.ts +134 -0
- package/scripts/release-loop/vault-note-node.ts +19 -2
- package/scripts/release-loop/version-node.ts +7 -4
- package/scripts/release-story/draft.ts +42 -14
- package/scripts/rhythm/monthly.ts +97 -0
- package/src/acp/client.ts +17 -2
- package/src/agent-mission/driver.ts +104 -11
- package/src/agent-mission/supervisor-metrics-cli.ts +40 -0
- package/src/agent-mission/supervisor-metrics.ts +218 -0
- package/src/autopilot/track-agent-shadow.ts +2 -1
- package/src/autopilot/track-agent.ts +5 -3
- package/src/briefing/brief-items.ts +191 -36
- package/src/budget/usage-outcomes.ts +204 -0
- package/src/cli/autopilot-cli.ts +66 -0
- package/src/cli/brief-cli.ts +73 -0
- package/src/cli/decisions-cli.ts +4 -1
- package/src/cli/intake-cli.ts +1 -1
- package/src/cli/know-cli.ts +20 -0
- package/src/cli/log-event-names.ts +1 -0
- package/src/cli/logs-cli.ts +43 -1
- package/src/cli/pr-cli.ts +92 -13
- package/src/cli/provider-cli.ts +14 -0
- package/src/cli/release-cli.ts +11 -8
- package/src/cli/self-update-canary.ts +46 -0
- package/src/cli/self-update.ts +399 -1
- package/src/cli/usage-cli.ts +26 -0
- package/src/context-card/index.ts +45 -0
- package/src/context-card/task.ts +76 -0
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +2 -0
- package/src/decisions/decision-ledger.ts +10 -1
- package/src/discord-self-message.ts +9 -0
- package/src/flow-loop/split.ts +24 -0
- package/src/harness/harness-cli-command.ts +178 -12
- package/src/harness/harness-pod-dispatch.ts +36 -55
- package/src/harness/harness-queue.ts +86 -7
- package/src/harness/harness-seams.ts +13 -3
- package/src/harness/helper-repair.ts +139 -8
- package/src/harness/helper-scan.ts +13 -2
- package/src/harness/nested-elanous-policy.ts +133 -0
- package/src/hq/fence-wrapper.ts +123 -0
- package/src/hq/hq.ts +16 -2
- package/src/hq/lease.ts +5 -3
- package/src/hq/transfer.ts +185 -0
- package/src/index.ts +168 -13
- package/src/intake-plane/digest.ts +85 -28
- package/src/knowledge/know-find.ts +105 -0
- package/src/llm.ts +75 -19
- package/src/loops/orchestrator/finish-rate.ts +14 -3
- package/src/loops/orchestrator/tick.ts +96 -25
- package/src/maturity/bot-slash-catalog.ts +11 -2
- package/src/maturity/feature-maturity.ts +2 -2
- package/src/maturity/tui-slash-availability.ts +9 -0
- package/src/nexus/api/grid.ts +61 -0
- package/src/nexus/api/http-server.ts +19 -1
- package/src/nexus/api/task-cards-api.ts +19 -0
- package/src/oauth/codex.ts +82 -2
- package/src/oauth/overload-failover-launch.ts +75 -0
- package/src/oauth/overload-failover.ts +137 -0
- package/src/release-loop/checklist.ts +10 -5
- package/src/release-loop/feature-store.ts +10 -9
- package/src/release-loop/landing-freeze.ts +18 -10
- package/src/release-loop/placement.ts +17 -8
- package/src/seat-dispatch/seat-ask.ts +33 -6
- package/src/seat-loop/seat-loop.ts +172 -21
- package/src/self-dev/ask-launch-flow.ts +48 -8
- package/src/self-dev/authored-goal-trace.ts +64 -0
- package/src/self-dev/dev-cli.ts +19 -1
- package/src/self-dev/dev-pipeline.ts +6 -5
- package/src/self-dev/entrance-registry.ts +10 -6
- package/src/self-dev/old-door.ts +73 -0
- package/src/self-dev/run-store.ts +5 -1
- package/src/self-dev/run-supervisor.ts +7 -1
- package/src/self-dev/supervisor-verdict-edges.ts +46 -0
- package/src/self-implement/app-api-budget.ts +166 -0
- package/src/self-implement/budget-gate.ts +7 -5
- package/src/self-implement/follow-up-goals.ts +289 -0
- package/src/self-implement/frozen-merges.ts +25 -20
- package/src/self-implement/goal-author-clarification.ts +81 -0
- package/src/self-implement/goal-author-cli.ts +33 -5
- package/src/self-implement/goal-author.ts +107 -25
- package/src/self-implement/goal-clarification-escalation.ts +37 -1
- package/src/self-implement/goal-document-write.ts +28 -0
- package/src/self-implement/heal-intake.ts +4 -0
- package/src/self-implement/heal-triage.ts +44 -0
- package/src/self-implement/host-regate.ts +3 -1
- package/src/self-implement/orchestrator.ts +157 -8
- package/src/self-implement/run-ledger.ts +4 -0
- package/src/self-implement/seams.ts +44 -15
- package/src/session-runtime/index.ts +2 -0
- package/src/session-runtime/retry-api.ts +87 -0
- package/src/session-runtime/retry-policy.ts +107 -6
- package/src/steward/working-backwards.ts +16 -1
- package/src/task-agent/actions.ts +118 -0
- package/src/task-agent/judge.ts +96 -0
- package/src/task-cards/card-cli.ts +25 -0
- package/src/task-cards/card-store.ts +9 -4
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +33 -4
- package/src/task-orchestrator/surfaces/pod-lease.ts +5 -2
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +4 -3
- package/src/task-orchestrator/surfaces/pod-ledger-prod-sink.ts +75 -0
- package/src/task-orchestrator/surfaces/pod-pool.ts +95 -18
- package/src/task-orchestrator/surfaces/pod-source-receive.ts +37 -1
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +168 -37
- package/src/task-orchestrator/surfaces/self-implement.ts +2 -0
- package/src/telegram-commands.ts +6 -1
- package/src/telegram-loops-command.ts +21 -14
- package/src/telegram.ts +47 -0
- package/src/user-config.ts +59 -4
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/8827-e2f5b09a8d37e83b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/899-46946fe8363e1262.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9247-29d889b809288e65.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-f544338193a9a762.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-5491c115b1f9a923.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-96398e364fbd4548.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-28b9448091776c06.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-13c7b53e0c2519a9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-65e484302921a932.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-34d08facaf08f556.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-955ec2da3be4c560.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-9f88fbc15d9058d1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-c9fc5706b8abaa11.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-8d47f45a0dc044ac.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-e2ee2896811e98d0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-83b19dae8619de05.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-85f1a1fc0a82738c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-da6c08aae0f92768.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-87a11e6a05f15199.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-7d4182964407c09c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-7e8c39241f8eb6d0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-008c1a96618a4887.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-f5597e894c02cd33.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-0337fdf7fb5e37b6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-8fcec4c9ea2e7f70.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-4881b8f9f63694b4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-97f9f43172a3a90e.js +0 -1
- package/apps/pwa/out/_next/static/css/1f7c958c41ffa3b9.css +0 -1
- package/src/launch-head/seat-goal-queue-lock.fixture.ts +0 -15
- package/src/launch-head/seat-goal-queue.ts +0 -198
- /package/apps/pwa/out/_next/static/{LZcstaO9_NXypPFmujolv → yRA5ur6DcPl0YHSRheH2O}/_buildManifest.js +0 -0
- /package/apps/pwa/out/_next/static/{LZcstaO9_NXypPFmujolv → yRA5ur6DcPl0YHSRheH2O}/_ssgManifest.js +0 -0
package/src/index.ts
CHANGED
|
@@ -27,10 +27,12 @@ import { installDevCompletionGuard } from './self-dev/dev-completion-guard.js';
|
|
|
27
27
|
import { buildOrchestrateDecomposePrepareArgs, normalizeOrchestrateRequest, splitOrchestrateGoalTexts } from './self-dev/self-orchestrate-runtime.js';
|
|
28
28
|
import { CLI_ENTRANCE_BASELINE, evaluateCommandEntranceBaseline } from './self-dev/entrance-baseline.js';
|
|
29
29
|
import { CLI_HARNESS_DOGFOOD_ENTRANCE, CLI_HARNESS_ORCHESTRATE_ENTRANCE, describeEntranceCommand, listEntrancesWithModelExposure, renderLaunchEntrances, summarizeEntrances, type EntranceId } from './self-dev/entrance-registry.js';
|
|
30
|
+
import { isOldDoorInternalCall, refuseOldDoor } from './self-dev/old-door.js';
|
|
30
31
|
import { collectCommandEntrances, renderCommandEntrances } from './self-dev/entrance-inventory.js';
|
|
31
32
|
import { isGoalAuthorFileName } from './self-implement/goal-document.js';
|
|
32
33
|
import { DEV_PIPELINE_SINK_SURFACE } from './self-implement/self-cli-sink-surface.js';
|
|
33
34
|
import { registerPrivateLedgerCommands } from './cli/private-ledger-commands.js';
|
|
35
|
+
import { registerKnowCommand } from './cli/know-cli.js';
|
|
34
36
|
import { registerSeatRequestsCommands } from './cli/seat-requests-cli.js';
|
|
35
37
|
import { applyTestStateDirFlagFromArgv } from './cli/test-state-dir-flag.js';
|
|
36
38
|
import { applyTestFlagFromArgv, observeTestFlagOwnership, uncoveredTestFlagPaths, staleTestFlagPaths } from './cli/test-flag.js';
|
|
@@ -53,6 +55,7 @@ export { runSchedule, scheduleCreatePlan } from './cli/schedule-cli.js';
|
|
|
53
55
|
export type { ScheduleDispatch } from './cli/schedule-cli.js';
|
|
54
56
|
import { registerReviewLoopOptions, buildReviewLoopOpts } from './agent-mission/review-loop-cli.js';
|
|
55
57
|
import { registerCapabilitiesCommand } from './agent-mission/capabilities-cli.js';
|
|
58
|
+
import { registerSupervisorMetricsCommand } from './agent-mission/supervisor-metrics-cli.js';
|
|
56
59
|
import { agentBackendNames } from './agent-mission/driver.js';
|
|
57
60
|
import { registerWhereCommand } from './cli/where-cli.js';
|
|
58
61
|
import { registerShadowCommand } from './cli/shadow-cli.js';
|
|
@@ -70,6 +73,7 @@ import { registerFlowCommands } from './cli/flow-cli.js';
|
|
|
70
73
|
import { registerModelWatchCommand } from './cli/model-watch-cli.js';
|
|
71
74
|
import { registerIntakeCommands } from './cli/intake-cli.js';
|
|
72
75
|
import { registerTasksCommands } from './cli/tasks-cli.js';
|
|
76
|
+
import { registerBriefCommands } from './cli/brief-cli.js';
|
|
73
77
|
import { registerLabelsCommands } from './cli/labels-cli.js';
|
|
74
78
|
import { registerMsgCommands } from './cli/msg-cli.js';
|
|
75
79
|
import { registerDoctorCommand } from './cli/doctor-cli.js';
|
|
@@ -255,7 +259,7 @@ interface RunDevAskFromGoalFileDeps {
|
|
|
255
259
|
runSayLaunchFlow: (
|
|
256
260
|
sayText: string,
|
|
257
261
|
entrance?: import('./self-dev/entrance-registry.js').EntranceDeclaration,
|
|
258
|
-
selection?: { readonly forcePreflight?: boolean; readonly goalType?: import('./self-implement/goal-author.js').GoalType; readonly groundingCwd?: string },
|
|
262
|
+
selection?: { readonly forcePreflight?: boolean; readonly goalType?: import('./self-implement/goal-author.js').GoalType; readonly groundingCwd?: string; readonly authorGrade?: import('./self-implement/goal-author.js').GoalAuthorGradeSelection },
|
|
259
263
|
) => Promise<RunDevAskFlowResult>;
|
|
260
264
|
runAskFileLaunchFlow: (
|
|
261
265
|
askPath: string,
|
|
@@ -290,7 +294,7 @@ export function assembleAskLaunchPolicy(selection: {
|
|
|
290
294
|
async function runDefaultSayLaunchFlow(
|
|
291
295
|
sayText: string,
|
|
292
296
|
entrance?: import('./self-dev/entrance-registry.js').EntranceDeclaration,
|
|
293
|
-
selection: { readonly forcePreflight?: boolean; readonly goalType?: import('./self-implement/goal-author.js').GoalType; readonly groundingCwd?: string } = {},
|
|
297
|
+
selection: { readonly forcePreflight?: boolean; readonly goalType?: import('./self-implement/goal-author.js').GoalType; readonly groundingCwd?: string; readonly authorGrade?: import('./self-implement/goal-author.js').GoalAuthorGradeSelection } = {},
|
|
294
298
|
): Promise<RunDevAskFlowResult> {
|
|
295
299
|
const { readFileSync, writeFileSync } = await import('node:fs');
|
|
296
300
|
const [flowMod, { runGoalAuthorCli }, { relative: relativeToCwdPath }, { CLI_HARNESS_SAY_ENTRANCE }] = await Promise.all([
|
|
@@ -315,6 +319,7 @@ async function runDefaultSayLaunchFlow(
|
|
|
315
319
|
...assembleAskLaunchPolicy(selection),
|
|
316
320
|
...(selection.goalType === undefined ? {} : { goalType: selection.goalType }),
|
|
317
321
|
...(selection.groundingCwd === undefined ? {} : { groundingCwd: selection.groundingCwd }),
|
|
322
|
+
...(selection.authorGrade === undefined ? {} : { authorGrade: selection.authorGrade }),
|
|
318
323
|
}, {
|
|
319
324
|
print: (line) => console.error(line),
|
|
320
325
|
log: (event, data, level) => debug.log('dev-pipeline', event, data, { level }),
|
|
@@ -324,6 +329,9 @@ async function runDefaultSayLaunchFlow(
|
|
|
324
329
|
cwd: () => process.cwd(),
|
|
325
330
|
now: () => Date.now(),
|
|
326
331
|
isInteractive: () => process.stdin.isTTY === true && process.stdout.isTTY === true,
|
|
332
|
+
// POD-SELF-ANSWER: non-TTY (Pod) authoring self-answers clarifications with the author resolver.
|
|
333
|
+
selfResolveClarification: (await import('./self-implement/goal-author-clarification.js')).defaultGoalAuthorSelfResolve,
|
|
334
|
+
writeGoalDocument: (await import('./self-implement/goal-document-write.js')).writeGoalDocumentAtomic,
|
|
327
335
|
buildPreflightDeps: askIo.buildAskPreflightDeps,
|
|
328
336
|
priorBlockSamples: () => askIo.priorBlockSamplesFrom(askLogRows),
|
|
329
337
|
recentAuthoringSamples: () => askIo.recentAuthoringSamplesFrom(askLogRows),
|
|
@@ -372,6 +380,9 @@ async function runDefaultAskFileLaunchFlow(
|
|
|
372
380
|
cwd: () => process.cwd(),
|
|
373
381
|
now: () => Date.now(),
|
|
374
382
|
isInteractive: () => process.stdin.isTTY === true && process.stdout.isTTY === true,
|
|
383
|
+
// POD-SELF-ANSWER: non-TTY (Pod) authoring self-answers clarifications with the author resolver.
|
|
384
|
+
selfResolveClarification: (await import('./self-implement/goal-author-clarification.js')).defaultGoalAuthorSelfResolve,
|
|
385
|
+
writeGoalDocument: (await import('./self-implement/goal-document-write.js')).writeGoalDocumentAtomic,
|
|
375
386
|
buildPreflightDeps: askIo.buildAskPreflightDeps,
|
|
376
387
|
priorBlockSamples: () => askIo.priorBlockSamplesFrom(askLogRows),
|
|
377
388
|
recentAuthoringSamples: () => askIo.recentAuthoringSamplesFrom(askLogRows),
|
|
@@ -814,6 +825,7 @@ async function runDevSayFromWords(
|
|
|
814
825
|
authoringEntrance,
|
|
815
826
|
{
|
|
816
827
|
...(opts.forcePreflight === undefined ? {} : { forcePreflight: opts.forcePreflight }),
|
|
828
|
+
...(opts.authorGrade === undefined ? {} : { authorGrade: { grade: opts.authorGrade, source: opts.authorGradeSource ?? 'flag' } }),
|
|
817
829
|
...(grounding.groundingCwd === undefined ? {} : { groundingCwd: grounding.groundingCwd }),
|
|
818
830
|
},
|
|
819
831
|
);
|
|
@@ -875,6 +887,7 @@ registerUsageCommand(program);
|
|
|
875
887
|
registerLiveCommands(program);
|
|
876
888
|
registerResearchCommand(program);
|
|
877
889
|
registerReleaseCommands(program);
|
|
890
|
+
registerKnowCommand(program);
|
|
878
891
|
registerFreezeCommands(program);
|
|
879
892
|
registerFlowCommands(program);
|
|
880
893
|
// HQ-HB · HQ-FENCE (10-04): one writer across mbp · node-b · cloud-vm — lease on the arbiter, 2-of-3 quorum, generation fencing.
|
|
@@ -893,24 +906,42 @@ hqCmd.command('host').description('이 머신의 HQ 호스트 이름 설정')
|
|
|
893
906
|
});
|
|
894
907
|
hqCmd.command('lease').argument('<action>', 'acquire|renew|status|release')
|
|
895
908
|
.option('--host <name>', '이 호스트 이름(기본 ~/.elanous-hq/host · hq.hostName · os.hostname)')
|
|
909
|
+
.option('--expected-holder <name>', 'release CAS: 해제할 임대의 예상 소유자')
|
|
910
|
+
.option('--expected-generation <number>', 'release CAS: 해제할 임대의 예상 세대')
|
|
896
911
|
.option('--json', 'JSON 출력')
|
|
897
|
-
.action(async (action: string, opts: { host?: string; json?: boolean }) => {
|
|
912
|
+
.action(async (action: string, opts: { host?: string; expectedHolder?: string; expectedGeneration?: string; json?: boolean }) => {
|
|
898
913
|
try {
|
|
899
914
|
if (!['acquire', 'renew', 'status', 'release'].includes(action)) throw new CliUserError(`hq lease: unknown action ${action}`, 'acquire|renew|status|release');
|
|
915
|
+
if ((opts.expectedHolder !== undefined || opts.expectedGeneration !== undefined) && action !== 'release')
|
|
916
|
+
throw new CliUserError('hq lease: expected holder/generation are only valid for release');
|
|
917
|
+
if ((opts.expectedHolder === undefined) !== (opts.expectedGeneration === undefined)
|
|
918
|
+
|| (opts.expectedGeneration !== undefined && (!/^[1-9]\d*$/.test(opts.expectedGeneration) || !Number.isSafeInteger(Number(opts.expectedGeneration)))))
|
|
919
|
+
throw new CliUserError('hq lease release: supply both --expected-holder and --expected-generation (positive integer)');
|
|
900
920
|
await hqSink();
|
|
901
921
|
const { hqLease } = await import('./hq/hq.js');
|
|
902
|
-
const result = hqLease(action as 'acquire', {},
|
|
922
|
+
const result = hqLease(action as 'acquire', {}, {
|
|
923
|
+
...(opts.host ? { host: opts.host } : {}),
|
|
924
|
+
...(opts.expectedHolder !== undefined ? { expectedHolder: opts.expectedHolder, expectedGeneration: Number(opts.expectedGeneration) } : {}),
|
|
925
|
+
});
|
|
903
926
|
if (opts.json) await writeStdoutJson(`${JSON.stringify(result)}\n`);
|
|
904
927
|
else console.log(result.ok ? `hq lease ${action}: ok${'record' in result && result.record ? ` · holder ${result.record.holder} · generation ${result.record.generation}` : ''}` : `hq lease ${action}: refused — ${'reason' in result ? result.reason : ''}`);
|
|
905
928
|
if (!result.ok) process.exitCode = 2;
|
|
906
929
|
} catch (error) { if (error instanceof CliUserError) throw error; console.error(`hq lease: ${error instanceof Error ? error.message : String(error)}`); process.exitCode = 1; }
|
|
907
930
|
});
|
|
908
931
|
hqCmd.command('promote').description('HQ 승격 런북 (기본 dry-run · --apply 일 때만 변경)')
|
|
909
|
-
.
|
|
910
|
-
.option('--apply', '
|
|
932
|
+
.option('--to <host>', '기존 임대 취득 후 승격 대상 호스트 (생략하면 mbp → node-b 전환 포함)')
|
|
933
|
+
.option('--apply', '임대 이전 및 승격 실행 · 터미널 확인 필수 (기존 --to 경로는 임대 보유 필요)')
|
|
911
934
|
.option('--json', 'JSON 출력')
|
|
912
935
|
.action(async (opts: { to: string; apply?: boolean; json?: boolean }) => {
|
|
913
936
|
try {
|
|
937
|
+
if (!opts.to) {
|
|
938
|
+
const { transferHq, formatTransfer } = await import('./hq/transfer.js');
|
|
939
|
+
const result = transferHq('promote', opts.apply === true);
|
|
940
|
+
if (opts.json) await writeStdoutJson(`${JSON.stringify(result)}\n`);
|
|
941
|
+
else console.log(formatTransfer(result));
|
|
942
|
+
if (!result.ok) process.exitCode = 1;
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
914
945
|
const { promoteHq, formatPromote } = await import('./hq/promote.js');
|
|
915
946
|
const result = promoteHq(opts.to, opts.apply === true);
|
|
916
947
|
if (opts.json) await writeStdoutJson(`${JSON.stringify(result)}\n`);
|
|
@@ -918,6 +949,18 @@ hqCmd.command('promote').description('HQ 승격 런북 (기본 dry-run · --appl
|
|
|
918
949
|
if (!result.ok) process.exitCode = 1;
|
|
919
950
|
} catch (error) { console.error(`hq promote: ${error instanceof Error ? error.message : String(error)}`); process.exitCode = 1; }
|
|
920
951
|
});
|
|
952
|
+
hqCmd.command('handback').description('node-b → mbp 임대·크론 울타리 복귀 (기본 읽기 전용)')
|
|
953
|
+
.option('--apply', '각 검사를 통과한 뒤 터미널 확인을 받고 이전 실행')
|
|
954
|
+
.option('--json', '단계별 결과·저널 경로 JSON 출력')
|
|
955
|
+
.action(async (opts: { apply?: boolean; json?: boolean }) => {
|
|
956
|
+
try {
|
|
957
|
+
const { transferHq, formatTransfer } = await import('./hq/transfer.js');
|
|
958
|
+
const result = transferHq('handback', opts.apply === true);
|
|
959
|
+
if (opts.json) await writeStdoutJson(`${JSON.stringify(result)}\n`);
|
|
960
|
+
else console.log(formatTransfer(result));
|
|
961
|
+
if (!result.ok) process.exitCode = 1;
|
|
962
|
+
} catch (error) { console.error(`hq handback: ${error instanceof Error ? error.message : String(error)}`); process.exitCode = 1; }
|
|
963
|
+
});
|
|
921
964
|
hqCmd.command('heartbeat').description('보유자는 갱신 · 아니면 대기(보유자에 닿는지 중재자에 보고)').option('--json', 'JSON 출력')
|
|
922
965
|
.action(async (opts: { json?: boolean }) => {
|
|
923
966
|
try {
|
|
@@ -942,12 +985,47 @@ hqCmd.command('seen').description('이 호스트가 본 가장 높은 임대 세
|
|
|
942
985
|
process.exitCode = result.seen ? 0 : 3;
|
|
943
986
|
});
|
|
944
987
|
registerHqMovePlanCommand(hqCmd);
|
|
988
|
+
const fenceWrapperCmd = hqCmd.command('fence-wrapper').description('설치본 HQ 울타리 크론 래퍼');
|
|
989
|
+
fenceWrapperCmd.command('install').description('래퍼 제안 또는 백업 후 원자적 설치')
|
|
990
|
+
.option('--dry-run', '변경 내용만 출력 (기본)')
|
|
991
|
+
.option('--yes', '기존 파일 백업 후 설치')
|
|
992
|
+
.action(async (opts: { dryRun?: boolean; yes?: boolean }) => {
|
|
993
|
+
try {
|
|
994
|
+
if (opts.dryRun && opts.yes) throw new Error('--dry-run and --yes cannot be used together');
|
|
995
|
+
const { getElanousConfigDir } = await import('./elanous-config-dir.js');
|
|
996
|
+
const { installedHqFenceEntry, installHqFenceWrapper, renderHqFenceWrapper, resolveHqFenceBun } = await import('./hq/fence-wrapper.js');
|
|
997
|
+
const configDir = getElanousConfigDir();
|
|
998
|
+
const bun = resolveHqFenceBun();
|
|
999
|
+
const body = renderHqFenceWrapper({ entry: installedHqFenceEntry(), configDir, bun });
|
|
1000
|
+
const result = installHqFenceWrapper(configDir, body, opts.yes === true);
|
|
1001
|
+
if (opts.yes) console.log(`hq-fence installed: ${result.path} · bun: ${bun}${result.backup ? ` · backup: ${result.backup}` : ''}`);
|
|
1002
|
+
else console.log(`hq-fence bun (absolute · cron has no PATH): ${bun}\n${result.preview}`);
|
|
1003
|
+
} catch (error) { console.error(`hq fence-wrapper install: ${error instanceof Error ? error.message : String(error)}`); process.exitCode = 1; }
|
|
1004
|
+
});
|
|
1005
|
+
fenceWrapperCmd.command('alert <reason>').description('HQ 울타리 실패를 기존 루프 소유자 요청 원장에 적재')
|
|
1006
|
+
.action(async (reason: string) => {
|
|
1007
|
+
try {
|
|
1008
|
+
const { notifyOwners } = await import('./loops/checker.js');
|
|
1009
|
+
const { getElanousConfigDir } = await import('./elanous-config-dir.js');
|
|
1010
|
+
notifyOwners([{ id: `hq-fence:${Date.now()}:${process.pid}`, owner: 'TC', enabled: true, registered: true,
|
|
1011
|
+
state: 'failing', reason }], { root: getElanousConfigDir() });
|
|
1012
|
+
} catch (error) { console.error(`hq fence-wrapper alert: ${error instanceof Error ? error.message : String(error)}`); process.exitCode = 1; }
|
|
1013
|
+
});
|
|
945
1014
|
hqCmd.command('fence-audit').description('운영 crontab 의 HQ 울타리 밖 쓰기 잡 목록과 감싼 줄 제안(읽기 전용)')
|
|
946
1015
|
.option('--json', 'JSON 출력')
|
|
947
1016
|
.action(async (opts: { json?: boolean }) => {
|
|
948
1017
|
try {
|
|
949
1018
|
await hqSink();
|
|
950
1019
|
const rows = (await import('./hq/fence-audit.js')).hqFenceAudit();
|
|
1020
|
+
const { getElanousConfigDir } = await import('./elanous-config-dir.js');
|
|
1021
|
+
const { repositoryWorktreeCdWarning } = await import('./hq/fence-wrapper.js');
|
|
1022
|
+
const wrapper = _joinPath(getElanousConfigDir(), 'bin', 'hq-fence');
|
|
1023
|
+
if (existsSync(wrapper)) {
|
|
1024
|
+
try {
|
|
1025
|
+
const warning = repositoryWorktreeCdWarning(readFileSync(wrapper, 'utf8'));
|
|
1026
|
+
if (warning) console.error(warning);
|
|
1027
|
+
} catch (error) { console.error(`hq fence-audit: wrapper inspection unavailable: ${error instanceof Error ? error.message : String(error)}`); }
|
|
1028
|
+
}
|
|
951
1029
|
if (opts.json) await writeStdoutJson(`${JSON.stringify(rows)}\n`);
|
|
952
1030
|
else if (!rows.length) console.log('hq fence-audit: unfenced 0');
|
|
953
1031
|
else for (const row of rows) console.log(`${row.raw}\n role: ${row.recommendedRole}\n suggested: ${row.suggestedLine}`);
|
|
@@ -967,9 +1045,59 @@ registerSeatRequestsCommands(seatCmd);
|
|
|
967
1045
|
seatCmd.command('answer <askId> <answer>').description('자리 질문에 답을 남기고 원래 표면으로 회신 예약')
|
|
968
1046
|
.action(async (askId: string, answer: string) => {
|
|
969
1047
|
try {
|
|
970
|
-
|
|
1048
|
+
try { await (await import('./domains/standalone-log-sink.js')).registerStandaloneLogSink('seat-answer'); } catch { /* file trail remains available */ }
|
|
1049
|
+
const { debug } = await import('./debug/log.js');
|
|
1050
|
+
const { answerSeatAsk, deliverSeatAnswers, seatAskDeliveryInProgress, seatAskReplyInfo } = await import('./seat-dispatch/seat-ask.js');
|
|
971
1051
|
answerSeatAsk(askId, answer);
|
|
1052
|
+
const { origin, askedAt, answeredAt } = seatAskReplyInfo(askId);
|
|
972
1053
|
console.log(`자리 답변 기록: ${askId}`);
|
|
1054
|
+
if (origin.channel !== 'telegram') {
|
|
1055
|
+
debug.log('seat.ask', 'reply-no-target', { id: askId, via: origin.channel });
|
|
1056
|
+
console.log(`전달 대상 없음: ${askId} (${origin.channel})`);
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1059
|
+
const token = process.env.TELEGRAM_BOT_TOKEN?.trim();
|
|
1060
|
+
const elapsedMs = askedAt === null ? null : Math.max(0, answeredAt - askedAt);
|
|
1061
|
+
if (!token) {
|
|
1062
|
+
debug.log('seat.ask', 'reply-failed', { id: askId, via: 'telegram', reason: 'token-missing', elapsedMs });
|
|
1063
|
+
console.log(`텔레그램 토큰 없음 (TELEGRAM_BOT_TOKEN): ${askId} · 회신 대기`);
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
const botId = token.split(':')[0];
|
|
1067
|
+
if (origin.botId && origin.botId !== botId) {
|
|
1068
|
+
debug.log('seat.ask', 'reply-no-target', { id: askId, via: 'telegram', reason: 'bot-mismatch' });
|
|
1069
|
+
console.log(`전달 대상 없음: ${askId} (수신 봇 불일치) · 회신 대기`);
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
try {
|
|
1073
|
+
let sent = false;
|
|
1074
|
+
await deliverSeatAnswers({ askId, channel: 'telegram', botId, send: async (target, text) => {
|
|
1075
|
+
if (target.channel !== 'telegram') throw new Error('seat ask belongs to another surface');
|
|
1076
|
+
const response = await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
|
|
1077
|
+
method: 'POST', headers: { 'content-type': 'application/json' },
|
|
1078
|
+
body: JSON.stringify({ chat_id: target.chatId, text, reply_to_message_id: target.messageId,
|
|
1079
|
+
...(target.threadId === undefined ? {} : { message_thread_id: target.threadId }) }),
|
|
1080
|
+
});
|
|
1081
|
+
const result = await response.json() as { ok?: boolean; description?: string };
|
|
1082
|
+
if (!response.ok || result.ok !== true) throw new Error(result.description ?? `Telegram HTTP ${response.status}`);
|
|
1083
|
+
sent = true;
|
|
1084
|
+
} });
|
|
1085
|
+
if (!sent) {
|
|
1086
|
+
if (seatAskDeliveryInProgress(askId)) {
|
|
1087
|
+
debug.log('seat.ask', 'reply-in-progress', { id: askId, via: 'telegram', reason: 'claimed-by-other-worker' });
|
|
1088
|
+
console.log(`다른 작업자가 전달 중: ${askId}`);
|
|
1089
|
+
} else {
|
|
1090
|
+
debug.log('seat.ask', 'reply-no-target', { id: askId, via: 'telegram', reason: 'not-claimed' });
|
|
1091
|
+
console.log(`전달 대상 없음: ${askId} (회신 대기)`);
|
|
1092
|
+
}
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
debug.log('seat.ask', 'reply-sent', { id: askId, via: 'telegram', elapsedMs });
|
|
1096
|
+
console.log(`텔레그램 회신 전달: ${askId} · 지연 ${elapsedMs === null ? '미상' : `${elapsedMs}ms`}`);
|
|
1097
|
+
} catch (error) {
|
|
1098
|
+
debug.log('seat.ask', 'reply-failed', { id: askId, via: 'telegram', reason: error instanceof Error ? error.message : String(error), elapsedMs });
|
|
1099
|
+
console.log(`텔레그램 회신 실패: ${askId} · 회신 대기`);
|
|
1100
|
+
}
|
|
973
1101
|
} catch (error) { console.error(`seat answer: ${error instanceof Error ? error.message : String(error)}`); process.exitCode = 1; }
|
|
974
1102
|
});
|
|
975
1103
|
seatCmd.command('loop').requiredOption('--seat <SEAT>', 'MK|OP|TC|UX')
|
|
@@ -1170,7 +1298,7 @@ pythonCmd.command('setup').description('elanous venv(~/.local/share/elanous/pyth
|
|
|
1170
1298
|
|
|
1171
1299
|
program.command('self-update')
|
|
1172
1300
|
.alias('update')
|
|
1173
|
-
.description('설치본은 릴리스로, 체크아웃은 깨끗한 체크아웃으로
|
|
1301
|
+
.description('설치본은 릴리스로, 체크아웃은 깨끗한 체크아웃으로 갱신 · --auto 는 캐너리 자동 판단, --auto on|off|status 는 기존 자동 갱신 서비스 관리')
|
|
1174
1302
|
.option('--from <checkout>', '설치할 체크아웃 (기본: 설치본은 릴리스, 체크아웃은 현재 체크아웃)')
|
|
1175
1303
|
.option('--version <version>', '설치본에서 지정한 릴리스 버전 설치 (기본: latest)')
|
|
1176
1304
|
.option('--restart', '넥서스 재시작 승인')
|
|
@@ -1178,9 +1306,15 @@ program.command('self-update')
|
|
|
1178
1306
|
.option('--keep <n>', '설치 뒤 남길 최근 판 수(설치본은 current·직전 판, 체크아웃은 current·데몬 판 보호 · 0 이면 정리 안 함)', '3')
|
|
1179
1307
|
.option('--alert', '실패(exit≠0)를 알림으로도 보냄 — 크론(무인) 실행용')
|
|
1180
1308
|
.option('--skip-pwa-build', '체크아웃 설치 전 PWA 빌드를 명시적으로 건너뜀')
|
|
1181
|
-
.option('--
|
|
1182
|
-
.
|
|
1183
|
-
|
|
1309
|
+
.option('--dev', '판 사이 dev 빌드 설치 — versions/<판>-dev.<sha12> · install.json channel=dev · 이전 versionDir 보존 (사람 승인 · 자동 아님)')
|
|
1310
|
+
.option('--rollback', 'install.json 의 이전 versionDir 로 current 를 되돌린다 (이전이 없으면 거부)')
|
|
1311
|
+
.option('--auto [on|off|status]', '인자 없이: 에지 레일 계획/실행 · on|off|status: 기존 자동 갱신 서비스 관리')
|
|
1312
|
+
.option('--dry-run', '에지 레일 계획만 출력; 설치·되돌림·상태 쓰기 없음')
|
|
1313
|
+
.option('--seat <seat>', '에지 레일 자리 (OP|TC|MK|UX)')
|
|
1314
|
+
.option('--quiet-window', '상주 프로세스 재시작이 가능한 조용한 창이라고 명시')
|
|
1315
|
+
.option('--canary-run-id <id>', '캐너리 성공 런의 고유 ID (독립 검증된 성공 런에서만 입력; 같은 ID 는 중복 적중하지 않음)')
|
|
1316
|
+
.action(async (opts: { from?: string; version?: string; restart?: boolean; json?: boolean; keep?: string; alert?: boolean; skipPwaBuild?: boolean; dev?: boolean; rollback?: boolean; auto?: string | boolean; dryRun?: boolean; seat?: string; quietWindow?: boolean; canaryRunId?: string }) => {
|
|
1317
|
+
if (typeof opts.auto === 'string') {
|
|
1184
1318
|
if (!['on', 'off', 'status'].includes(opts.auto)) { console.error(`--auto 는 on · off · status 중 하나: ${opts.auto}`); process.exitCode = 2; return; }
|
|
1185
1319
|
const { runAutoUpdate } = await import('./cli/update-auto.js');
|
|
1186
1320
|
process.exitCode = (await runAutoUpdate(opts.auto as 'on' | 'off' | 'status')).exitCode;
|
|
@@ -1188,9 +1322,9 @@ program.command('self-update')
|
|
|
1188
1322
|
}
|
|
1189
1323
|
const { runUpdateForInstallation } = await import('./cli/self-update.js');
|
|
1190
1324
|
// ⭐ 한 번 도는 CLI 는 logs.db 싱크를 스스로 붙여야 `debug.log('self-update', …)` 가 저장된다(없으면 조용히 사라진다 · 09-24 실측).
|
|
1191
|
-
try { const { registerStandaloneLogSink } = await import('./domains/standalone-log-sink.js'); await registerStandaloneLogSink('self-update'); } catch { /* 관측 실패가 갱신을 막지 않는다 */ }
|
|
1325
|
+
if (!opts.dryRun) try { const { registerStandaloneLogSink } = await import('./domains/standalone-log-sink.js'); await registerStandaloneLogSink('self-update'); } catch { /* 관측 실패가 갱신을 막지 않는다 */ }
|
|
1192
1326
|
const keep = Number.parseInt(opts.keep ?? '3', 10);
|
|
1193
|
-
const result = await runUpdateForInstallation({ ...opts, skipPwaBuild: opts.skipPwaBuild, keep: Number.isFinite(keep) ? keep : 3 }, { cliRoot: REPOSITORY_ROOT });
|
|
1327
|
+
const result = await runUpdateForInstallation({ ...opts, auto: opts.auto === true, quietWindow: opts.quietWindow === true, skipPwaBuild: opts.skipPwaBuild, keep: Number.isFinite(keep) ? keep : 3 }, { cliRoot: REPOSITORY_ROOT });
|
|
1194
1328
|
process.exitCode = result.exitCode;
|
|
1195
1329
|
});
|
|
1196
1330
|
|
|
@@ -4259,6 +4393,12 @@ selfCmd
|
|
|
4259
4393
|
.option('--supervise-rounds <n>', '슈퍼바이저 재개 라운드 상한 (기본 3 · 끄려면 --no-supervise)')
|
|
4260
4394
|
.option('--json', '구조화 출력 {stage, ok, branch, worktree, prUrl?, merged?}')
|
|
4261
4395
|
.action(async (parts: string[], opts: { featureFile?: string; base?: string; draft?: boolean; openPr?: boolean; autoMerge?: boolean; autoReview?: boolean; maxWait?: string; json?: boolean; plan?: boolean; enhance?: boolean; ground?: boolean; observeOnly?: boolean }) => {
|
|
4396
|
+
if (!isOldDoorInternalCall('self-implement')) {
|
|
4397
|
+
const refused = refuseOldDoor('self-implement');
|
|
4398
|
+
debug.log('harness.entrance', 'old-door-refused', { door: refused.door, caller: refused.caller });
|
|
4399
|
+
ui.error(refused.message);
|
|
4400
|
+
process.exit(refused.exitCode);
|
|
4401
|
+
}
|
|
4262
4402
|
if (opts.featureFile && parts.length > 0) { ui.error('--feature-file 과 위치 인자 feature 는 함께 줄 수 없다'); process.exit(2); }
|
|
4263
4403
|
let feature: string;
|
|
4264
4404
|
try { feature = (opts.featureFile ? readFileSync(opts.featureFile, 'utf8') : parts.join(' ')).trim(); }
|
|
@@ -4418,6 +4558,12 @@ const selfOrchestrateCmd = selfCmd
|
|
|
4418
4558
|
.option('--help-all', '모든 orchestrate 옵션 표시')
|
|
4419
4559
|
.action(function (this: Command, parts: string[], opts: { goalFile?: string[]; concurrency?: string; autoMerge?: boolean; autoReview?: boolean; openPr?: boolean; base?: string; teardown?: boolean; resume?: string; board?: boolean; decompose?: boolean; fabricDecompose?: boolean; maxTasks?: string; supervise?: boolean; superviseRounds?: string; json?: boolean }) {
|
|
4420
4560
|
return (async () => {
|
|
4561
|
+
if (!isOldDoorInternalCall('self-orchestrate')) {
|
|
4562
|
+
const refused = refuseOldDoor('self-orchestrate');
|
|
4563
|
+
debug.log('harness.entrance', 'old-door-refused', { door: refused.door, caller: refused.caller });
|
|
4564
|
+
ui.error(refused.message);
|
|
4565
|
+
process.exit(refused.exitCode);
|
|
4566
|
+
}
|
|
4421
4567
|
// 위치 인자는 종전처럼 분리하고 파일 입력은 파일당 한 골이다.
|
|
4422
4568
|
let inputGoals: ReturnType<typeof readSelfOrchestrateGoals>;
|
|
4423
4569
|
try { inputGoals = readSelfOrchestrateGoals(parts, opts.goalFile); }
|
|
@@ -5943,6 +6089,7 @@ agentCmd.hook('preAction', async (_thisCommand, actionCommand) => {
|
|
|
5943
6089
|
});
|
|
5944
6090
|
|
|
5945
6091
|
registerCapabilitiesCommand(agentCmd);
|
|
6092
|
+
registerSupervisorMetricsCommand(agentCmd);
|
|
5946
6093
|
|
|
5947
6094
|
agentCmd.command('install-from-docs <tool>')
|
|
5948
6095
|
.description('공식 설치 문서를 읽고 안전한 설치 줄을 선택한다 — sudo는 사람에게 넘긴다')
|
|
@@ -6814,6 +6961,11 @@ const selfDevCmd = program
|
|
|
6814
6961
|
// ⭐ `--ask`/`--say` — 발사 절차 넷을 «한 로직»으로. 저작 → 전제 검사 → 발사.
|
|
6815
6962
|
// ⛔ 여기서 저작한 골 파일을 아래 `--file` 자리에 그대로 넘긴다(새 실행 경로를 만들지 않는다).
|
|
6816
6963
|
let askAuthoredFile: string | undefined;
|
|
6964
|
+
if (opts.ask !== undefined && !isOldDoorInternalCall('dev-ask')) {
|
|
6965
|
+
const refused = refuseOldDoor('dev-ask');
|
|
6966
|
+
debug.log('harness.entrance', 'old-door-refused', { door: refused.door, caller: refused.caller });
|
|
6967
|
+
throw new DevPipelineError(refused.message);
|
|
6968
|
+
}
|
|
6817
6969
|
const retirementInput = opts.ask !== undefined
|
|
6818
6970
|
? { kind: 'ask' as const, value: opts.ask }
|
|
6819
6971
|
: opts.say !== undefined
|
|
@@ -6896,6 +7048,8 @@ const selfDevCmd = program
|
|
|
6896
7048
|
now: () => Date.now(),
|
|
6897
7049
|
// ⛔ 「대화형인가」는 «두 쪽»을 본다 — 파이프로 몰면 물어도 답이 안 온다.
|
|
6898
7050
|
isInteractive: () => process.stdin.isTTY === true && process.stdout.isTTY === true,
|
|
7051
|
+
selfResolveClarification: (await import('./self-implement/goal-author-clarification.js')).defaultGoalAuthorSelfResolve,
|
|
7052
|
+
writeGoalDocument: (await import('./self-implement/goal-document-write.js')).writeGoalDocumentAtomic,
|
|
6899
7053
|
buildPreflightDeps: buildAskPreflightDeps,
|
|
6900
7054
|
priorBlockSamples: () => askIo.priorBlockSamplesFrom(askLogRows),
|
|
6901
7055
|
recentAuthoringSamples: () => askIo.recentAuthoringSamplesFrom(askLogRows),
|
|
@@ -7538,6 +7692,7 @@ missionCmd
|
|
|
7538
7692
|
});
|
|
7539
7693
|
|
|
7540
7694
|
registerTasksCommands(program);
|
|
7695
|
+
registerBriefCommands(program);
|
|
7541
7696
|
registerLabelsCommands(program);
|
|
7542
7697
|
registerMsgCommands(program);
|
|
7543
7698
|
|
|
@@ -6,8 +6,9 @@ import { loadIntakeLedger, type IntakeItem } from './items.js';
|
|
|
6
6
|
import { readSavedCursorAt } from './collect-telegram-saved.js';
|
|
7
7
|
import { intakeOutboxDir, kstDay } from './route.js';
|
|
8
8
|
|
|
9
|
-
export
|
|
10
|
-
export interface
|
|
9
|
+
export type DigestLensVerdict = '대체 후보' | '보강' | '경쟁 대조';
|
|
10
|
+
export interface DigestEntry { id: string; sources: string[]; url?: string; note?: string; noteName?: string; oneLiner?: string; axis: string; impact?: { verdict: DigestLensVerdict; why: string; target: string } }
|
|
11
|
+
export interface IntakeDigest { day: string; absorbed: DigestEntry[]; goals: { fact: string; url?: string }[]; news?: { title: string; url: string; summary: string[]; implication: string[] }[]; shadowSuggestions?: { fact: string; url?: string; verdict: string }[]; review?: { fact: string; note?: string }[]; grounding: number; release: number; manual: number; savedSilence?: { days: number; lastNewAt: string } }
|
|
11
12
|
|
|
12
13
|
const AXIS_LABEL: Record<string, string> = {
|
|
13
14
|
video_automation: '영상 자동화', agent_basics: '에이전트 기본', agent_applied: '에이전트 응용', unrelated: '그 밖',
|
|
@@ -40,27 +41,32 @@ function readJsonl(file: string): Record<string, unknown>[] {
|
|
|
40
41
|
/** Saved Messages silent for more than two days → the digest says so (a quiet 0 must not look like «nothing to do»). */
|
|
41
42
|
const SAVED_SILENCE_MS = 48 * 3600_000;
|
|
42
43
|
|
|
44
|
+
/** A «why» that only names paths (optionally behind a label like «경로:») is evidence, not a reason for us. */
|
|
45
|
+
export function pathOnly(why: string): boolean {
|
|
46
|
+
const rest = why
|
|
47
|
+
.replace(/`?(?:[\w.-]+\/)*[\w.-]+\.[A-Za-z0-9]{1,6}(?::\d+)?`?/g, ' ')
|
|
48
|
+
.replace(/`?(?:[\w.-]+\/)+[\w.-]*`?/g, ' ')
|
|
49
|
+
.replace(/(?:경로|파일|근거|path|file|evidence)\s*[::]?/gi, ' ')
|
|
50
|
+
.replace(/[\s,·:;()[\]\-–—]+/g, '');
|
|
51
|
+
return rest.length < 4;
|
|
52
|
+
}
|
|
53
|
+
|
|
43
54
|
export function buildIntakeDigest(root: string, day: string, readFile: (p: string) => string | undefined = (p) => (existsSync(p) ? readFileSync(p, 'utf8') : undefined), now: Date = new Date()): IntakeDigest {
|
|
44
55
|
const items = [...loadIntakeLedger(root).items.values()]
|
|
45
56
|
.filter((i) => (i.status === 'absorbed' || i.status === 'routed') && kstDay(i.lastSeenAt) === day && i.outputs.some((o) => o.kind === 'note'));
|
|
46
57
|
const out = intakeOutboxDir(root);
|
|
47
58
|
const relevant = new Map<string, NonNullable<DigestEntry['impact']>>();
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
['manual', '문서·약속을 현재 동작에 맞게 보강한다'],
|
|
51
|
-
['review', '대조 근거를 추가로 확인한다'],
|
|
52
|
-
['release', '이미 착지한 기능의 바깥 맥락을 릴리스 노트에 반영한다'],
|
|
53
|
-
] as const) {
|
|
59
|
+
// The lens (src/intake-plane/lens.ts) writes its verdicts to outbox/lens/<day>.jsonl; route folders carry none.
|
|
60
|
+
for (const folder of ['lens'] as const) {
|
|
54
61
|
for (const row of readJsonl(join(out, folder, `${day}.jsonl`))) {
|
|
55
62
|
if (typeof row.id !== 'string' || relevant.has(row.id)) continue;
|
|
56
|
-
const
|
|
57
|
-
if (
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
});
|
|
63
|
+
const verdict = row.lensVerdict;
|
|
64
|
+
if (verdict !== '대체 후보' && verdict !== '보강' && verdict !== '경쟁 대조') continue;
|
|
65
|
+
const why = typeof row.why === 'string' ? row.why.trim() : '';
|
|
66
|
+
const target = typeof row.target === 'string' ? row.target.trim() : '';
|
|
67
|
+
// A check's fact/current or repo path is evidence, not a lens judgement or a reason for us.
|
|
68
|
+
if (!why || !target || pathOnly(why)) continue;
|
|
69
|
+
relevant.set(row.id, { verdict, why, target });
|
|
64
70
|
}
|
|
65
71
|
}
|
|
66
72
|
const absorbed = items.map((i: IntakeItem): DigestEntry => {
|
|
@@ -76,12 +82,24 @@ export function buildIntakeDigest(root: string, day: string, readFile: (p: strin
|
|
|
76
82
|
};
|
|
77
83
|
});
|
|
78
84
|
const goals = readJsonl(join(out, 'goals', `${day}.jsonl`)).map((g) => ({ fact: String(g.fact ?? ''), ...(g.url ? { url: String(g.url) } : {}) }));
|
|
85
|
+
const lensRows = readJsonl(join(out, 'lens', `${day}.jsonl`));
|
|
86
|
+
const news = lensRows.flatMap((row) =>
|
|
87
|
+
typeof row.title === 'string' && typeof row.url === 'string' && Array.isArray(row.summary) && Array.isArray(row.implication)
|
|
88
|
+
? [{ title: row.title, url: row.url, summary: row.summary.filter((s): s is string => typeof s === 'string'), implication: row.implication.filter((s): s is string => typeof s === 'string') }]
|
|
89
|
+
: []);
|
|
90
|
+
const shadowSuggestions = lensRows.flatMap((row) =>
|
|
91
|
+
Array.isArray(row.suggestions) ? row.suggestions.flatMap((raw: unknown) => {
|
|
92
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return [];
|
|
93
|
+
const suggestion = raw as Record<string, unknown>;
|
|
94
|
+
if (suggestion.status !== '제안' || typeof suggestion.fact !== 'string' || !suggestion.fact.trim()) return [];
|
|
95
|
+
return [{ fact: suggestion.fact, ...(typeof row.url === 'string' ? { url: row.url } : {}), verdict: typeof suggestion.verdict === 'string' ? suggestion.verdict : '판단 필요' }];
|
|
96
|
+
}) : []);
|
|
79
97
|
const review = readJsonl(join(out, 'review', `${day}.jsonl`)).map((r) => ({ fact: String(r.fact ?? ''), ...(r.note ? { note: String(r.note) } : {}) }));
|
|
80
98
|
const grounding = readJsonl(join(out, 'grounding.jsonl')).filter((g) => typeof g.at === 'string' && kstDay(g.at) === day).length;
|
|
81
99
|
const lastNewAt = readSavedCursorAt(root);
|
|
82
100
|
const silentMs = lastNewAt ? now.getTime() - Date.parse(lastNewAt) : NaN;
|
|
83
101
|
return {
|
|
84
|
-
day, absorbed, goals, review, grounding,
|
|
102
|
+
day, absorbed, goals, ...(news.length ? { news } : {}), ...(shadowSuggestions.length ? { shadowSuggestions } : {}), review, grounding,
|
|
85
103
|
release: readJsonl(join(out, 'release', `${day}.jsonl`)).length,
|
|
86
104
|
manual: readJsonl(join(out, 'manual', `${day}.jsonl`)).length,
|
|
87
105
|
...(lastNewAt && silentMs > SAVED_SILENCE_MS ? { savedSilence: { days: Math.floor(silentMs / 86_400_000), lastNewAt } } : {}),
|
|
@@ -92,7 +110,8 @@ export function buildIntakeDigest(root: string, day: string, readFile: (p: strin
|
|
|
92
110
|
export function renderDigestMarkdown(d: IntakeDigest): string {
|
|
93
111
|
const L: string[] = [`## 📰 오늘의 흡수 요약 (${d.absorbed.length})`, ''];
|
|
94
112
|
if (d.savedSilence) L.push(`> ⚠️ 텔레그램 «저장된 메시지»에서 새 글을 ${d.savedSilence.days}일째 못 받았다(마지막 새 글 수집 ${kstDay(d.savedSilence.lastNewAt)}) — 저장했는데 안 들어왔다면 저장한 곳(본인 «저장된 메시지»인지)을 확인한다.`, '');
|
|
95
|
-
if (!d.absorbed.length)
|
|
113
|
+
if (!d.absorbed.length) L.push('오늘 흡수한 것이 없다.', '');
|
|
114
|
+
if (!d.absorbed.length && !d.news?.length) return L.join('\n');
|
|
96
115
|
const byAxis = new Map<string, DigestEntry[]>();
|
|
97
116
|
for (const e of d.absorbed) (byAxis.get(e.axis) ?? byAxis.set(e.axis, []).get(e.axis)!).push(e);
|
|
98
117
|
for (const [axis, rows] of byAxis) {
|
|
@@ -100,34 +119,72 @@ export function renderDigestMarkdown(d: IntakeDigest): string {
|
|
|
100
119
|
for (const e of rows) L.push(`- ${e.noteName ? `[[${e.noteName}]]` : e.url ?? e.id} — ${e.oneLiner ?? '(요약 줄 없음)'}`);
|
|
101
120
|
L.push('');
|
|
102
121
|
}
|
|
122
|
+
if (d.news?.length) {
|
|
123
|
+
L.push(`### 📰 관심 뉴스 (${d.news.length})`, '');
|
|
124
|
+
for (const article of d.news.slice(0, 10)) {
|
|
125
|
+
L.push(`- [${article.title}](${article.url})`);
|
|
126
|
+
for (const line of article.summary) L.push(` - ${line}`);
|
|
127
|
+
for (const line of article.implication) L.push(` - 엘라누스 함의: ${line}`);
|
|
128
|
+
}
|
|
129
|
+
L.push('');
|
|
130
|
+
}
|
|
103
131
|
if (d.goals.length) {
|
|
104
132
|
L.push(`### 🔴 엘라누스에 없는 것 — 골 후보 (${d.goals.length})`, '');
|
|
105
133
|
for (const g of d.goals.slice(0, 10)) L.push(`- ${g.fact}${g.url ? ` · [원본](${g.url})` : ''}`);
|
|
106
134
|
L.push('');
|
|
107
135
|
}
|
|
108
|
-
if (d.
|
|
109
|
-
L.push(`###
|
|
110
|
-
for (const
|
|
111
|
-
if (d.review.length > 10) L.push(`- … 외 ${d.review.length - 10}건`);
|
|
136
|
+
if (d.shadowSuggestions?.length) {
|
|
137
|
+
L.push(`### 📰 뉴스 칸 제안 — 그림자, 판 미등록 (${d.shadowSuggestions.length})`, '');
|
|
138
|
+
for (const s of d.shadowSuggestions.slice(0, 10)) L.push(`- [${s.verdict}] ${s.fact}${s.url ? ` · [원본](${s.url})` : ''}`);
|
|
112
139
|
L.push('');
|
|
113
140
|
}
|
|
141
|
+
const undecided = d.absorbed.filter((e) => !e.impact).length;
|
|
142
|
+
if (undecided) L.push(`렌즈 판정 못 함 ${undecided} — 원장 \`elanous intake items\``, '');
|
|
114
143
|
L.push(`> 매뉴얼 후보 ${d.manual} · 릴리스 노트 맥락 ${d.release} · 그라운딩 후보 ${d.grounding} — 흡수 원장 \`elanous intake items\` · 큐 \`<인스턴스>/intake/outbox/\``, '');
|
|
115
144
|
return L.join('\n');
|
|
116
145
|
}
|
|
117
146
|
|
|
118
147
|
/** 텔레그램 — 흡수 렌즈가 대조한 항목만 SCQA 짧은 판으로 낸다. */
|
|
119
148
|
export function renderDigestTelegram(d: IntakeDigest, _opts: { vaultRoot?: string; notePath?: string } = {}): string {
|
|
149
|
+
// «Touching» is decided by the lens verdict alone; a missing note summary only changes how S reads (ACP must-fix).
|
|
120
150
|
const touching = d.absorbed.filter((e) => e.impact);
|
|
121
151
|
const L = [`흡수 ${d.absorbed.length} → 우리에게 닿는 것 ${touching.length}`];
|
|
122
|
-
|
|
123
|
-
for (const
|
|
152
|
+
// 관심 뉴스는 최대 셋 — 기사마다 S(요약 첫 줄) · A(엘라누스 함의 첫 줄) · 링크를 한 덩어리로(NEWS-INTAKE).
|
|
153
|
+
for (const article of (d.news ?? []).slice(0, 3)) {
|
|
154
|
+
L.push(`📰 ${article.title}`);
|
|
155
|
+
if (article.summary[0]) L.push(`S: ${article.summary[0]}`);
|
|
156
|
+
if (article.implication[0]) L.push(`A: ${article.implication[0]}`);
|
|
157
|
+
L.push(article.url);
|
|
158
|
+
}
|
|
159
|
+
if (d.shadowSuggestions?.length) L.push(`뉴스 칸 제안 ${d.shadowSuggestions.length}건 (그림자·판 미등록): ${d.shadowSuggestions.slice(0, 3).map((s) => `[${s.verdict}] ${s.fact}${s.url ? ` ${s.url}` : ''}`).join(' · ')}`);
|
|
160
|
+
if (!touching.length) return L.join('\n');
|
|
161
|
+
// At most three items are shown, each as its own S·C·A·link block so context and source stay paired;
|
|
162
|
+
// a later item sharing (verdict, target) says «위와 같은 행동» instead of repeating the A sentence (ACP must-fix).
|
|
163
|
+
// S must be the note's own summary: items without one still count as touching but are not shown (ACP must-fix).
|
|
164
|
+
const shownItems = touching.filter((e) => e.oneLiner?.trim()).slice(0, 3);
|
|
165
|
+
const actionSeen = new Set<string>();
|
|
166
|
+
const actionsUsed = new Set<string>();
|
|
167
|
+
for (const e of shownItems) {
|
|
168
|
+
const impact = e.impact!;
|
|
169
|
+
const key = `${impact.verdict}\u0000${impact.target}`;
|
|
170
|
+
const base = impact.verdict === '대체 후보' ? `${impact.target} lite Pod 실증 제안`
|
|
171
|
+
: impact.verdict === '보강' ? `칸 ${impact.target} 에 근거 추가`
|
|
172
|
+
: `${impact.target} 비교표 갱신`;
|
|
173
|
+
// Every item keeps its own concrete action; a repeat of (verdict, target) names which item it is, so no two A lines match.
|
|
174
|
+
const label = (e.oneLiner?.trim() || impact.why).slice(0, 24);
|
|
175
|
+
let action = actionSeen.has(key) ? `${base} — «${label}» 근거로` : base;
|
|
176
|
+
// Two identical labels still differ by their position in the list.
|
|
177
|
+
if (actionsUsed.has(action)) action = `${action} (${shownItems.indexOf(e) + 1})`;
|
|
178
|
+
actionsUsed.add(action);
|
|
179
|
+
actionSeen.add(key);
|
|
124
180
|
L.push('',
|
|
125
|
-
`S 무엇: ${e.
|
|
126
|
-
`C 우리에게 왜: ${
|
|
127
|
-
`A 그래서 무엇을 하나: ${
|
|
181
|
+
`S 무엇: ${e.oneLiner?.trim() || '노트 한 줄 요약 없음'}`,
|
|
182
|
+
`C 우리에게 왜: ${impact.why}`,
|
|
183
|
+
`A 그래서 무엇을 하나: ${action}`,
|
|
128
184
|
`🔗 원문 링크: ${e.url?.trim() || '링크 없음'}`,
|
|
129
185
|
);
|
|
130
186
|
}
|
|
131
|
-
if (
|
|
187
|
+
if (touching.length > shownItems.length) L.push('', `닿는 것 ${touching.length - shownItems.length}건 더(요약 없는 것 포함) · 원장 \`elanous intake items\``);
|
|
188
|
+
if (d.absorbed.length > touching.length) L.push('', `그 밖 ${d.absorbed.length - touching.length}건 · 참고`);
|
|
132
189
|
return L.join('\n');
|
|
133
190
|
}
|