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.
Files changed (315) hide show
  1. package/apps/pwa/out/404/index.html +1 -1
  2. package/apps/pwa/out/404.html +1 -1
  3. package/apps/pwa/out/_next/static/chunks/1307-08b716476f1d50af.js +1 -0
  4. package/apps/pwa/out/_next/static/chunks/{2489-ff55a35b202c0f94.js → 2489-d238d3e2eb5b2bf5.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/3857-5985236d9307f676.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/5633-d6852385000738ef.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/5977-5b9e8a45a5b0c823.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/8857-aa1cc4ac90af4d40.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/9247-55bcfbb0e8057f59.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/app/approvals/page-88459e0903da2803.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-d781be3e3a9cfd74.js → page-d85c43f46f58716b.js} +1 -1
  12. package/apps/pwa/out/_next/static/chunks/app/board/{page-b0a80997903852d8.js → page-4e645600579ae952.js} +1 -1
  13. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-2b1263165630d9c8.js → page-87edbfd19117823b.js} +1 -1
  14. package/apps/pwa/out/_next/static/chunks/app/bots/page-7f0c2b7801ac6a54.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/app/ceo/page-bf1f030a9d1adffd.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/app/chat/{page-e2d6bed48ac20120.js → page-cbaf66b1e1804adf.js} +1 -1
  17. package/apps/pwa/out/_next/static/chunks/app/consult/{page-111cabe4d1fd69ae.js → page-80ed511e02c81412.js} +1 -1
  18. package/apps/pwa/out/_next/static/chunks/app/control/{page-544dd4bd4847eeb7.js → page-9f5610caaa06178c.js} +1 -1
  19. package/apps/pwa/out/_next/static/chunks/app/decisions/{page-3ee191319bf97d87.js → page-ec8f3575e339de74.js} +1 -1
  20. package/apps/pwa/out/_next/static/chunks/app/design-check/page-86d753e460f1a989.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/app/editor/page-4166a7d51cf0a57d.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/app/exec/{page-a449f1ed472b81c2.js → page-2a19c5cd3b562580.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/app/field/{page-9045094d6d3b5cd0.js → page-b6a09cde2e07521a.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/app/inside/{page-c06673596849ef45.js → page-f7eae1518ca0c353.js} +1 -1
  25. package/apps/pwa/out/_next/static/chunks/app/intake/page-456a8573ad811381.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/app/{layout-9f81233f29dcd409.js → layout-8462d6a543cbb2d9.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/app/live/page-2974cdfb87c0f9c9.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/app/live-run/page-f927ffa25f3c86d1.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/app/loops/page-abd86cd832aaa011.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/app/market/page-79daf68f6256adaa.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-8c3f168fc3f91939.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/app/morning/{page-7a12f61be4a579d1.js → page-786fb4504d0f2fa9.js} +1 -1
  33. package/apps/pwa/out/_next/static/chunks/app/{not-found-c85b0589591a99c9.js → not-found-75209f14597dcb8f.js} +1 -1
  34. package/apps/pwa/out/_next/static/chunks/app/observatory/{page-725a4da02dd129eb.js → page-bf30affe3a85af7e.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1187a44b7515434b.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-3d0910185a66ed54.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-f401ee1dd5a26e25.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/app/outputs/page-5dfe7604fd842784.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/app/{page-21bec4afeb2f0917.js → page-76cf838050a8d681.js} +1 -1
  40. package/apps/pwa/out/_next/static/chunks/app/reflection/{page-dc39ee7a36d26385.js → page-7ec8e289b4c14df6.js} +1 -1
  41. package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-84ae759f76053672.js → page-8c8ac17d52be2a79.js} +1 -1
  42. package/apps/pwa/out/_next/static/chunks/app/sessions/{page-3db58ddb61fe2be9.js → page-d359ab048d63a9d9.js} +1 -1
  43. package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-c5315e30f3528dc4.js → page-b8b345c880bf78d1.js} +1 -1
  44. package/apps/pwa/out/_next/static/chunks/app/settings/{page-2fe368efb6de3b6a.js → page-f151cc10ad026940.js} +1 -1
  45. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-9875f103057da433.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/app/setup/page-9789328af9a9aa62.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/app/share/{page-c69762987b751174.js → page-bf495a1e284f4e00.js} +1 -1
  48. package/apps/pwa/out/_next/static/chunks/app/showroom/page-a263d94bca4be22a.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/tasks/page-3962eab8ec58d16e.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/term/page-9abcfac07e02a542.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/today/page-bee49f927c333abb.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/trace/page-bac2d56dcb02d955.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/vault/page-937cbcc13176295c.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d09495594f7f480d.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/workflows/{page-7f8d7fcdf058a845.js → page-69cb93d1a2e57e0e.js} +1 -1
  56. package/apps/pwa/out/_next/static/chunks/app/workspace/{page-22163556cd38bc88.js → page-72ec543928d67c3e.js} +1 -1
  57. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-82906eeda0186a7b.js +1 -0
  58. package/apps/pwa/out/_next/static/css/f2bd5257e6af88d8.css +1 -0
  59. package/apps/pwa/out/approvals/index.html +1 -1
  60. package/apps/pwa/out/approvals/index.txt +14 -14
  61. package/apps/pwa/out/autopilot/index.html +1 -1
  62. package/apps/pwa/out/autopilot/index.txt +14 -14
  63. package/apps/pwa/out/board/index.html +1 -1
  64. package/apps/pwa/out/board/index.txt +14 -14
  65. package/apps/pwa/out/botlab/index.html +1 -1
  66. package/apps/pwa/out/botlab/index.txt +14 -14
  67. package/apps/pwa/out/bots/index.html +1 -1
  68. package/apps/pwa/out/bots/index.txt +14 -14
  69. package/apps/pwa/out/ceo/index.html +1 -1
  70. package/apps/pwa/out/ceo/index.txt +14 -14
  71. package/apps/pwa/out/chat/index.html +1 -1
  72. package/apps/pwa/out/chat/index.txt +14 -14
  73. package/apps/pwa/out/consult/index.html +1 -1
  74. package/apps/pwa/out/consult/index.txt +14 -14
  75. package/apps/pwa/out/control/index.html +1 -1
  76. package/apps/pwa/out/control/index.txt +14 -14
  77. package/apps/pwa/out/decisions/index.html +1 -1
  78. package/apps/pwa/out/decisions/index.txt +14 -14
  79. package/apps/pwa/out/design-check/index.html +1 -1
  80. package/apps/pwa/out/design-check/index.txt +14 -14
  81. package/apps/pwa/out/editor/index.html +1 -1
  82. package/apps/pwa/out/editor/index.txt +14 -14
  83. package/apps/pwa/out/exec/index.html +1 -1
  84. package/apps/pwa/out/exec/index.txt +14 -14
  85. package/apps/pwa/out/field/index.html +1 -1
  86. package/apps/pwa/out/field/index.txt +14 -14
  87. package/apps/pwa/out/healthz +1 -1
  88. package/apps/pwa/out/index.html +1 -1
  89. package/apps/pwa/out/index.txt +14 -14
  90. package/apps/pwa/out/inside/index.html +1 -1
  91. package/apps/pwa/out/inside/index.txt +14 -14
  92. package/apps/pwa/out/intake/index.html +1 -1
  93. package/apps/pwa/out/intake/index.txt +14 -14
  94. package/apps/pwa/out/live/index.html +1 -1
  95. package/apps/pwa/out/live/index.txt +14 -14
  96. package/apps/pwa/out/live-run/index.html +1 -1
  97. package/apps/pwa/out/live-run/index.txt +14 -14
  98. package/apps/pwa/out/loops/index.html +1 -1
  99. package/apps/pwa/out/loops/index.txt +14 -14
  100. package/apps/pwa/out/market/index.html +1 -1
  101. package/apps/pwa/out/market/index.txt +14 -14
  102. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  103. package/apps/pwa/out/missions/placeholder/index.txt +14 -14
  104. package/apps/pwa/out/morning/index.html +1 -1
  105. package/apps/pwa/out/morning/index.txt +14 -14
  106. package/apps/pwa/out/observatory/index.html +1 -1
  107. package/apps/pwa/out/observatory/index.txt +14 -14
  108. package/apps/pwa/out/ops/checklist/index.html +1 -1
  109. package/apps/pwa/out/ops/checklist/index.txt +14 -14
  110. package/apps/pwa/out/ops/release/index.html +1 -1
  111. package/apps/pwa/out/ops/release/index.txt +14 -14
  112. package/apps/pwa/out/ops/seats/index.html +1 -1
  113. package/apps/pwa/out/ops/seats/index.txt +14 -14
  114. package/apps/pwa/out/outputs/index.html +1 -1
  115. package/apps/pwa/out/outputs/index.txt +14 -14
  116. package/apps/pwa/out/reflection/index.html +1 -1
  117. package/apps/pwa/out/reflection/index.txt +15 -15
  118. package/apps/pwa/out/scheduler/index.html +1 -1
  119. package/apps/pwa/out/scheduler/index.txt +14 -14
  120. package/apps/pwa/out/sessions/index.html +1 -1
  121. package/apps/pwa/out/sessions/index.txt +14 -14
  122. package/apps/pwa/out/settings/devices/index.html +1 -1
  123. package/apps/pwa/out/settings/devices/index.txt +14 -14
  124. package/apps/pwa/out/settings/index.html +1 -1
  125. package/apps/pwa/out/settings/index.txt +14 -14
  126. package/apps/pwa/out/setup/done/index.html +1 -1
  127. package/apps/pwa/out/setup/done/index.txt +14 -14
  128. package/apps/pwa/out/setup/index.html +1 -1
  129. package/apps/pwa/out/setup/index.txt +14 -14
  130. package/apps/pwa/out/share/index.html +1 -1
  131. package/apps/pwa/out/share/index.txt +14 -14
  132. package/apps/pwa/out/showroom/index.html +1 -1
  133. package/apps/pwa/out/showroom/index.txt +14 -14
  134. package/apps/pwa/out/tasks/index.html +1 -1
  135. package/apps/pwa/out/tasks/index.txt +14 -14
  136. package/apps/pwa/out/term/index.html +1 -1
  137. package/apps/pwa/out/term/index.txt +14 -14
  138. package/apps/pwa/out/today/index.html +1 -1
  139. package/apps/pwa/out/today/index.txt +14 -14
  140. package/apps/pwa/out/trace/index.html +1 -1
  141. package/apps/pwa/out/trace/index.txt +14 -14
  142. package/apps/pwa/out/vault/index.html +1 -1
  143. package/apps/pwa/out/vault/index.txt +14 -14
  144. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  145. package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
  146. package/apps/pwa/out/workflows/index.html +1 -1
  147. package/apps/pwa/out/workflows/index.txt +14 -14
  148. package/apps/pwa/out/workspace/index.html +1 -1
  149. package/apps/pwa/out/workspace/index.txt +14 -14
  150. package/apps/pwa/out/worktrees/index.html +1 -1
  151. package/apps/pwa/out/worktrees/index.txt +14 -14
  152. package/graphs/catalog/catalog.yaml +1 -1
  153. package/graphs/drafts/recipes.yaml +2 -2
  154. package/graphs/test-diet/recipes.yaml +3 -0
  155. package/graphs/test-diet/test-diet.yaml +3 -1
  156. package/package.json +1 -1
  157. package/scripts/f-session-monitor.ts +69 -0
  158. package/scripts/hq/standby-snapshot.ts +7 -3
  159. package/scripts/knowledge-compress.ts +11 -0
  160. package/scripts/nightly-docops.ts +90 -3
  161. package/scripts/research-coo-admin-dates.ts +39 -0
  162. package/scripts/skills/public-check.ts +7 -2
  163. package/scripts/smart-merge/resync-shadow.ts +5 -3
  164. package/scripts/test-diet/lib.ts +81 -1
  165. package/scripts/test-diet/node.ts +48 -2
  166. package/scripts/verify-coo-admin-evidence.ts +83 -0
  167. package/src/acp/client.ts +4 -2
  168. package/src/agent/run-context.ts +4 -0
  169. package/src/agent-env/profile.ts +25 -5
  170. package/src/agent-mission/driver.ts +26 -12
  171. package/src/agent-mission/supervisor-metrics.ts +17 -2
  172. package/src/agent-substrate/acp-reviewer.ts +12 -5
  173. package/src/agent-substrate/pr-reviewer.ts +11 -4
  174. package/src/agent-substrate/review-intent.ts +102 -3
  175. package/src/autopilot/build/llm-conflict-merge.ts +22 -8
  176. package/src/autopilot/pr-manager.ts +18 -8
  177. package/src/away/release-watch.ts +47 -8
  178. package/src/briefing/brief-items.ts +104 -11
  179. package/src/chat/index.ts +1 -0
  180. package/src/cli/brief-cli.ts +20 -2
  181. package/src/cli/config-classify.ts +83 -0
  182. package/src/cli/control-cli.ts +14 -0
  183. package/src/cli/decisions-cli.ts +26 -16
  184. package/src/cli/logs-model-input.ts +1 -1
  185. package/src/cli/pr-cli.ts +65 -8
  186. package/src/cli/release-cli.ts +21 -4
  187. package/src/cli/tasks-cli.ts +108 -1
  188. package/src/connectors/types.ts +6 -6
  189. package/src/context-bus/context-now-surfaces.ts +18 -0
  190. package/src/context-bus/coord-events.ts +32 -4
  191. package/src/context-bus/long-term-memory.ts +23 -6
  192. package/src/context-bus/nightly-condense.ts +169 -21
  193. package/src/control/control-status.ts +87 -0
  194. package/src/control-plane/light-member.ts +5 -3
  195. package/src/control-plane/member.ts +172 -7
  196. package/src/core-turn/run-goal-loop.ts +22 -1
  197. package/src/dashboard/slash-runtime/dashboard-handlers.ts +9 -0
  198. package/src/decisions/decision-cards.ts +20 -1
  199. package/src/decisions/decision-ledger.ts +11 -8
  200. package/src/decisions/web-push-decision-cards.ts +19 -3
  201. package/src/discord-self-message.ts +2 -2
  202. package/src/domains/coo-admin-dates.evidence.json +876 -0
  203. package/src/domains/coo-admin-tool.ts +48 -14
  204. package/src/domains/ops-status.ts +53 -0
  205. package/src/domains/outbound-alert.ts +24 -1
  206. package/src/exec-requests/planner.ts +6 -0
  207. package/src/graph-kinds/registry.ts +6 -6
  208. package/src/graph-runner/graph-cli.ts +34 -2
  209. package/src/graph-runner/graph-run-api.ts +7 -0
  210. package/src/graph-runner/graph-tick.ts +73 -12
  211. package/src/graph-runner/runner.ts +43 -11
  212. package/src/graph-unify/wf-to-graph.ts +79 -0
  213. package/src/graph-wizard/generate.ts +34 -2
  214. package/src/graph-wizard/steps.ts +13 -0
  215. package/src/harness/harness-cli-command.ts +121 -23
  216. package/src/harness/harness-queue.ts +58 -14
  217. package/src/harness/launch-advice.ts +23 -0
  218. package/src/index.ts +36 -7
  219. package/src/knowledge/conclusion-compress.ts +93 -0
  220. package/src/knowledge/know-find.ts +9 -2
  221. package/src/knowledge/query.ts +20 -0
  222. package/src/llm.ts +26 -16
  223. package/src/loops/neighbor-runtime.ts +88 -0
  224. package/src/loops/orchestrator/author-depth.ts +29 -11
  225. package/src/loops/orchestrator/overlap.ts +7 -1
  226. package/src/loops/orchestrator/resync-shadow.ts +1 -1
  227. package/src/loops/orchestrator/tick.ts +35 -2
  228. package/src/loops/orchestrator/traffic.ts +2 -0
  229. package/src/loops/registry.ts +5 -0
  230. package/src/market/publish.ts +17 -1
  231. package/src/maturity/feature-maturity.ts +2 -1
  232. package/src/mss/logging/redaction.ts +17 -0
  233. package/src/nexus/api/draft-metrics.ts +22 -8
  234. package/src/nexus/api/graph-peer-edit.ts +77 -0
  235. package/src/nexus/api/graph-wizard-api.ts +9 -1
  236. package/src/nexus/api/graphs-api.ts +145 -7
  237. package/src/nexus/api/http-server.ts +93 -8
  238. package/src/nexus/api/plugins-market.ts +24 -0
  239. package/src/nexus/index.ts +3 -2
  240. package/src/oauth/claude-pty-maturity.ts +24 -0
  241. package/src/oauth/fallback-chain.ts +27 -14
  242. package/src/plugins/maker/market-bundle.ts +9 -0
  243. package/src/plugins/maker/plugin-maker.ts +9 -3
  244. package/src/plugins/maker/wizard-generate.ts +73 -9
  245. package/src/policy/llm-policy.ts +11 -6
  246. package/src/release-loop/checklist-board.ts +27 -0
  247. package/src/release-loop/checklist.ts +38 -0
  248. package/src/seat-address/seat-address.ts +1 -0
  249. package/src/seat-loop/seat-loop.ts +174 -20
  250. package/src/seat-loop/seat-report.ts +29 -1
  251. package/src/self-dev/dev-cli.ts +90 -8
  252. package/src/self-dev/dev-pipeline.ts +20 -1
  253. package/src/self-dev/draft-sweep.ts +45 -8
  254. package/src/self-dev/orchestrate.ts +3 -0
  255. package/src/self-dev/review-provider-fallback.ts +83 -0
  256. package/src/self-dev/run-supervisor.ts +5 -1
  257. package/src/self-implement/harness-policy-gates.ts +7 -5
  258. package/src/self-implement/host-regate.ts +28 -14
  259. package/src/self-implement/l8-merge-queue.ts +80 -8
  260. package/src/self-implement/orchestrator.ts +68 -27
  261. package/src/self-implement/seams.ts +5 -3
  262. package/src/self-implement/self-implement-cli.ts +4 -0
  263. package/src/shell-env-bootstrap.ts +19 -4
  264. package/src/steward/triage.ts +25 -8
  265. package/src/storage/file-lock.ts +33 -4
  266. package/src/task-agent/completion-evidence.ts +31 -3
  267. package/src/task-agent/cover.ts +61 -0
  268. package/src/task-agent/live-moves.ts +126 -3
  269. package/src/task-agent/shadow.ts +19 -4
  270. package/src/task-agent/task-hand.ts +2 -0
  271. package/src/task-orchestrator/surfaces/pod-pool.ts +138 -15
  272. package/src/task-orchestrator/surfaces/self-implement.ts +7 -0
  273. package/src/telegram-commands.ts +8 -0
  274. package/src/telegram-project-command.ts +6 -0
  275. package/src/telegram.ts +2 -2
  276. package/src/typecheck-ratchet.ts +2 -1
  277. package/src/user-config.ts +35 -4
  278. package/src/version/packed-revision.json +1 -1
  279. package/src/workflow-runtime/executor.ts +24 -2
  280. package/src/workflow-runtime/triggers/registry.ts +27 -0
  281. package/src/workflow-runtime/triggers/schedule-source.ts +30 -3
  282. package/apps/pwa/out/_next/static/chunks/1307-c97eba9fb72a9a5e.js +0 -1
  283. package/apps/pwa/out/_next/static/chunks/3857-995ea9f5ef33d71e.js +0 -1
  284. package/apps/pwa/out/_next/static/chunks/5977-59fedea50d13c762.js +0 -1
  285. package/apps/pwa/out/_next/static/chunks/8857-f1762140ef9a96b1.js +0 -1
  286. package/apps/pwa/out/_next/static/chunks/9247-7581825d511a64c0.js +0 -1
  287. package/apps/pwa/out/_next/static/chunks/9497-b0e758d1cd4ae8d7.js +0 -1
  288. package/apps/pwa/out/_next/static/chunks/app/approvals/page-937c92292a4ab4f0.js +0 -1
  289. package/apps/pwa/out/_next/static/chunks/app/bots/page-ea998f7f5d156bea.js +0 -1
  290. package/apps/pwa/out/_next/static/chunks/app/ceo/page-33c328b64ca508f8.js +0 -1
  291. package/apps/pwa/out/_next/static/chunks/app/design-check/page-e4bcb2c503dab04b.js +0 -1
  292. package/apps/pwa/out/_next/static/chunks/app/editor/page-300897dcbbd5ed76.js +0 -1
  293. package/apps/pwa/out/_next/static/chunks/app/intake/page-103fa3191c1b2e08.js +0 -1
  294. package/apps/pwa/out/_next/static/chunks/app/live/page-be8b4615c919e36c.js +0 -1
  295. package/apps/pwa/out/_next/static/chunks/app/live-run/page-c685b104e0dfa609.js +0 -1
  296. package/apps/pwa/out/_next/static/chunks/app/loops/page-8d693331cfbb6add.js +0 -1
  297. package/apps/pwa/out/_next/static/chunks/app/market/page-cef4f30a4c22fae1.js +0 -1
  298. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66ea1b54fc83fa15.js +0 -1
  299. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-8cc707bd09a497d9.js +0 -1
  300. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-90b55be1f15ce0e8.js +0 -1
  301. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cb16606e08be65db.js +0 -1
  302. package/apps/pwa/out/_next/static/chunks/app/outputs/page-16dc64b74ed6a40a.js +0 -1
  303. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-8d04132dd0ac5e10.js +0 -1
  304. package/apps/pwa/out/_next/static/chunks/app/setup/page-f23484de467c9ea3.js +0 -1
  305. package/apps/pwa/out/_next/static/chunks/app/showroom/page-5456692bda39aafb.js +0 -1
  306. package/apps/pwa/out/_next/static/chunks/app/tasks/page-d75f7cd65845ce98.js +0 -1
  307. package/apps/pwa/out/_next/static/chunks/app/term/page-5603f5e568b25ce3.js +0 -1
  308. package/apps/pwa/out/_next/static/chunks/app/today/page-d2cc0f4e89f42028.js +0 -1
  309. package/apps/pwa/out/_next/static/chunks/app/trace/page-a6eb73b8a4840f1c.js +0 -1
  310. package/apps/pwa/out/_next/static/chunks/app/vault/page-c0a3f4d10edb83fa.js +0 -1
  311. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-6c0b88068c143f15.js +0 -1
  312. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-141436fd26dd56fc.js +0 -1
  313. package/apps/pwa/out/_next/static/css/e81df11490b4413b.css +0 -1
  314. /package/apps/pwa/out/_next/static/{MObU8ujkMciAKr6ljAV05 → aBi6EyMtLsrl7J8eH4kF7}/_buildManifest.js +0 -0
  315. /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
- const upsert = manager.upsertPr({
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')
@@ -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
- .action(async (doc: string, _opts: unknown, cmd: Command) => {
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 rows = cellsReferencingDoc(doc, features.assignedVersions(ledgerRoot).map((version) => listChecklist(version, ledgerRoot)));
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 });
@@ -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>', '상태로 필터')
@@ -1,5 +1,5 @@
1
- export interface ExternalTaskEvent {
2
- provider: 'linear' | 'asana';
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: ExternalTaskEvent['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 first = input.headline.split(/\r?\n/, 1)[0] ?? '';
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
- // Compare only the winning, non-retired assertions; a superseded claim cannot keep a decision candidate alive.
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: Date.parse(item.updatedAt) < cutoff ? 'retired' as const : 'active' as const,
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) {