elanous 0.2.21 → 0.2.22
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-08b716476f1d50af.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2489-ff55a35b202c0f94.js → 2489-d238d3e2eb5b2bf5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3857-5985236d9307f676.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5633-d6852385000738ef.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5977-5b9e8a45a5b0c823.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8857-aa1cc4ac90af4d40.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9247-55bcfbb0e8057f59.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-88459e0903da2803.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-d781be3e3a9cfd74.js → page-d85c43f46f58716b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/{page-b0a80997903852d8.js → page-4e645600579ae952.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-2b1263165630d9c8.js → page-87edbfd19117823b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-7f0c2b7801ac6a54.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-bf1f030a9d1adffd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-e2d6bed48ac20120.js → page-cbaf66b1e1804adf.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/{page-111cabe4d1fd69ae.js → page-80ed511e02c81412.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/control/{page-544dd4bd4847eeb7.js → page-9f5610caaa06178c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/{page-3ee191319bf97d87.js → page-ec8f3575e339de74.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-86d753e460f1a989.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-4166a7d51cf0a57d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-a449f1ed472b81c2.js → page-2a19c5cd3b562580.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-9045094d6d3b5cd0.js → page-b6a09cde2e07521a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/{page-c06673596849ef45.js → page-f7eae1518ca0c353.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-456a8573ad811381.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/{layout-9f81233f29dcd409.js → layout-8462d6a543cbb2d9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-2974cdfb87c0f9c9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-f927ffa25f3c86d1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-abd86cd832aaa011.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-79daf68f6256adaa.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-8c3f168fc3f91939.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-7a12f61be4a579d1.js → page-786fb4504d0f2fa9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{not-found-c85b0589591a99c9.js → not-found-75209f14597dcb8f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/{page-725a4da02dd129eb.js → page-bf30affe3a85af7e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1187a44b7515434b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-3d0910185a66ed54.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-f401ee1dd5a26e25.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-5dfe7604fd842784.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/{page-21bec4afeb2f0917.js → page-76cf838050a8d681.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/{page-dc39ee7a36d26385.js → page-7ec8e289b4c14df6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-84ae759f76053672.js → page-8c8ac17d52be2a79.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/{page-3db58ddb61fe2be9.js → page-d359ab048d63a9d9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-c5315e30f3528dc4.js → page-b8b345c880bf78d1.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-2fe368efb6de3b6a.js → page-f151cc10ad026940.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-9875f103057da433.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-9789328af9a9aa62.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/{page-c69762987b751174.js → page-bf495a1e284f4e00.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-a263d94bca4be22a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-3962eab8ec58d16e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-9abcfac07e02a542.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-bee49f927c333abb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-bac2d56dcb02d955.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-937cbcc13176295c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d09495594f7f480d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/{page-7f8d7fcdf058a845.js → page-69cb93d1a2e57e0e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/{page-22163556cd38bc88.js → page-72ec543928d67c3e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-82906eeda0186a7b.js +1 -0
- package/apps/pwa/out/_next/static/css/f2bd5257e6af88d8.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +14 -14
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +14 -14
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +14 -14
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +14 -14
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +14 -14
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +14 -14
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +14 -14
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +14 -14
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +14 -14
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +14 -14
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +14 -14
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +14 -14
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +14 -14
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +14 -14
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +14 -14
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +14 -14
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +14 -14
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +14 -14
- package/apps/pwa/out/live-run/index.html +1 -1
- package/apps/pwa/out/live-run/index.txt +14 -14
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +14 -14
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +14 -14
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +14 -14
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +14 -14
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +14 -14
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +14 -14
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +14 -14
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +14 -14
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +14 -14
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +15 -15
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +14 -14
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +14 -14
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +14 -14
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +14 -14
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +14 -14
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +14 -14
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +14 -14
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +14 -14
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +14 -14
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +14 -14
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +14 -14
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +14 -14
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +14 -14
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +14 -14
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +14 -14
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +14 -14
- package/graphs/catalog/catalog.yaml +1 -1
- package/graphs/drafts/recipes.yaml +2 -2
- package/graphs/test-diet/recipes.yaml +3 -0
- package/graphs/test-diet/test-diet.yaml +3 -1
- package/package.json +1 -1
- package/scripts/f-session-monitor.ts +69 -0
- package/scripts/hq/standby-snapshot.ts +7 -3
- package/scripts/knowledge-compress.ts +11 -0
- package/scripts/nightly-docops.ts +90 -3
- package/scripts/research-coo-admin-dates.ts +39 -0
- package/scripts/skills/public-check.ts +7 -2
- package/scripts/smart-merge/resync-shadow.ts +5 -3
- package/scripts/test-diet/lib.ts +81 -1
- package/scripts/test-diet/node.ts +48 -2
- package/scripts/verify-coo-admin-evidence.ts +83 -0
- package/src/acp/client.ts +4 -2
- package/src/agent/run-context.ts +4 -0
- package/src/agent-env/profile.ts +25 -5
- package/src/agent-mission/driver.ts +26 -12
- package/src/agent-mission/supervisor-metrics.ts +17 -2
- package/src/agent-substrate/acp-reviewer.ts +12 -5
- package/src/agent-substrate/pr-reviewer.ts +11 -4
- package/src/agent-substrate/review-intent.ts +102 -3
- package/src/autopilot/build/llm-conflict-merge.ts +22 -8
- package/src/autopilot/pr-manager.ts +18 -8
- package/src/away/release-watch.ts +47 -8
- package/src/briefing/brief-items.ts +104 -11
- package/src/chat/index.ts +1 -0
- package/src/cli/brief-cli.ts +20 -2
- package/src/cli/config-classify.ts +83 -0
- package/src/cli/control-cli.ts +14 -0
- package/src/cli/decisions-cli.ts +26 -16
- package/src/cli/logs-model-input.ts +1 -1
- package/src/cli/pr-cli.ts +65 -8
- package/src/cli/release-cli.ts +21 -4
- package/src/cli/tasks-cli.ts +108 -1
- package/src/connectors/types.ts +6 -6
- package/src/context-bus/context-now-surfaces.ts +18 -0
- package/src/context-bus/coord-events.ts +32 -4
- package/src/context-bus/long-term-memory.ts +23 -6
- package/src/context-bus/nightly-condense.ts +169 -21
- package/src/control/control-status.ts +87 -0
- package/src/control-plane/light-member.ts +5 -3
- package/src/control-plane/member.ts +172 -7
- package/src/core-turn/run-goal-loop.ts +22 -1
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +9 -0
- package/src/decisions/decision-cards.ts +20 -1
- package/src/decisions/decision-ledger.ts +11 -8
- package/src/decisions/web-push-decision-cards.ts +19 -3
- package/src/discord-self-message.ts +2 -2
- package/src/domains/coo-admin-dates.evidence.json +876 -0
- package/src/domains/coo-admin-tool.ts +48 -14
- package/src/domains/ops-status.ts +53 -0
- package/src/domains/outbound-alert.ts +24 -1
- package/src/exec-requests/planner.ts +6 -0
- package/src/graph-kinds/registry.ts +6 -6
- package/src/graph-runner/graph-cli.ts +34 -2
- package/src/graph-runner/graph-run-api.ts +7 -0
- package/src/graph-runner/graph-tick.ts +73 -12
- package/src/graph-runner/runner.ts +43 -11
- package/src/graph-unify/wf-to-graph.ts +79 -0
- package/src/graph-wizard/generate.ts +34 -2
- package/src/graph-wizard/steps.ts +13 -0
- package/src/harness/harness-cli-command.ts +121 -23
- package/src/harness/harness-queue.ts +58 -14
- package/src/harness/launch-advice.ts +23 -0
- package/src/index.ts +36 -7
- package/src/knowledge/conclusion-compress.ts +93 -0
- package/src/knowledge/know-find.ts +9 -2
- package/src/knowledge/query.ts +20 -0
- package/src/llm.ts +26 -16
- package/src/loops/neighbor-runtime.ts +88 -0
- package/src/loops/orchestrator/author-depth.ts +29 -11
- package/src/loops/orchestrator/overlap.ts +7 -1
- package/src/loops/orchestrator/resync-shadow.ts +1 -1
- package/src/loops/orchestrator/tick.ts +35 -2
- package/src/loops/orchestrator/traffic.ts +2 -0
- package/src/loops/registry.ts +5 -0
- package/src/market/publish.ts +17 -1
- package/src/maturity/feature-maturity.ts +2 -1
- package/src/mss/logging/redaction.ts +17 -0
- package/src/nexus/api/draft-metrics.ts +22 -8
- package/src/nexus/api/graph-peer-edit.ts +77 -0
- package/src/nexus/api/graph-wizard-api.ts +9 -1
- package/src/nexus/api/graphs-api.ts +145 -7
- package/src/nexus/api/http-server.ts +93 -8
- package/src/nexus/api/plugins-market.ts +24 -0
- package/src/nexus/index.ts +3 -2
- package/src/oauth/claude-pty-maturity.ts +24 -0
- package/src/oauth/fallback-chain.ts +27 -14
- package/src/plugins/maker/market-bundle.ts +9 -0
- package/src/plugins/maker/plugin-maker.ts +9 -3
- package/src/plugins/maker/wizard-generate.ts +73 -9
- package/src/policy/llm-policy.ts +11 -6
- package/src/release-loop/checklist-board.ts +27 -0
- package/src/release-loop/checklist.ts +38 -0
- package/src/seat-address/seat-address.ts +1 -0
- package/src/seat-loop/seat-loop.ts +174 -20
- package/src/seat-loop/seat-report.ts +29 -1
- package/src/self-dev/dev-cli.ts +90 -8
- package/src/self-dev/dev-pipeline.ts +20 -1
- package/src/self-dev/draft-sweep.ts +45 -8
- package/src/self-dev/orchestrate.ts +3 -0
- package/src/self-dev/review-provider-fallback.ts +83 -0
- package/src/self-dev/run-supervisor.ts +5 -1
- package/src/self-implement/harness-policy-gates.ts +7 -5
- package/src/self-implement/host-regate.ts +28 -14
- package/src/self-implement/l8-merge-queue.ts +80 -8
- package/src/self-implement/orchestrator.ts +68 -27
- package/src/self-implement/seams.ts +5 -3
- package/src/self-implement/self-implement-cli.ts +4 -0
- package/src/shell-env-bootstrap.ts +19 -4
- package/src/steward/triage.ts +25 -8
- package/src/storage/file-lock.ts +33 -4
- package/src/task-agent/completion-evidence.ts +31 -3
- package/src/task-agent/cover.ts +61 -0
- package/src/task-agent/live-moves.ts +126 -3
- package/src/task-agent/shadow.ts +19 -4
- package/src/task-agent/task-hand.ts +2 -0
- package/src/task-orchestrator/surfaces/pod-pool.ts +138 -15
- package/src/task-orchestrator/surfaces/self-implement.ts +7 -0
- package/src/telegram-commands.ts +8 -0
- package/src/telegram-project-command.ts +6 -0
- package/src/telegram.ts +2 -2
- package/src/typecheck-ratchet.ts +2 -1
- package/src/user-config.ts +35 -4
- package/src/version/packed-revision.json +1 -1
- package/src/workflow-runtime/executor.ts +24 -2
- package/src/workflow-runtime/triggers/registry.ts +27 -0
- package/src/workflow-runtime/triggers/schedule-source.ts +30 -3
- package/apps/pwa/out/_next/static/chunks/1307-c97eba9fb72a9a5e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3857-995ea9f5ef33d71e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5977-59fedea50d13c762.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8857-f1762140ef9a96b1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9247-7581825d511a64c0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9497-b0e758d1cd4ae8d7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-937c92292a4ab4f0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-ea998f7f5d156bea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-33c328b64ca508f8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-e4bcb2c503dab04b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-300897dcbbd5ed76.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-103fa3191c1b2e08.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-be8b4615c919e36c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-c685b104e0dfa609.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-8d693331cfbb6add.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-cef4f30a4c22fae1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66ea1b54fc83fa15.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-8cc707bd09a497d9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-90b55be1f15ce0e8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cb16606e08be65db.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-16dc64b74ed6a40a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-8d04132dd0ac5e10.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-f23484de467c9ea3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-5456692bda39aafb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-d75f7cd65845ce98.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-5603f5e568b25ce3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-d2cc0f4e89f42028.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-a6eb73b8a4840f1c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-c0a3f4d10edb83fa.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-6c0b88068c143f15.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-141436fd26dd56fc.js +0 -1
- package/apps/pwa/out/_next/static/css/e81df11490b4413b.css +0 -1
- /package/apps/pwa/out/_next/static/{MObU8ujkMciAKr6ljAV05 → aBi6EyMtLsrl7J8eH4kF7}/_buildManifest.js +0 -0
- /package/apps/pwa/out/_next/static/{MObU8ujkMciAKr6ljAV05 → aBi6EyMtLsrl7J8eH4kF7}/_ssgManifest.js +0 -0
package/src/cli/pr-cli.ts
CHANGED
|
@@ -62,6 +62,9 @@ import { enqueueL8ShadowQueue, runL8ShadowQueue, shadowPrNumber, statusL8ShadowQ
|
|
|
62
62
|
export { branchLineageSlug, findSiblingPrs };
|
|
63
63
|
|
|
64
64
|
export interface PrLandOpts {
|
|
65
|
+
/** Pin an existing PR and its reviewed head; both must be supplied together. */
|
|
66
|
+
pr?: string;
|
|
67
|
+
expectedHead?: string;
|
|
65
68
|
base?: string;
|
|
66
69
|
cwd?: string;
|
|
67
70
|
dryRun?: boolean;
|
|
@@ -1180,10 +1183,33 @@ export async function runPrLand(opts: PrLandOpts = {}, deps: PrLandDeps = {}): P
|
|
|
1180
1183
|
|
|
1181
1184
|
const cwd = opts.cwd ?? process.cwd();
|
|
1182
1185
|
const run = deps.run ?? defaultCmdRunner;
|
|
1186
|
+
const pinned = opts.pr !== undefined || opts.expectedHead !== undefined;
|
|
1187
|
+
let pinnedBranch: string | undefined;
|
|
1188
|
+
let pinnedUrl: string | undefined;
|
|
1189
|
+
if (pinned) {
|
|
1190
|
+
const pr = Number(opts.pr);
|
|
1191
|
+
if (!/^[1-9]\d*$/.test(opts.pr ?? '') || !Number.isSafeInteger(pr) || !/^[a-f0-9]{40}$/i.test(opts.expectedHead ?? '') || opts.hold || opts.dryRun) {
|
|
1192
|
+
out.error('✗ expected-head: --pr and --expected-head (full SHA) are required together without --hold/--dry-run');
|
|
1193
|
+
return 1;
|
|
1194
|
+
}
|
|
1195
|
+
const viewed = run('gh', ['pr', 'view', String(pr), '--json', 'headRefOid,headRefName,state,isDraft,url'], { cwd });
|
|
1196
|
+
let value: { headRefOid?: string; headRefName?: string; state?: string; isDraft?: boolean; url?: string } = {};
|
|
1197
|
+
try { if (viewed.ok) value = JSON.parse(viewed.out) as typeof value; } catch { /* unreadable is not approval */ }
|
|
1198
|
+
const local = run('git', ['rev-parse', 'HEAD'], { cwd });
|
|
1199
|
+
const dirty = run('git', ['status', '--porcelain'], { cwd });
|
|
1200
|
+
if (value.state !== 'OPEN' || value.isDraft !== false || value.headRefOid?.toLowerCase() !== opts.expectedHead!.toLowerCase()
|
|
1201
|
+
|| !value.url?.endsWith(`/pull/${pr}`) || !value.headRefName || !local.ok || local.out.trim().toLowerCase() !== opts.expectedHead!.toLowerCase()
|
|
1202
|
+
|| !dirty.ok || dirty.out.trim()) {
|
|
1203
|
+
out.error('✗ expected-head: PR OPEN non-draft, matching local HEAD and clean worktree required');
|
|
1204
|
+
return 1;
|
|
1205
|
+
}
|
|
1206
|
+
pinnedBranch = value.headRefName;
|
|
1207
|
+
pinnedUrl = value.url;
|
|
1208
|
+
}
|
|
1183
1209
|
const resolveBase = deps.resolveBase ?? resolveDeliverableBase;
|
|
1184
1210
|
const branch = (deps.currentBranch ?? liveCurrentBranch)(cwd);
|
|
1185
|
-
if (!branch) {
|
|
1186
|
-
const detail = '현재 브랜치를 해석하지 못했습니다.';
|
|
1211
|
+
if (!branch || (pinned && branch !== pinnedBranch)) {
|
|
1212
|
+
const detail = pinned ? 'PR head branch와 현재 브랜치가 다릅니다.' : '현재 브랜치를 해석하지 못했습니다.';
|
|
1187
1213
|
record('branch', false);
|
|
1188
1214
|
out.error(`✗ branch: ${detail}`);
|
|
1189
1215
|
return 1;
|
|
@@ -1567,6 +1593,10 @@ export async function runPrLand(opts: PrLandOpts = {}, deps: PrLandDeps = {}): P
|
|
|
1567
1593
|
});
|
|
1568
1594
|
}
|
|
1569
1595
|
const hold = !!opts.hold || overlapDecision?.outcome === 'user-hold';
|
|
1596
|
+
if (pinned && overlapDecision && overlapDecision.outcome !== 'user-merge') {
|
|
1597
|
+
out.error('✗ expected-head: overlap merge approval unavailable for the reviewed head');
|
|
1598
|
+
return 1;
|
|
1599
|
+
}
|
|
1570
1600
|
|
|
1571
1601
|
const manager = deps.manager ?? makePrManager(run);
|
|
1572
1602
|
const existing = manager.findPrForBranchOutcome(branch, cwd);
|
|
@@ -1638,7 +1668,12 @@ export async function runPrLand(opts: PrLandOpts = {}, deps: PrLandDeps = {}): P
|
|
|
1638
1668
|
}
|
|
1639
1669
|
}
|
|
1640
1670
|
|
|
1641
|
-
|
|
1671
|
+
// A pinned, already-reviewed commit must not go through upsertPr (git add/commit/force-push/PR edit).
|
|
1672
|
+
const upsert = pinned
|
|
1673
|
+
? existing.status === 'ok OUTPUT' && existing.url === pinnedUrl
|
|
1674
|
+
? { ok: true as const, url: pinnedUrl!, reused: true }
|
|
1675
|
+
: { ok: false as const, reason: 'gh' as const, detail: 'pinned PR is not the branch open PR' }
|
|
1676
|
+
: manager.upsertPr({
|
|
1642
1677
|
branch,
|
|
1643
1678
|
worktreePath: cwd,
|
|
1644
1679
|
base,
|
|
@@ -1654,7 +1689,7 @@ export async function runPrLand(opts: PrLandOpts = {}, deps: PrLandDeps = {}): P
|
|
|
1654
1689
|
...(excludingActiveRunFiles ? { excludePaths } : {}),
|
|
1655
1690
|
});
|
|
1656
1691
|
if (!upsert.ok) {
|
|
1657
|
-
if (upsert.reason === 'noop' && excludingActiveRunFiles) {
|
|
1692
|
+
if (upsert.reason === 'noop' && excludingActiveRunFiles && !pinned) {
|
|
1658
1693
|
record('upsert-ready-noop', true, { excludePaths, detail: upsert.detail });
|
|
1659
1694
|
out.log(`✓ upsert-ready: 제외 후 올릴 것이 없습니다 — ${upsert.detail}`);
|
|
1660
1695
|
return 0;
|
|
@@ -1664,6 +1699,13 @@ export async function runPrLand(opts: PrLandOpts = {}, deps: PrLandDeps = {}): P
|
|
|
1664
1699
|
return 1;
|
|
1665
1700
|
}
|
|
1666
1701
|
record('upsert-ready', true);
|
|
1702
|
+
if (pinned) {
|
|
1703
|
+
const upsertHead = run('git', ['rev-parse', 'HEAD'], { cwd });
|
|
1704
|
+
if (!upsert.reused || upsert.url !== pinnedUrl || !upsertHead.ok || upsertHead.out.trim().toLowerCase() !== opts.expectedHead!.toLowerCase()) {
|
|
1705
|
+
out.error('✗ expected-head: pinned PR identity or reviewed HEAD changed');
|
|
1706
|
+
return 1;
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1667
1709
|
out.log(`✓ upsert-ready: ${upsert.url}${upsert.reused ? ' (기존 PR)' : ' (새 PR)'}`);
|
|
1668
1710
|
|
|
1669
1711
|
if (hold) {
|
|
@@ -1688,21 +1730,34 @@ export async function runPrLand(opts: PrLandOpts = {}, deps: PrLandDeps = {}): P
|
|
|
1688
1730
|
debug.log('harness.merge', 'frozen', { prUrl: upsert.url, reason: frozen.reason, until: frozen.until });
|
|
1689
1731
|
record('freeze', true, { url: upsert.url, reason: frozen.reason, until: frozen.until });
|
|
1690
1732
|
out.log(`✓ ready: ${upsert.url} — ${landingFreezeMessage(frozen)} · 병합 연기`);
|
|
1691
|
-
return 0;
|
|
1733
|
+
return pinned ? 1 : 0;
|
|
1692
1734
|
}
|
|
1693
1735
|
if (admission.kind === 'taken') {
|
|
1694
1736
|
record('freeze', true, { url: upsert.url, resumedElsewhere: true });
|
|
1695
1737
|
out.log(`✓ ready: ${upsert.url} — 동결이 막 풀려 보류 병합 재개가 이 PR 을 맡았습니다`);
|
|
1696
|
-
return 0;
|
|
1738
|
+
return pinned ? 1 : 0;
|
|
1697
1739
|
}
|
|
1698
1740
|
const landing = admission;
|
|
1741
|
+
if (pinned) {
|
|
1742
|
+
const view = run('gh', ['pr', 'view', String(opts.pr), '--json', 'headRefOid,state,isDraft'], { cwd });
|
|
1743
|
+
let current: { headRefOid?: string; state?: string; isDraft?: boolean } = {};
|
|
1744
|
+
try { if (view.ok) current = JSON.parse(view.out) as typeof current; } catch { /* fail closed */ }
|
|
1745
|
+
const finalHead = run('git', ['rev-parse', 'HEAD'], { cwd });
|
|
1746
|
+
if (current.headRefOid?.toLowerCase() !== opts.expectedHead!.toLowerCase() || current.state !== 'OPEN' || current.isDraft !== false
|
|
1747
|
+
|| !finalHead.ok || finalHead.out.trim().toLowerCase() !== opts.expectedHead!.toLowerCase()) {
|
|
1748
|
+
landing.end(false);
|
|
1749
|
+
out.error('✗ expected-head: PR head/state changed before merge');
|
|
1750
|
+
return 1;
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1699
1753
|
let merged: ReturnType<typeof manager.mergePrOutcome>;
|
|
1700
1754
|
try {
|
|
1701
|
-
merged = manager.mergePrOutcome(upsert.url);
|
|
1755
|
+
merged = manager.mergePrOutcome(upsert.url, pinned ? opts.expectedHead : undefined, pinned ? cwd : undefined);
|
|
1702
1756
|
// `GIT-S14` — 병합이 «미완료(OPEN)»로 끝났고 GitHub 가 아직 `mergeable=UNKNOWN`(병합 가능 여부 계산 중)이면
|
|
1703
1757
|
// 짧게 기다렸다 다시 병합한다. 표본 셋(08-25 · 09-24 🅢 #20233 등)이 모두 «같은 명령 재시도에 병합»이었다.
|
|
1704
1758
|
// ⛔ UNKNOWN 일 때만 재시도한다 — CONFLICTING 등 다른 값은 기다려도 안 풀린다(그대로 실패를 낸다).
|
|
1705
1759
|
const sleep = deps.sleep ?? ((ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms)));
|
|
1760
|
+
// pinned 도 재시도한다 — mergePrOutcome 이 머리를 다시 확인하고 --match-head-commit 으로 병합하므로 바뀐 머리로는 안 들어간다.
|
|
1706
1761
|
for (let attempt = 1; !merged.ok && merged.kind === 'not-merged' && merged.state === 'OPEN' && attempt <= PR_LAND_MERGEABLE_UNKNOWN_RETRIES; attempt += 1) {
|
|
1707
1762
|
const mergeable = run('gh', ['pr', 'view', upsert.url, '--json', 'mergeable', '--jq', '.mergeable'], { cwd });
|
|
1708
1763
|
const value = mergeable.ok ? mergeable.out.trim() : '';
|
|
@@ -1710,7 +1765,7 @@ export async function runPrLand(opts: PrLandOpts = {}, deps: PrLandDeps = {}): P
|
|
|
1710
1765
|
record('merge-retry', true, { attempt, mergeable: value, waitMs: PR_LAND_MERGEABLE_UNKNOWN_WAIT_MS });
|
|
1711
1766
|
out.log(`⏳ merge: mergeable=UNKNOWN — GitHub 가 병합 가능 여부를 아직 계산 중입니다 · ${PR_LAND_MERGEABLE_UNKNOWN_WAIT_MS / 1000}초 뒤 다시 병합 (${attempt}/${PR_LAND_MERGEABLE_UNKNOWN_RETRIES})`);
|
|
1712
1767
|
await sleep(PR_LAND_MERGEABLE_UNKNOWN_WAIT_MS);
|
|
1713
|
-
merged = manager.mergePrOutcome(upsert.url);
|
|
1768
|
+
merged = manager.mergePrOutcome(upsert.url, pinned ? opts.expectedHead : undefined, pinned ? cwd : undefined);
|
|
1714
1769
|
}
|
|
1715
1770
|
} finally { landing.end(merged!?.ok === true); }
|
|
1716
1771
|
if (!merged.ok) {
|
|
@@ -1781,6 +1836,8 @@ export function registerPrCommands(program: Command, deps: PrLandDeps = {}, queu
|
|
|
1781
1836
|
.description('현재 브랜치 PR을 생성 또는 갱신해 ready 상태로 만들고 squash merge')
|
|
1782
1837
|
.option('--base <branch>', 'PR base branch')
|
|
1783
1838
|
.option('--cwd <path>', '작업 트리 경로')
|
|
1839
|
+
.option('--pr <number>', '기존 PR 번호 고정 (--expected-head 와 함께)')
|
|
1840
|
+
.option('--expected-head <sha>', '리뷰된 PR 머리 SHA 고정 (--pr 와 함께)')
|
|
1784
1841
|
.option('--dry-run', '변경 없이 landing 계획만 출력')
|
|
1785
1842
|
.option('--hold', 'PR을 생성 또는 갱신한 뒤 병합하지 않고 쌓아 둔다')
|
|
1786
1843
|
.option('--title <title>', 'PR title')
|
package/src/cli/release-cli.ts
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
// ⛔ 공개본은 «원본 매니페스트»(`release/public-export.yaml`)대로만 만든다 — 매매 실행 코드는 그 매니페스트가 이미 뺀다(🅢 #20529).
|
|
19
19
|
import { spawnSync } from 'node:child_process';
|
|
20
20
|
import { createHash } from 'node:crypto';
|
|
21
|
-
import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
21
|
+
import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
22
22
|
import { tmpdir } from 'node:os';
|
|
23
23
|
import { dirname, join, resolve } from 'node:path';
|
|
24
24
|
import type { Command } from 'commander';
|
|
@@ -27,7 +27,7 @@ import { effectiveInstanceRoot, releaseLedgerRoot } from '../instance/resolve.js
|
|
|
27
27
|
import { getElanousConfigDirOverride } from '../elanous-config-dir.js';
|
|
28
28
|
import { envLiteral } from '../platform/env-literal.js';
|
|
29
29
|
import { getUserConfig, userConfigPath } from '../user-config.js';
|
|
30
|
-
import { addItem, cellsReferencingDoc, claimItem, decodeClaimHistoryEntry, devVersion, listChecklist, ownerMatches, parseOwner, parityGap, removeItem, renderRefsStatus, seedFromRoadmap, setItem, summarizeChecklist, type ChecklistStatus, type ChecklistDisposition, type ChecklistKind } from '../release-loop/checklist.js';
|
|
30
|
+
import { addItem, cellsReferencingDoc, claimItem, decodeClaimHistoryEntry, devVersion, lintDocRefs, listChecklist, normalizeRefs, ownerMatches, parseOwner, parityGap, refRoots, removeItem, renderRefsStatus, seedFromRoadmap, setItem, summarizeChecklist, type ChecklistStatus, type ChecklistDisposition, type ChecklistKind } from '../release-loop/checklist.js';
|
|
31
31
|
import * as features from '../release-loop/feature-store.js';
|
|
32
32
|
import { evidencePlan, landedButYellow, type MergedChecklistPr } from '../release-loop/landed-but-yellow.js';
|
|
33
33
|
import { formatSchedule, getSchedule, listSchedules, setSchedule } from '../release-loop/release-schedule.js';
|
|
@@ -705,11 +705,28 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
|
|
|
705
705
|
console.log(`루브릭 없는 칸 ${missing}`);
|
|
706
706
|
});
|
|
707
707
|
withContext(checklist.command('refs <doc>').description('문서를 인용하는 칸 — 모든 판의 구현 현황(읽기 전용)'))
|
|
708
|
-
.
|
|
708
|
+
.option('--lint', '문서 절과 칸 인용을 양방향 대조(읽기 전용)')
|
|
709
|
+
.action(async (doc: string, opts: { lint?: boolean }, cmd: Command) => {
|
|
709
710
|
if (!doc.trim() || doc.trim().startsWith('#')) throw new CliUserError(`잘못된 문서: ${doc}`, '저장소 상대 경로[#절] — 예: docs/RFC-x.md#§3');
|
|
710
711
|
const { json } = context(cmd);
|
|
711
712
|
const ledgerRoot = releaseLedgerRoot();
|
|
712
|
-
const
|
|
713
|
+
const checklists = features.assignedVersions(ledgerRoot).map((version) => listChecklist(version, ledgerRoot));
|
|
714
|
+
if (opts.lint) {
|
|
715
|
+
const path = doc.trim().split('#')[0]!.replace(/^\.\//, '');
|
|
716
|
+
const roots = refRoots();
|
|
717
|
+
normalizeRefs([path], roots);
|
|
718
|
+
const file = roots.map((root) => join(root, path)).find((candidate) => {
|
|
719
|
+
try { return statSync(candidate).isFile(); } catch { return false; }
|
|
720
|
+
})!;
|
|
721
|
+
const result = lintDocRefs(path, readFileSync(file, 'utf8'), checklists);
|
|
722
|
+
debug.log('release.checklist', 'refs-lint', { doc, sections: result.sections.length, uncovered: result.uncoveredSections.length, dangling: result.danglingRefs.length });
|
|
723
|
+
if (json) { await writeStdoutJson(`${JSON.stringify({ doc, ...result })}\n`); return; }
|
|
724
|
+
console.log(`칸 없는 절: ${result.uncoveredSections.join(', ') || '없음'}`);
|
|
725
|
+
console.log(`없는 절을 가리키는 칸: ${result.danglingRefs.map(({ version, id, ref }) => `${version} ${id} ${ref}`).join(' · ') || '없음'}`);
|
|
726
|
+
console.log(`절 ${result.sections.length} · 칸 없는 절 ${result.uncoveredSections.length} · 없는 절을 가리키는 칸 ${result.danglingRefs.length}`);
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
const rows = cellsReferencingDoc(doc, checklists);
|
|
713
730
|
const byStatus = { green: 0, yellow: 0, red: 0, done: 0 } as Record<ChecklistStatus, number>;
|
|
714
731
|
for (const row of rows) byStatus[row.status] += 1;
|
|
715
732
|
debug.log('release.checklist', 'refs-read', { doc, rows: rows.length });
|
package/src/cli/tasks-cli.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { closeSync, mkdirSync, openSync, readFileSync, readSync } from 'node:fs';
|
|
1
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync } from 'node:fs';
|
|
2
2
|
import { tmpdir } from 'node:os';
|
|
3
3
|
import { loadRunLedger as loadRunLedgerSync } from '../self-implement/run-ledger.js';
|
|
4
4
|
import { join, resolve } from 'node:path';
|
|
@@ -12,6 +12,9 @@ import type { Task } from '../task-orchestrator/types.js';
|
|
|
12
12
|
import { cardPrNumber, COMPLETION_KINDS, handTask, nextMoveFor, readTaskAgentState, readTaskCard, taskAgentStatePath as taskStatePathDefault, shellQuote, TASK_CARD_PREFIX, TASK_SEATS, type CompletionKind, type TaskCard, type TaskLaunchContext, type TaskLaunchReceipt, type TaskLauncher, type TaskSeat } from '../task-agent/task-hand.js';
|
|
13
13
|
import { readCardSalvageBranch, refreshCardRunBinding, type CardRunEvidenceDeps } from '../task-agent/card-evidence.js';
|
|
14
14
|
import { advanceMission, handMission, MissionAdvanceError, openMissionIds, recordPieceRef, type AdvanceMissionResult, type MissionDecompose, type PieceEvidenceReader } from '../task-agent/mission.js';
|
|
15
|
+
import { formatTaskAgentCover, measureTaskAgentCover, type CoverRow, type TaskAgentCoverInput } from '../task-agent/cover.js';
|
|
16
|
+
import { LogStore, STORE_SAFETY_MAX, type LogQuery, type LogStoreRow } from '../mss/logging/log-store.js';
|
|
17
|
+
import { resolveLogTargets } from './logs-cli.js';
|
|
15
18
|
|
|
16
19
|
type TaskRow = Pick<Task, 'id' | 'title' | 'priority' | 'status' | 'createdAt' | 'approval' | 'generatedBy'>;
|
|
17
20
|
|
|
@@ -38,6 +41,9 @@ export interface TasksCliDeps {
|
|
|
38
41
|
remoteLedgerMirror?: (card: TaskCard) => void;
|
|
39
42
|
/** `tasks parents` 의 낡은 부모 보고 의존(시험 주입). */
|
|
40
43
|
staleParents?: import('../task-agent/parent-host.js').StaleParentDeps;
|
|
44
|
+
/** `tasks cover` logs 조회 seam. 기본은 등록된 모든 우주(운영 ⊕ test)의 logs.db 를 read-only 로 연다 —
|
|
45
|
+
* pr land 는 작업 트리마다 파생 test 우주에 기록되므로 현재 우주만 읽으면 분모가 늘 0 이다. */
|
|
46
|
+
coverLogs?: (query: LogQuery) => LogStoreRow[] | CoverLogRead;
|
|
41
47
|
}
|
|
42
48
|
|
|
43
49
|
/** `<조각id>=<값>` 반복 인자. */
|
|
@@ -390,6 +396,74 @@ function handleError(error: unknown, out: (line: string) => void): void {
|
|
|
390
396
|
}
|
|
391
397
|
}
|
|
392
398
|
|
|
399
|
+
const COVER_QUERY_LIMIT = STORE_SAFETY_MAX;
|
|
400
|
+
|
|
401
|
+
/** Federated read: rows from every store, whether any single store hit the bound, and how many stores could not be read. */
|
|
402
|
+
export interface CoverLogRead { rows: LogStoreRow[]; limitReached: boolean; unreadableStores: number; storesRead: number }
|
|
403
|
+
|
|
404
|
+
function queryCoverLogs(query: LogQuery): CoverLogRead {
|
|
405
|
+
const { targets, error } = resolveLogTargets({ all: true, includeTest: true });
|
|
406
|
+
if (error) throw new Error(error);
|
|
407
|
+
const read: CoverLogRead = { rows: [], limitReached: false, unreadableStores: 0, storesRead: 0 };
|
|
408
|
+
for (const target of targets) {
|
|
409
|
+
if (!existsSync(target.dbPath)) continue;
|
|
410
|
+
let store: LogStore | undefined;
|
|
411
|
+
try {
|
|
412
|
+
store = LogStore.openReadOnly(target.dbPath);
|
|
413
|
+
const rows = store.query(query);
|
|
414
|
+
if (query.limit !== undefined && rows.length >= query.limit) read.limitReached = true;
|
|
415
|
+
read.rows.push(...rows);
|
|
416
|
+
read.storesRead++;
|
|
417
|
+
} catch { read.unreadableStores++; }
|
|
418
|
+
finally { try { store?.close(); } catch { /* already closed */ } }
|
|
419
|
+
}
|
|
420
|
+
if (read.storesRead === 0) throw new Error('읽을 수 있는 logs.db 없음');
|
|
421
|
+
return read;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function coverEvents<T>(rows: readonly LogStoreRow[], category: string, event: string, parse: (data: Record<string, unknown>) => T | null): T[] {
|
|
425
|
+
const events: T[] = [];
|
|
426
|
+
for (const row of rows) {
|
|
427
|
+
if (row.category !== category || row.event !== event || !row.data) continue;
|
|
428
|
+
try {
|
|
429
|
+
const data: unknown = JSON.parse(row.data);
|
|
430
|
+
if (data === null || typeof data !== 'object' || Array.isArray(data)) continue;
|
|
431
|
+
const parsed = parse(data as Record<string, unknown>);
|
|
432
|
+
if (parsed !== null) events.push(parsed);
|
|
433
|
+
} catch { /* malformed log data is not evidence of an action */ }
|
|
434
|
+
}
|
|
435
|
+
return events;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export function readTaskAgentCover(
|
|
439
|
+
window: { sinceMs?: number; untilMs?: number } = {},
|
|
440
|
+
query: NonNullable<TasksCliDeps['coverLogs']> = queryCoverLogs,
|
|
441
|
+
): { rows: CoverRow[]; limitReached: boolean; unreadableStores: number } {
|
|
442
|
+
let unreadableStores = 0;
|
|
443
|
+
const read = (category: 'task-agent' | 'pr.land', event: 'action' | 'step'): { rows: LogStoreRow[]; limitReached: boolean } | null => {
|
|
444
|
+
try {
|
|
445
|
+
const result = query({ ...window, limit: COVER_QUERY_LIMIT, exactCategories: [category], events: [event] });
|
|
446
|
+
if (Array.isArray(result)) return { rows: result, limitReached: result.length >= COVER_QUERY_LIMIT };
|
|
447
|
+
unreadableStores = Math.max(unreadableStores, result.unreadableStores);
|
|
448
|
+
return { rows: result.rows, limitReached: result.limitReached };
|
|
449
|
+
} catch { return null; }
|
|
450
|
+
};
|
|
451
|
+
const actionRead = read('task-agent', 'action');
|
|
452
|
+
const landRead = read('pr.land', 'step');
|
|
453
|
+
const actions = actionRead?.rows ?? null;
|
|
454
|
+
const lands = landRead?.rows ?? null;
|
|
455
|
+
const truncated = { taskAgentActions: actionRead?.limitReached === true, allLands: landRead?.limitReached === true };
|
|
456
|
+
const input: TaskAgentCoverInput = {
|
|
457
|
+
taskAgentActions: actions === null ? null : coverEvents(actions, 'task-agent', 'action', (data) =>
|
|
458
|
+
typeof data.kind === 'string' && typeof data.result === 'string'
|
|
459
|
+
? { kind: data.kind as NonNullable<TaskAgentCoverInput['taskAgentActions']>[number]['kind'], result: data.result } : null),
|
|
460
|
+
allLands: lands === null ? null : coverEvents(lands, 'pr.land', 'step', (data) =>
|
|
461
|
+
typeof data.step === 'string' && typeof data.ok === 'boolean' ? { step: data.step, ok: data.ok } : null),
|
|
462
|
+
truncated,
|
|
463
|
+
};
|
|
464
|
+
return { rows: measureTaskAgentCover(input), limitReached: truncated.taskAgentActions || truncated.allLands, unreadableStores };
|
|
465
|
+
}
|
|
466
|
+
|
|
393
467
|
export function registerTasksCommands(program: Command, deps: TasksCliDeps = {}): void {
|
|
394
468
|
let activeToken: string | undefined;
|
|
395
469
|
const output = deps.output ?? console.log;
|
|
@@ -402,6 +476,39 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
402
476
|
};
|
|
403
477
|
const tasks = program.command('tasks').alias('task').description('TOX 태스크 조회 및 승인 · TASK-AGENT 과제 넘기기(hand)');
|
|
404
478
|
|
|
479
|
+
tasks.command('cover')
|
|
480
|
+
.description('TASK-AGENT 가 대신한 착지 비율과 리뷰·재발사·green 의 아직 없는 분모를 조회(읽기 전용)')
|
|
481
|
+
.option('--since <iso>', '시작 시각 (ISO)')
|
|
482
|
+
.option('--until <iso>', '끝 시각 (ISO)')
|
|
483
|
+
.option('--json', 'JSON 출력')
|
|
484
|
+
.option('--lines', '동사별 한 줄 요약 출력 (읽기 전용)')
|
|
485
|
+
.action((opts: { since?: string; until?: string; json?: boolean; lines?: boolean }) => {
|
|
486
|
+
const parseIso = (value: string | undefined, flag: string): number | undefined => {
|
|
487
|
+
if (value === undefined) return undefined;
|
|
488
|
+
const time = Date.parse(value);
|
|
489
|
+
if (!/^\d{4}-\d{2}-\d{2}T.*(?:Z|[+-]\d\d:\d\d)$/.test(value) || !Number.isFinite(time)) throw new Error(`${flag} 는 ISO 시각이어야 한다`);
|
|
490
|
+
return time;
|
|
491
|
+
};
|
|
492
|
+
try {
|
|
493
|
+
const sinceMs = parseIso(opts.since, '--since');
|
|
494
|
+
const untilMs = parseIso(opts.until, '--until');
|
|
495
|
+
if (sinceMs !== undefined && untilMs !== undefined && sinceMs > untilMs) throw new Error('--since 가 --until 보다 늦다');
|
|
496
|
+
const { rows, limitReached, unreadableStores } = readTaskAgentCover({ sinceMs, untilMs }, deps.coverLogs);
|
|
497
|
+
const limitWarning = limitReached ? `조회 상한 ${COVER_QUERY_LIMIT} 도달 — 더 오래된 사건이 있을 수 있다(절단된 수를 전체로 읽지 말 것)` : null;
|
|
498
|
+
const storeWarning = unreadableStores > 0 ? `logs.db ${unreadableStores}개를 못 읽었다 — 그 우주의 사건은 수에 없다(«없다»로 읽지 말 것)` : null;
|
|
499
|
+
if (opts.json) out(JSON.stringify({ rows, limitReached, unreadableStores, ...(limitWarning ? { warning: limitWarning } : {}), ...(storeWarning ? { storeWarning } : {}) }));
|
|
500
|
+
else {
|
|
501
|
+
if (opts.lines) for (const row of rows) out(formatTaskAgentCover(row));
|
|
502
|
+
else {
|
|
503
|
+
out('동사\t대신\t전체\t비율\t상태\t관측 사건');
|
|
504
|
+
for (const row of rows) out(`${row.verb}\t${row.byTaskAgent ?? '-'}\t${row.total ?? '-'}\t${row.ratio ?? '-'}\t${row.state}\t${row.observedActions ?? '-'}`);
|
|
505
|
+
}
|
|
506
|
+
if (limitWarning) out(`⚠ ${limitWarning}`);
|
|
507
|
+
if (storeWarning) out(`⚠ ${storeWarning}`);
|
|
508
|
+
}
|
|
509
|
+
} catch (error) { out(error instanceof Error ? error.message : '커버 조회 실패'); process.exitCode = 1; }
|
|
510
|
+
});
|
|
511
|
+
|
|
405
512
|
tasks.command('list')
|
|
406
513
|
.description('우선순위와 생성 시각 순으로 태스크 보기')
|
|
407
514
|
.option('--status <s>', '상태로 필터')
|
package/src/connectors/types.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export interface ExternalTaskEvent {
|
|
2
|
-
provider:
|
|
1
|
+
export interface ExternalTaskEvent<Provider extends string = 'linear' | 'asana'> {
|
|
2
|
+
provider: Provider;
|
|
3
3
|
eventId: string;
|
|
4
4
|
kind: 'created' | 'updated';
|
|
5
5
|
ref: string;
|
|
@@ -11,9 +11,9 @@ export interface ExternalTaskEvent {
|
|
|
11
11
|
occurredAt: string;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
export interface TaskConnector {
|
|
15
|
-
provider:
|
|
14
|
+
export interface TaskConnector<Provider extends string = ExternalTaskEvent['provider']> {
|
|
15
|
+
provider: Provider;
|
|
16
16
|
verify(req: { rawBody: string | Uint8Array; signature: string; secret: string; now?: number }): { ok: true } | { ok: false; reason: string };
|
|
17
|
-
parse(body: unknown, deliveryId: string): ExternalTaskEvent | null;
|
|
18
|
-
idempotencyKey(event: ExternalTaskEvent): string;
|
|
17
|
+
parse(body: unknown, deliveryId: string): ExternalTaskEvent<Provider> | null;
|
|
18
|
+
idempotencyKey(event: ExternalTaskEvent<Provider>): string;
|
|
19
19
|
}
|
|
@@ -53,6 +53,24 @@ export function renderTelegramNow(input: ContextNowAnswer, audience: ContextNowA
|
|
|
53
53
|
return lines.map(line => line.replace(/\s+/g, ' ').trim()).join('\n');
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
/** Context-first opening for chat connectors: up to three operational rows (live runs, an unfinished
|
|
57
|
+
* release run, late schedules) ahead of the same `/now` body. `/now` itself stays unchanged. */
|
|
58
|
+
export function renderContextFirstNow(input: ContextNowAnswer, audience: ContextNowAudience = 'operator'): string {
|
|
59
|
+
const answer = forAudience(input, audience);
|
|
60
|
+
const labels = { run: '도는 런', release: '발행 런', 'schedule-late': '지연 스케줄' } as const;
|
|
61
|
+
const operational = answer.facts.filter(fact => fact.kind === 'run' ||
|
|
62
|
+
(fact.kind === 'release' && (fact.unreadable || fact.status !== 'done')) ||
|
|
63
|
+
(fact.kind === 'schedule-late' && (fact.unreadable || !!fact.count)));
|
|
64
|
+
// Run goals are whole asks (often thousands of characters); one line each keeps the opening to one screen.
|
|
65
|
+
const short = (text: string) => { const chars = Array.from(text); return chars.length > 60 ? `${chars.slice(0, 60).join('')}…` : text; };
|
|
66
|
+
const lines = operational.slice(0, 3).map(fact => {
|
|
67
|
+
const text = fact.kind === 'run' && !fact.unreadable ? `${short(fact.goal.replace(/\s+/g, ' ').trim())} · ${fact.phase} · ${fact.elapsed}` : factText(fact);
|
|
68
|
+
return `${labels[fact.kind as keyof typeof labels]}: ${text} — ${fact.source}`.replace(/\s+/g, ' ').trim();
|
|
69
|
+
});
|
|
70
|
+
if (operational.length > lines.length) lines.push(`… 운영 항목 ${operational.length - lines.length}개 더 (/now)`);
|
|
71
|
+
return [...lines, renderTelegramNow(answer, 'operator')].join('\n');
|
|
72
|
+
}
|
|
73
|
+
|
|
56
74
|
export function renderTuiNow(input: ContextNowAnswer, audience: ContextNowAudience = 'operator'): string[] {
|
|
57
75
|
const answer = forAudience(input, audience);
|
|
58
76
|
const shortText = (text: string) => {
|
|
@@ -2,7 +2,9 @@ import { Database } from 'bun:sqlite';
|
|
|
2
2
|
import { readFileSync, existsSync } from 'node:fs';
|
|
3
3
|
import { resolve } from 'node:path';
|
|
4
4
|
import { debug, redactSecretText } from '../debug/log.js';
|
|
5
|
+
import { DecisionLedger, type Seat } from '../decisions/decision-ledger.js';
|
|
5
6
|
import { openSurfaceEventsDb, recordEvent, surfaceEventsDbPath } from '../domains/surface-events.js';
|
|
7
|
+
import { registeredSeats } from '../seat-address/seat-address.js';
|
|
6
8
|
|
|
7
9
|
export interface CoordEventInput {
|
|
8
10
|
seat: string;
|
|
@@ -31,6 +33,13 @@ function seatIds(): Set<string> {
|
|
|
31
33
|
}
|
|
32
34
|
const kinds = new Set(['요청', '결정', '사고', '보고', '정정']);
|
|
33
35
|
|
|
36
|
+
function cleanCoordHeadline(value: string): string {
|
|
37
|
+
const first = value.split(/\r?\n/, 1)[0] ?? '';
|
|
38
|
+
const withoutPrefix = first.replace(/^\*\*\[[A-Z]+\]\*\*\s*/, '')
|
|
39
|
+
.replace(/^(?:📌안내\s*)?\d{4}-\d\d-\d\d\s+\d\d:\d\d\s+KST\s*/, '');
|
|
40
|
+
return redactSecretText(withoutPrefix).trim().slice(0, 120);
|
|
41
|
+
}
|
|
42
|
+
|
|
34
43
|
/** Mirror the sender's ordered envelope fields; only the first line enters this parser. */
|
|
35
44
|
export function parseCoordHeader(seat: string, header: string): Omit<CoordEventInput, 'url' | 'at'> {
|
|
36
45
|
const first = header.split(/\r?\n/, 1)[0] ?? '';
|
|
@@ -69,10 +78,7 @@ export function recordCoordEvent(input: CoordEventInput, deps: { db?: Database }
|
|
|
69
78
|
try {
|
|
70
79
|
db = deps.db ?? openSurfaceEventsDb();
|
|
71
80
|
const at = input.at ?? new Date().toISOString();
|
|
72
|
-
const
|
|
73
|
-
const withoutPrefix = first.replace(/^\*\*\[[A-Z]+\]\*\*\s*/, '')
|
|
74
|
-
.replace(/^(?:📌안내\s*)?\d{4}-\d\d-\d\d\s+\d\d:\d\d\s+KST\s*/, '');
|
|
75
|
-
const headline = redactSecretText(withoutPrefix).trim().slice(0, 120);
|
|
81
|
+
const headline = cleanCoordHeadline(input.headline);
|
|
76
82
|
const safe = (value: string) => redactSecretText(value.split(/\r?\n/, 1)[0] ?? '');
|
|
77
83
|
const refs = { seat: safe(input.seat), recipients: input.recipients.map(safe), all: input.all,
|
|
78
84
|
kind: input.kind === null ? null : safe(input.kind), slot: input.slot === null ? null : safe(input.slot),
|
|
@@ -98,6 +104,28 @@ export function recordCoordEvent(input: CoordEventInput, deps: { db?: Database }
|
|
|
98
104
|
} finally { if (!deps.db) db?.close(); }
|
|
99
105
|
}
|
|
100
106
|
|
|
107
|
+
/** A failed decision mirror must never turn a successful channel/context record into a failure. */
|
|
108
|
+
export function mirrorCoordDecision(input: CoordEventInput, deps: { ledger?: Pick<DecisionLedger, 'recordSeatDecision'> } = {}): void {
|
|
109
|
+
if (input.kind !== '결정') return;
|
|
110
|
+
try {
|
|
111
|
+
if (!registeredSeats().some(({ id }) => id === input.seat)) return;
|
|
112
|
+
const entry = (deps.ledger ?? new DecisionLedger()).recordSeatDecision({
|
|
113
|
+
seat: input.seat as Seat,
|
|
114
|
+
title: input.slot ? redactSecretText(input.slot.split(/\r?\n/, 1)[0] ?? '').trim() : cleanCoordHeadline(input.headline),
|
|
115
|
+
decision: cleanCoordHeadline(input.headline),
|
|
116
|
+
delegation: '조율 채널 — 자리 위임 범위',
|
|
117
|
+
...(input.url ? { refs: [input.url] } : {}),
|
|
118
|
+
decidedAt: input.at ?? new Date().toISOString(),
|
|
119
|
+
});
|
|
120
|
+
try { debug.log('context.coord', 'decision-mirrored', { seat: entry.seat, id: entry.id }); }
|
|
121
|
+
catch { /* observation cannot turn a successful write into a failed send */ }
|
|
122
|
+
} catch (error) {
|
|
123
|
+
if (error instanceof Error && error.message === 'seat decision source already recorded') return;
|
|
124
|
+
try { debug.log('context.coord', 'decision-mirror-failed', { seat: redactSecretText(input.seat) }); }
|
|
125
|
+
catch { /* observation cannot turn a successful send into a failure */ }
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
101
129
|
/** Read-only chronological list; a missing store is an empty ledger, not a newly created one. */
|
|
102
130
|
export function listCoordEvents(opts: { since: string; seat?: string; channelOnly?: boolean }, deps: { db?: Database } = {}): CoordEvent[] {
|
|
103
131
|
if (!deps.db && !existsSync(surfaceEventsDbPath())) return [];
|
|
@@ -18,6 +18,14 @@ export interface MemoryItem {
|
|
|
18
18
|
conflict?: { owner: 'OP'; sources: [string, string] };
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
/** Guardian retention boundary: lifecycle hygiene, independent of librarian promotion. */
|
|
22
|
+
export function guardianRetireAged(items: readonly MemoryItem[], now: Date): MemoryItem[] {
|
|
23
|
+
const cutoff = now.getTime() - 30 * DAY_MS;
|
|
24
|
+
return items.map(item => ({ ...item,
|
|
25
|
+
status: Date.parse(item.updatedAt) < cutoff ? 'retired' as const : item.status,
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
28
|
+
|
|
21
29
|
export type MemorySource =
|
|
22
30
|
| { kind: 'event'; at: string; seat: string; text: string; source: string }
|
|
23
31
|
| { kind: 'decision'; at: string; seat: string; text: string; source: string };
|
|
@@ -28,6 +36,8 @@ export interface MemorySummary {
|
|
|
28
36
|
summary: string;
|
|
29
37
|
/** Only supply this when the two strings identify the same fact and mutually exclusive values. */
|
|
30
38
|
claim?: { key: string; value: string };
|
|
39
|
+
/** Sleep-review task agent's promotion decision; not stored in a MemoryItem. */
|
|
40
|
+
promote?: boolean;
|
|
31
41
|
}
|
|
32
42
|
|
|
33
43
|
type CondenseEvent = CoordEvent & { surface?: string };
|
|
@@ -38,7 +48,7 @@ export interface MemoryDeps {
|
|
|
38
48
|
decisions?: () => DecisionEntry[];
|
|
39
49
|
}
|
|
40
50
|
|
|
41
|
-
export type CondenseSkipReason = 'no-seat' | 'no-project' | 'no-topic' | 'no-summary' | 'bad-link' | 'bad-claim' | 'no-claim' | 'summarize-failed';
|
|
51
|
+
export type CondenseSkipReason = 'not-promoted' | 'no-seat' | 'no-project' | 'no-topic' | 'no-summary' | 'bad-link' | 'bad-claim' | 'no-claim' | 'summarize-failed';
|
|
42
52
|
export type CondenseSkipped = Partial<Record<CondenseSkipReason, number>>;
|
|
43
53
|
export interface FunnelCount { total: number; bySeat: Record<string, number> }
|
|
44
54
|
export interface CondenseFunnel {
|
|
@@ -77,22 +87,25 @@ export function readCondenseEvents(since: string): CondenseEvent[] {
|
|
|
77
87
|
/** Condense one UTC day; callers persist the returned snapshot, not raw transcripts. Read-only ledger access. */
|
|
78
88
|
export async function condenseContextDay(
|
|
79
89
|
day: string, previous: readonly MemoryItem[], deps: MemoryDeps, now: Date = new Date(),
|
|
90
|
+
policy: { promotion?: 'sleep-review'; retention?: 'guardian' } = {},
|
|
80
91
|
): Promise<MemoryItem[]> {
|
|
81
92
|
if (!/^\d{4}-\d\d-\d\d$/.test(day) || new Date(`${day}T00:00:00.000Z`).toISOString().slice(0, 10) !== day) {
|
|
82
93
|
throw new Error('day must be a valid UTC YYYY-MM-DD');
|
|
83
94
|
}
|
|
84
|
-
return condenseContextWindow(`${day}T00:00:00.000Z`, new Date(Date.parse(`${day}T00:00:00.000Z`) + DAY_MS).toISOString(), previous, deps, now);
|
|
95
|
+
return condenseContextWindow(`${day}T00:00:00.000Z`, new Date(Date.parse(`${day}T00:00:00.000Z`) + DAY_MS).toISOString(), previous, deps, now, policy);
|
|
85
96
|
}
|
|
86
97
|
|
|
87
98
|
/** Read-only condensation over an exact rolling UTC window; the caller owns any persistence. */
|
|
88
99
|
export async function condenseContextWindow(
|
|
89
100
|
since: string, until: string, previous: readonly MemoryItem[], deps: MemoryDeps, now: Date = new Date(),
|
|
101
|
+
policy: { promotion?: 'sleep-review'; retention?: 'guardian' } = {},
|
|
90
102
|
): Promise<MemoryItem[]> {
|
|
91
|
-
return (await condenseContextWindowWithReport(since, until, previous, deps, now)).cards;
|
|
103
|
+
return (await condenseContextWindowWithReport(since, until, previous, deps, now, policy)).cards;
|
|
92
104
|
}
|
|
93
105
|
|
|
94
106
|
export async function condenseContextWindowWithReport(
|
|
95
107
|
since: string, until: string, previous: readonly MemoryItem[], deps: MemoryDeps, now: Date = new Date(),
|
|
108
|
+
policy: { promotion?: 'sleep-review'; retention?: 'guardian' } = {},
|
|
96
109
|
): Promise<CondenseWindowReport> {
|
|
97
110
|
const start = Date.parse(since);
|
|
98
111
|
const end = Date.parse(until);
|
|
@@ -228,6 +241,7 @@ export async function condenseContextWindowWithReport(
|
|
|
228
241
|
try { summary = await deps.summarize(source); }
|
|
229
242
|
catch { skip('summarize-failed', source); continue; }
|
|
230
243
|
bump(funnel.summarized, source.seat);
|
|
244
|
+
if (policy.promotion === 'sleep-review' && summary.promote !== true) { skip('not-promoted', source); continue; }
|
|
231
245
|
const project = safeLine(summary?.project);
|
|
232
246
|
const topic = safeLine(summary?.topic);
|
|
233
247
|
const text = safeLine(summary?.summary);
|
|
@@ -253,17 +267,20 @@ export async function condenseContextWindowWithReport(
|
|
|
253
267
|
updatedKeys.add(key);
|
|
254
268
|
}
|
|
255
269
|
const cutoff = now.getTime() - 30 * DAY_MS;
|
|
256
|
-
//
|
|
270
|
+
// The nightly librarian makes promotion judgments; guardian retention runs at the record boundary.
|
|
271
|
+
// The ordinary condense command keeps its existing retention behavior.
|
|
257
272
|
const finalItems: MemoryItem[] = [...items.values()].map(({ conflict: _conflict, ...item }) => ({ ...item,
|
|
258
|
-
status:
|
|
273
|
+
status: policy.retention === 'guardian' ? item.status
|
|
274
|
+
: Date.parse(item.updatedAt) < cutoff ? 'retired' as const : 'active' as const,
|
|
259
275
|
}));
|
|
260
276
|
finalItems.sort((a, b) => Date.parse(a.updatedAt) - Date.parse(b.updatedAt) || a.source.localeCompare(b.source)
|
|
261
277
|
|| a.seat.localeCompare(b.seat));
|
|
262
278
|
for (let index = 0; index < finalItems.length; index++) {
|
|
263
279
|
const item = finalItems[index]!;
|
|
264
|
-
if (item.status !== 'active' || !item.claim) continue;
|
|
280
|
+
if (item.status !== 'active' || !item.claim || (policy.retention === 'guardian' && Date.parse(item.updatedAt) < cutoff)) continue;
|
|
265
281
|
const { key, value } = item.claim;
|
|
266
282
|
const conflicting = finalItems.slice(0, index).find(other => other.status === 'active'
|
|
283
|
+
&& (policy.retention !== 'guardian' || Date.parse(other.updatedAt) >= cutoff)
|
|
267
284
|
&& other.project === item.project
|
|
268
285
|
&& other.claim?.key === key && other.claim.value !== value);
|
|
269
286
|
if (conflicting) {
|