elanous 0.2.16 → 0.2.18

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 (354) 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/P6YTVgzuZz5oTZhY4Din_/_buildManifest.js +1 -0
  4. package/apps/pwa/out/_next/static/chunks/{1307-b46d45b705e8c9ae.js → 1307-30dff22c9e1f3073.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/195-fb1d7690bf30bab4.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/1986-33e13d648a9df765.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/2537-6c289376caa88ca1.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/2699-325cc923ba7b8631.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/3857-4f936cdb30322aa6.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/4554-5761612531d0146b.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/4565-3d7e3fdeb43686a8.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/5977-a11f37254196b659.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/{6158.9c0449a9a8d0064b.js → 6158.af2e93b7adff709b.js} +1 -1
  14. package/apps/pwa/out/_next/static/chunks/6877-74399f69c7da1433.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/7797-df5aa0381fdc078f.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/8401-8615a660c84cdb03.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/8695-846034ce44852c70.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/9247-269e4464cc4b491c.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/app/approvals/page-d92f7ee3994e84c7.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-3cb52e5ea8fc2fa5.js → page-4733c967b56a4277.js} +1 -1
  21. package/apps/pwa/out/_next/static/chunks/app/board/page-d22fd849cc22f104.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-c1e20d7d338713f1.js → page-bb742f995c6769e7.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/app/bots/{page-482c5dbde699947c.js → page-02b073a2ac793b83.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/app/ceo/page-60c3da708f9a0ccc.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/app/chat/page-8a9b3594bc23289b.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/app/consult/page-a0eb02638b1e6f31.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/app/control/{page-a0e2a6d1bfb52fc5.js → page-308111abadf35670.js} +1 -1
  28. package/apps/pwa/out/_next/static/chunks/app/decisions/page-6eaf7bac37830c21.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/app/design-check/{page-c53598cfce6156f2.js → page-5d97b2901e0d1ea6.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/app/editor/{page-13a63953f53c6a23.js → page-c39554c321576982.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/app/exec/{page-5d3c8429d04f3831.js → page-8c3d8e63ae2e1c98.js} +1 -1
  32. package/apps/pwa/out/_next/static/chunks/app/field/{page-3193bdc0bbab047a.js → page-9b72a07d424701c6.js} +1 -1
  33. package/apps/pwa/out/_next/static/chunks/app/inside/page-bd5d586429167fea.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/app/intake/{page-6107ca8ddd5e3bdd.js → page-a572f58cd3ca5864.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/app/layout-59b7834d497c2255.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/app/live/page-be5ea965a07fbc41.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/app/loops/page-c130b63862437814.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/app/market/page-fa244bd146a00ae8.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-44d56d6391965a6a.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/app/morning/{page-4167348049166852.js → page-83a700d4c754d802.js} +1 -1
  41. package/apps/pwa/out/_next/static/chunks/app/{not-found-de52e8fdf9095f7a.js → not-found-7b3170f374f77b9d.js} +1 -1
  42. package/apps/pwa/out/_next/static/chunks/app/observatory/page-5f23d0c66a3b4793.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-ca3952cd78a83ca2.js → page-a3c58f5c72a5f3f9.js} +1 -1
  44. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-a2c4625596f8d69c.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-0e9099531d239f11.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/app/outputs/page-e8e1ecaea7cd71d2.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/app/page-a1b7ffed710a93e0.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/reflection/{page-1ebfe049c640e171.js → page-1467047104141297.js} +1 -1
  49. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-2da6f9b8a38c6978.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/sessions/page-102310930791197a.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-f331e38fefc00393.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/settings/page-6ed0b73bae8351d8.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-abe8e3b74bea1ffc.js → page-44d5f767491692bf.js} +1 -1
  54. package/apps/pwa/out/_next/static/chunks/app/setup/page-43336055451f6e37.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/share/{page-d28573d6792b4275.js → page-3cc47dca0922ab9a.js} +1 -1
  56. package/apps/pwa/out/_next/static/chunks/app/showroom/page-a77ee355a2304ff0.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/tasks/{page-d751de5f8062ff5e.js → page-b17cd46cf0e9b8cf.js} +1 -1
  58. package/apps/pwa/out/_next/static/chunks/app/term/{page-fe1759f94003b32b.js → page-61553851aa606146.js} +1 -1
  59. package/apps/pwa/out/_next/static/chunks/app/today/{page-d64c984e855c8c68.js → page-a1f585e154e61c59.js} +1 -1
  60. package/apps/pwa/out/_next/static/chunks/app/trace/page-7bf54c9bb9274af8.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/vault/{page-9afdee66b820c76e.js → page-5150ef17b5383a57.js} +1 -1
  62. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b9d8ce70792c1672.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/workflows/page-a5a3de231f2cc7d5.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/workspace/{page-3544f154c87b43ee.js → page-134da468538baf48.js} +1 -1
  65. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-31ac4afa2dcd915c.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/{webpack-539cde99554be20f.js → webpack-c2782b330447ffb6.js} +1 -1
  67. package/apps/pwa/out/_next/static/css/7fa358fec7da7c47.css +1 -0
  68. package/apps/pwa/out/approvals/index.html +1 -1
  69. package/apps/pwa/out/approvals/index.txt +14 -14
  70. package/apps/pwa/out/autopilot/index.html +1 -1
  71. package/apps/pwa/out/autopilot/index.txt +14 -14
  72. package/apps/pwa/out/board/index.html +1 -1
  73. package/apps/pwa/out/board/index.txt +14 -14
  74. package/apps/pwa/out/botlab/index.html +1 -1
  75. package/apps/pwa/out/botlab/index.txt +14 -14
  76. package/apps/pwa/out/bots/index.html +1 -1
  77. package/apps/pwa/out/bots/index.txt +14 -14
  78. package/apps/pwa/out/ceo/index.html +1 -1
  79. package/apps/pwa/out/ceo/index.txt +14 -14
  80. package/apps/pwa/out/chat/index.html +1 -1
  81. package/apps/pwa/out/chat/index.txt +14 -14
  82. package/apps/pwa/out/consult/index.html +1 -1
  83. package/apps/pwa/out/consult/index.txt +14 -14
  84. package/apps/pwa/out/control/index.html +1 -1
  85. package/apps/pwa/out/control/index.txt +14 -14
  86. package/apps/pwa/out/decisions/index.html +1 -0
  87. package/apps/pwa/out/decisions/index.txt +30 -0
  88. package/apps/pwa/out/design-check/index.html +1 -1
  89. package/apps/pwa/out/design-check/index.txt +14 -14
  90. package/apps/pwa/out/editor/index.html +1 -1
  91. package/apps/pwa/out/editor/index.txt +14 -14
  92. package/apps/pwa/out/exec/index.html +1 -1
  93. package/apps/pwa/out/exec/index.txt +14 -14
  94. package/apps/pwa/out/field/index.html +1 -1
  95. package/apps/pwa/out/field/index.txt +14 -14
  96. package/apps/pwa/out/healthz +1 -1
  97. package/apps/pwa/out/index.html +1 -1
  98. package/apps/pwa/out/index.txt +14 -14
  99. package/apps/pwa/out/inside/index.html +1 -1
  100. package/apps/pwa/out/inside/index.txt +14 -14
  101. package/apps/pwa/out/intake/index.html +1 -1
  102. package/apps/pwa/out/intake/index.txt +14 -14
  103. package/apps/pwa/out/live/index.html +1 -1
  104. package/apps/pwa/out/live/index.txt +14 -14
  105. package/apps/pwa/out/loops/index.html +1 -1
  106. package/apps/pwa/out/loops/index.txt +14 -14
  107. package/apps/pwa/out/market/index.html +1 -1
  108. package/apps/pwa/out/market/index.txt +14 -14
  109. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  110. package/apps/pwa/out/missions/placeholder/index.txt +14 -14
  111. package/apps/pwa/out/morning/index.html +1 -1
  112. package/apps/pwa/out/morning/index.txt +14 -14
  113. package/apps/pwa/out/observatory/index.html +1 -1
  114. package/apps/pwa/out/observatory/index.txt +14 -14
  115. package/apps/pwa/out/ops/checklist/index.html +1 -1
  116. package/apps/pwa/out/ops/checklist/index.txt +14 -14
  117. package/apps/pwa/out/ops/release/index.html +1 -1
  118. package/apps/pwa/out/ops/release/index.txt +14 -14
  119. package/apps/pwa/out/ops/seats/index.html +1 -1
  120. package/apps/pwa/out/ops/seats/index.txt +14 -14
  121. package/apps/pwa/out/outputs/index.html +1 -1
  122. package/apps/pwa/out/outputs/index.txt +14 -14
  123. package/apps/pwa/out/reflection/index.html +1 -1
  124. package/apps/pwa/out/reflection/index.txt +15 -15
  125. package/apps/pwa/out/scheduler/index.html +1 -1
  126. package/apps/pwa/out/scheduler/index.txt +14 -14
  127. package/apps/pwa/out/sessions/index.html +1 -1
  128. package/apps/pwa/out/sessions/index.txt +14 -14
  129. package/apps/pwa/out/settings/devices/index.html +1 -1
  130. package/apps/pwa/out/settings/devices/index.txt +14 -14
  131. package/apps/pwa/out/settings/index.html +1 -1
  132. package/apps/pwa/out/settings/index.txt +14 -14
  133. package/apps/pwa/out/setup/done/index.html +1 -1
  134. package/apps/pwa/out/setup/done/index.txt +14 -14
  135. package/apps/pwa/out/setup/index.html +1 -1
  136. package/apps/pwa/out/setup/index.txt +14 -14
  137. package/apps/pwa/out/share/index.html +1 -1
  138. package/apps/pwa/out/share/index.txt +14 -14
  139. package/apps/pwa/out/showroom/index.html +1 -1
  140. package/apps/pwa/out/showroom/index.txt +14 -14
  141. package/apps/pwa/out/tasks/index.html +1 -1
  142. package/apps/pwa/out/tasks/index.txt +14 -14
  143. package/apps/pwa/out/term/index.html +1 -1
  144. package/apps/pwa/out/term/index.txt +14 -14
  145. package/apps/pwa/out/today/index.html +1 -1
  146. package/apps/pwa/out/today/index.txt +14 -14
  147. package/apps/pwa/out/trace/index.html +1 -1
  148. package/apps/pwa/out/trace/index.txt +14 -14
  149. package/apps/pwa/out/vault/index.html +1 -1
  150. package/apps/pwa/out/vault/index.txt +14 -14
  151. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  152. package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
  153. package/apps/pwa/out/workflows/index.html +1 -1
  154. package/apps/pwa/out/workflows/index.txt +14 -14
  155. package/apps/pwa/out/workspace/index.html +1 -1
  156. package/apps/pwa/out/workspace/index.txt +14 -14
  157. package/apps/pwa/out/worktrees/index.html +1 -1
  158. package/apps/pwa/out/worktrees/index.txt +14 -14
  159. package/graphs/intake/intake-daily.yaml +9 -2
  160. package/graphs/intake/news-sources.yaml +4 -0
  161. package/graphs/intake/recipes.yaml +6 -1
  162. package/package.json +2 -1
  163. package/samples/workflows/templates/approval-gated-deploy.yaml +30 -0
  164. package/samples/workflows/templates/chat-driven-research.yaml +35 -0
  165. package/samples/workflows/templates/discord-listen-and-respond.yaml +29 -0
  166. package/samples/workflows/templates/http-poll-then-alert.yaml +30 -0
  167. package/samples/workflows/templates/manual-quickstart.yaml +21 -0
  168. package/samples/workflows/templates/scheduled-daily-digest.yaml +30 -0
  169. package/samples/workflows/templates/webhook-deploy-hook.yaml +27 -0
  170. package/scripts/__fixtures__/intake-news.xml +7 -0
  171. package/scripts/ci-public-export-import-gate.ts +7 -2
  172. package/scripts/ci-typecheck-changed.ts +21 -6
  173. package/scripts/discord-seat-probe.ts +3 -2
  174. package/scripts/docs/generate-cli-anatomy.ts +162 -0
  175. package/scripts/goals/authoring-metrics.ts +73 -0
  176. package/scripts/intake-news.ts +169 -0
  177. package/scripts/landing-heal/must-fix-match.ts +7 -7
  178. package/scripts/landing-heal/run-mission.ts +3 -2
  179. package/scripts/public-export-test-run.ts +37 -2
  180. package/scripts/release-loop/checklist-gate-node.ts +13 -11
  181. package/scripts/release-loop/cut-branch.ts +2 -1
  182. package/scripts/release-loop/cutoff.ts +4 -2
  183. package/scripts/release-loop/docs-land-node.ts +77 -11
  184. package/scripts/release-loop/docs-node.ts +1 -1
  185. package/scripts/release-loop/gate-node.ts +99 -14
  186. package/scripts/release-loop/node-verdict.ts +2 -1
  187. package/scripts/release-loop/npm-publish-node.ts +11 -7
  188. package/scripts/release-loop/ops-upgrade-node.ts +8 -1
  189. package/scripts/release-loop/preflight.ts +246 -0
  190. package/scripts/release-loop/publish-node.ts +10 -3
  191. package/scripts/release-loop/release-docs.ts +134 -0
  192. package/scripts/release-loop/release-readiness.ts +11 -6
  193. package/scripts/release-loop/release-version.ts +48 -0
  194. package/scripts/release-loop/resume-release.ts +115 -0
  195. package/scripts/release-loop/unattended-release.ts +43 -7
  196. package/scripts/release-loop/version-node.ts +149 -41
  197. package/scripts/release-story/draft.ts +42 -14
  198. package/scripts/rhythm/daily.ts +25 -4
  199. package/scripts/skills/public-check.ts +62 -12
  200. package/scripts/test-deterministic.ts +51 -33
  201. package/scripts/test-diet/effectiveness.ts +62 -1
  202. package/scripts/test-diet/lib.ts +1 -1
  203. package/scripts/test-diet/node.ts +8 -1
  204. package/src/agent-mission/driver.ts +53 -10
  205. package/src/agent-mission/review-loop.ts +4 -3
  206. package/src/agent-substrate/pr-comment-meta.ts +20 -0
  207. package/src/autopilot/build/llm-conflict-merge.ts +28 -8
  208. package/src/autopilot/track-agent.ts +5 -3
  209. package/src/briefing/brief-items.ts +86 -17
  210. package/src/cli/autopilot-cli.ts +86 -1
  211. package/src/cli/brief-cli.ts +36 -2
  212. package/src/cli/decisions-cli.ts +4 -1
  213. package/src/cli/freeze-cli.ts +3 -2
  214. package/src/cli/intake-cli.ts +74 -2
  215. package/src/cli/log-event-names.ts +1 -0
  216. package/src/cli/notify-cli.ts +75 -0
  217. package/src/cli/pr-cli.ts +12 -1
  218. package/src/cli/provider-cli.ts +24 -0
  219. package/src/cli/release-cli.ts +101 -17
  220. package/src/cli/self-update-canary.ts +46 -0
  221. package/src/cli/self-update.ts +200 -1
  222. package/src/cli/tasks-cli.ts +309 -3
  223. package/src/context-bus/condense.ts +19 -3
  224. package/src/context-bus/context-day.ts +77 -24
  225. package/src/context-bus/context-first.ts +33 -0
  226. package/src/context-card/index.ts +45 -0
  227. package/src/context-card/task.ts +76 -0
  228. package/src/dashboard/slash-runtime/dashboard-handlers.ts +2 -0
  229. package/src/decisions/decision-ledger.ts +10 -1
  230. package/src/discord-self-message.ts +20 -2
  231. package/src/discord-slash-wire.ts +1 -0
  232. package/src/domains/outbound-alert.ts +200 -27
  233. package/src/domains/outbound-log-probe.ts +16 -1
  234. package/src/domains/release-tool.ts +2 -2
  235. package/src/execution-loop/gates.ts +11 -2
  236. package/src/flow-loop/split.ts +8 -4
  237. package/src/git-fs/worktree.ts +23 -3
  238. package/src/harness/harness-cli-command.ts +151 -11
  239. package/src/harness/harness-pod-dispatch.ts +11 -0
  240. package/src/harness/harness-queue.ts +466 -32
  241. package/src/harness/helper-scan.ts +10 -3
  242. package/src/harness/nested-elanous-policy.ts +23 -4
  243. package/src/hq/fence-wrapper.ts +123 -0
  244. package/src/hq/hq.ts +8 -0
  245. package/src/hq/seats.ts +369 -0
  246. package/src/index.ts +190 -23
  247. package/src/intake-plane/adopt-gate.ts +49 -0
  248. package/src/intake-plane/check.ts +3 -0
  249. package/src/intake-plane/collect-github.ts +57 -25
  250. package/src/intake-plane/digest.ts +39 -4
  251. package/src/intake-plane/discord-seat-work.ts +8 -2
  252. package/src/intake-plane/requirement-funnel.ts +316 -0
  253. package/src/knowledge/know-find.ts +10 -1
  254. package/src/loops/budget.ts +36 -0
  255. package/src/loops/orchestrator/finish-rate.ts +73 -1
  256. package/src/loops/orchestrator/grid-place.ts +11 -0
  257. package/src/loops/orchestrator/tick.ts +295 -20
  258. package/src/maturity/feature-maturity.ts +3 -2
  259. package/src/nexus/api/decisions-route.ts +11 -1
  260. package/src/nexus/api/grid.ts +61 -0
  261. package/src/nexus/api/http-server.ts +19 -1
  262. package/src/nexus/api/loop-edges.ts +69 -13
  263. package/src/nexus/api/ops-seats-sources.ts +2 -2
  264. package/src/nexus/api/task-cards-api.ts +19 -0
  265. package/src/nexus/api/workflow-templates.ts +3 -0
  266. package/src/oauth/codex.ts +120 -2
  267. package/src/onboarding/first-chat-gate.ts +29 -0
  268. package/src/release-loop/checklist.ts +104 -6
  269. package/src/release-loop/feature-store.ts +10 -9
  270. package/src/release-loop/landing-freeze.ts +24 -14
  271. package/src/release-loop/placement.ts +13 -3
  272. package/src/release-loop/rubric.ts +80 -0
  273. package/src/seat-dispatch/seat-ask.ts +79 -16
  274. package/src/seat-loop/seat-loop.ts +107 -37
  275. package/src/self-dev/ask-launch-flow.ts +50 -10
  276. package/src/self-dev/ask-path-candidates.ts +68 -0
  277. package/src/self-dev/dev-pipeline.ts +4 -0
  278. package/src/self-dev/draft-triage-rules.ts +1 -1
  279. package/src/self-dev/orchestrate-cli.ts +22 -0
  280. package/src/self-dev/round-pr-supersede.ts +244 -0
  281. package/src/self-dev/run-store.ts +5 -1
  282. package/src/self-dev/run-supervisor.ts +21 -1
  283. package/src/self-dev/supervisor-verdict-edges.ts +46 -0
  284. package/src/self-implement/follow-up-goals.ts +2 -2
  285. package/src/self-implement/frozen-merges.ts +25 -20
  286. package/src/self-implement/gate-remote.ts +427 -0
  287. package/src/self-implement/goal-author-clarification.ts +81 -0
  288. package/src/self-implement/goal-author-cli.ts +33 -5
  289. package/src/self-implement/goal-author.ts +93 -12
  290. package/src/self-implement/goal-clarification-escalation.ts +37 -1
  291. package/src/self-implement/goal-document-write.ts +28 -0
  292. package/src/self-implement/heal-intake.ts +4 -0
  293. package/src/self-implement/host-regate.ts +3 -1
  294. package/src/self-implement/l8-merge-queue.ts +29 -5
  295. package/src/self-implement/orchestrator.ts +78 -13
  296. package/src/self-implement/prior-draft-findings.ts +4 -3
  297. package/src/skills/skill-feedback.ts +76 -0
  298. package/src/task-agent/actions.ts +151 -0
  299. package/src/task-agent/judge.ts +122 -0
  300. package/src/task-agent/mission.ts +423 -0
  301. package/src/task-agent/shadow.ts +90 -0
  302. package/src/task-agent/task-hand.ts +235 -0
  303. package/src/task-cards/card-store.ts +4 -1
  304. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +43 -4
  305. package/src/task-orchestrator/surfaces/pod-lease.ts +5 -2
  306. package/src/task-orchestrator/surfaces/pod-pool.ts +81 -13
  307. package/src/task-orchestrator/surfaces/self-implement-pod.ts +225 -35
  308. package/src/telegram-commands.ts +1 -1
  309. package/src/telegram-loops-command.ts +22 -15
  310. package/src/telegram.ts +5 -20
  311. package/src/user-config.ts +86 -4
  312. package/src/version/packed-revision.json +1 -1
  313. package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.js +0 -1
  314. package/apps/pwa/out/_next/static/chunks/4554-3565eea1f1495ba7.js +0 -1
  315. package/apps/pwa/out/_next/static/chunks/4614-21403aac730d4586.js +0 -1
  316. package/apps/pwa/out/_next/static/chunks/5135-44008fe8b1f2aa41.js +0 -1
  317. package/apps/pwa/out/_next/static/chunks/5233-bf28bd6bd7a32099.js +0 -1
  318. package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +0 -1
  319. package/apps/pwa/out/_next/static/chunks/6877-7c6208705c284124.js +0 -1
  320. package/apps/pwa/out/_next/static/chunks/7395-164e238f471b5d7b.js +0 -1
  321. package/apps/pwa/out/_next/static/chunks/7787-02a1c0b04e25e811.js +0 -1
  322. package/apps/pwa/out/_next/static/chunks/8827-e2f5b09a8d37e83b.js +0 -1
  323. package/apps/pwa/out/_next/static/chunks/8949-ecb4128a0858b911.js +0 -1
  324. package/apps/pwa/out/_next/static/chunks/899-46946fe8363e1262.js +0 -1
  325. package/apps/pwa/out/_next/static/chunks/9247-29d889b809288e65.js +0 -1
  326. package/apps/pwa/out/_next/static/chunks/app/approvals/page-79c3ed84baaedd03.js +0 -1
  327. package/apps/pwa/out/_next/static/chunks/app/board/page-a424ef80885d1743.js +0 -1
  328. package/apps/pwa/out/_next/static/chunks/app/ceo/page-1e962b31d1f11081.js +0 -1
  329. package/apps/pwa/out/_next/static/chunks/app/chat/page-8c2d9526164f5d09.js +0 -1
  330. package/apps/pwa/out/_next/static/chunks/app/consult/page-ffff6d819fbc6e0f.js +0 -1
  331. package/apps/pwa/out/_next/static/chunks/app/inside/page-76270efaee638f17.js +0 -1
  332. package/apps/pwa/out/_next/static/chunks/app/layout-dce686e582c23937.js +0 -1
  333. package/apps/pwa/out/_next/static/chunks/app/live/page-d3d53b146416e422.js +0 -1
  334. package/apps/pwa/out/_next/static/chunks/app/loops/page-1a61c7ca1ceb3ae7.js +0 -1
  335. package/apps/pwa/out/_next/static/chunks/app/market/page-51f811fa119308b6.js +0 -1
  336. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-dacff46049e1557e.js +0 -1
  337. package/apps/pwa/out/_next/static/chunks/app/observatory/page-36a7d994f4ca0299.js +0 -1
  338. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-5b042736aa113756.js +0 -1
  339. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-acb749f1e432bcf3.js +0 -1
  340. package/apps/pwa/out/_next/static/chunks/app/outputs/page-cd78862707999911.js +0 -1
  341. package/apps/pwa/out/_next/static/chunks/app/page-54b9ef8afe6278fd.js +0 -1
  342. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-57924393e3e4e539.js +0 -1
  343. package/apps/pwa/out/_next/static/chunks/app/sessions/page-9e0ce6c8129fa641.js +0 -1
  344. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-31fa1ce4c4aff9ae.js +0 -1
  345. package/apps/pwa/out/_next/static/chunks/app/settings/page-0f5cd57ca928f42b.js +0 -1
  346. package/apps/pwa/out/_next/static/chunks/app/setup/page-4364ef9f4e7c7f0f.js +0 -1
  347. package/apps/pwa/out/_next/static/chunks/app/showroom/page-59f03fda76c03d22.js +0 -1
  348. package/apps/pwa/out/_next/static/chunks/app/trace/page-86914b0dee8ab7f8.js +0 -1
  349. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d4aef67abf264874.js +0 -1
  350. package/apps/pwa/out/_next/static/chunks/app/workflows/page-1ea64acadd704ace.js +0 -1
  351. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-30c2073bfc21c004.js +0 -1
  352. package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +0 -1
  353. package/apps/pwa/out/_next/static/rv9cierV0jSOvDBm1g0XB/_buildManifest.js +0 -1
  354. /package/apps/pwa/out/_next/static/{rv9cierV0jSOvDBm1g0XB → P6YTVgzuZz5oTZhY4Din_}/_ssgManifest.js +0 -0
@@ -1,8 +1,9 @@
1
- import { closeSync, constants, fstatSync, lstatSync, mkdirSync, openSync } from 'node:fs';
1
+ import { closeSync, constants, existsSync, fstatSync, lstatSync, mkdirSync, openSync } from 'node:fs';
2
2
  import { dlopen, FFIType } from 'bun:ffi';
3
3
  import { join } from 'node:path';
4
4
  import { listMemories, memoryRoot, saveMemory, type MemoryEntry } from '../memory.js';
5
5
  import { DecisionLedger } from '../decisions/decision-ledger.js';
6
+ import { debug } from '../debug/log.js';
6
7
  import { condenseContextWindowWithReport, readCondenseEvents, type CondenseFunnel, type CondenseSkipped, type MemoryDeps, type MemoryItem, type MemorySource, type MemorySummary } from './long-term-memory.js';
7
8
 
8
9
  const summarize = async (source: MemorySource): Promise<MemorySummary> =>
@@ -51,6 +52,7 @@ export function renderCondenseMarkdown(report: Pick<CondenseReport, 'since' | 'u
51
52
 
52
53
  type CondenseStore = {
53
54
  list(): MemoryEntry[];
55
+ hasMemoryDirectory?(): boolean;
54
56
  save(input: Parameters<typeof saveMemory>[0]): MemoryEntry;
55
57
  raise(input: Parameters<DecisionLedger['raiseOnce']>[0], ref: string): string;
56
58
  withLock<T>(action: () => T): T;
@@ -96,6 +98,7 @@ function defaultStore(root?: string): CondenseStore {
96
98
  const ledger = new DecisionLedger(root ? { stateDir: root } : {});
97
99
  return {
98
100
  list: () => listMemories({}, memoryDir),
101
+ hasMemoryDirectory: () => existsSync(memoryDir),
99
102
  save: input => saveMemory(input, memoryDir),
100
103
  raise: (input, ref) => ledger.raiseOnce(input, ref).id,
101
104
  withLock: action => withMemoryLock(memoryDir, action),
@@ -118,10 +121,22 @@ function newestActive(entries: readonly MemoryEntry[]): MemoryEntry | undefined
118
121
  }
119
122
 
120
123
  /** Apply candidate cards through the existing curated memory and decision ledger paths. */
121
- export function applyCondensedCards(cards: readonly MemoryItem[], store: CondenseStore): NonNullable<CondenseReport['applied']> {
124
+ export function applyCondensedCards(cards: readonly MemoryItem[], store: CondenseStore, now: Date = new Date()): NonNullable<CondenseReport['applied']> {
122
125
  const decisions: string[] = [];
123
126
  let written = 0;
124
127
  let retired = 0;
128
+ const cutoff = now.getTime() - 30 * 24 * 3_600_000;
129
+ const retireAged = () => {
130
+ let agedRetired = 0;
131
+ for (const old of store.list()) {
132
+ if (old.contextCard?.status !== 'active' || !(Date.parse(old.contextCard.updatedAt) < cutoff)) continue;
133
+ store.save({ type: old.type, name: old.name, description: old.description, body: old.body, id: old.id,
134
+ pinned: false, priority: old.priority, contextCard: { ...old.contextCard, status: 'retired' } });
135
+ agedRetired++;
136
+ }
137
+ retired += agedRetired;
138
+ debug.log('context.condense', 'aged-out', { retired: agedRetired });
139
+ };
125
140
  for (const card of cards) {
126
141
  if (card.conflict) {
127
142
  const ref = `context-condense:${JSON.stringify([card.project, card.seat, card.topic, card.source, card.updatedAt])}`;
@@ -162,6 +177,7 @@ export function applyCondensedCards(cards: readonly MemoryItem[], store: Condens
162
177
  };
163
178
  store.withLock(applyTopic);
164
179
  }
180
+ if (store.hasMemoryDirectory?.() !== false) store.withLock(retireAged);
165
181
  return { written, retired, decisions };
166
182
  }
167
183
 
@@ -184,6 +200,6 @@ export async function runContextCondense(options: {
184
200
  const conflicts = cards.filter(card => card.conflict !== undefined).length;
185
201
  const report: CondenseReport = { since, until, cards, conflicts, skipped, folded, droppedHarnessChild, funnel,
186
202
  markdown: renderCondenseMarkdown({ since, until, cards, conflicts, skipped, droppedHarnessChild, funnel }) };
187
- if (options.apply === true) report.applied = applyCondensedCards(cards, store ?? defaultStore(options.root));
203
+ if (options.apply === true) report.applied = applyCondensedCards(cards, store ?? defaultStore(options.root), now);
188
204
  return report;
189
205
  }
@@ -12,6 +12,16 @@ export interface ContextDayRow {
12
12
  status: '들어옴' | '안 들어옴';
13
13
  }
14
14
  export interface ContextDayReport { since: string; until: string; rows: ContextDayRow[] }
15
+ export interface ContextDayTimelineEvent {
16
+ at: string;
17
+ source: ContextDaySource;
18
+ kind: ContextDayKind | 'other';
19
+ summary: string;
20
+ link: string | null;
21
+ }
22
+ export interface ContextDayTimelineReport { since: string; until: string; events: ContextDayTimelineEvent[] }
23
+ interface JournalEvent { surface: string; kind: string; ts: string; refs: string | null; summary: string | null }
24
+ type DayDeps = { now?: () => Date; dbPath?: string; seat?: ContextDaySource };
15
25
 
16
26
  /** Reject malformed windows instead of silently changing the time range being judged. */
17
27
  export function contextDayHours(since: string): number {
@@ -34,43 +44,86 @@ function kindFor(surface: string, kind: string): ContextDayKind | null {
34
44
  return null;
35
45
  }
36
46
 
47
+ function sourceFor(event: JournalEvent): { source: ContextDaySource; refs: { url?: unknown; source?: unknown } } | null {
48
+ let refs: { seat?: unknown; origin?: unknown; url?: unknown; source?: unknown };
49
+ try { refs = JSON.parse(event.refs ?? '{}') as typeof refs; } catch { return null; }
50
+ if (!refs || typeof refs !== 'object') return null;
51
+ const source = event.surface === 'context:external' ? refs.origin : refs.seat;
52
+ if (typeof source !== 'string' || !CONTEXT_DAY_SOURCES.some(item => item === source)) return null;
53
+ const isExternal = CONTEXT_DAY_SOURCES.indexOf(source as ContextDaySource) >= 4;
54
+ // Do not attribute an external hook to a seat (or a seat event to an external origin).
55
+ if ((event.surface === 'context:external') !== isExternal) return null;
56
+ return { source: source as ContextDaySource, refs };
57
+ }
58
+
37
59
  /** Read only the context journal, not recallEvents (which mutates recall counts) or the writable DB opener. */
38
- export function contextDay(since = '24h', deps: { now?: () => Date; dbPath?: string } = {}): ContextDayReport {
60
+ function readDay(since: string, deps: DayDeps): { since: string; until: string; events: JournalEvent[] } {
39
61
  const hours = contextDayHours(since);
40
62
  const now = (deps.now ?? (() => new Date()))();
41
63
  const until = now.toISOString();
42
64
  const from = new Date(now.getTime() - hours * 3_600_000).toISOString();
43
- const rows: ContextDayRow[] = CONTEXT_DAY_SOURCES.map(source => ({
44
- source, counts: { 'task-claimed': 0, done: 0, asked: 0, 'guidance-changed': 0 }, lastAt: null, status: '안 들어옴',
45
- }));
46
- const bySource = new Map(rows.map(row => [row.source, row]));
47
65
  const path = deps.dbPath ?? surfaceEventsDbPath();
48
- if (!existsSync(path)) return { since: from, until, rows };
66
+ if (!existsSync(path)) return { since: from, until, events: [] };
49
67
  const db = new Database(path, { readonly: true });
50
68
  try {
51
- const events = db.prepare(`SELECT surface, kind, ts, refs FROM events
69
+ const events = db.prepare(`SELECT surface, kind, ts, refs, summary FROM events
52
70
  WHERE surface IN ('coord:channel', 'context:session', 'context:external')
53
- AND direction='outbound' AND ts>=? AND ts<=? ORDER BY ts ASC`).all(from, until) as Array<{
54
- surface: string; kind: string; ts: string; refs: string | null;
55
- }>;
56
- for (const event of events) {
57
- let refs: { seat?: unknown; origin?: unknown };
58
- try { refs = JSON.parse(event.refs ?? '{}') as typeof refs; } catch { continue; }
59
- const source = event.surface === 'context:external' ? refs.origin : refs.seat;
60
- if (typeof source !== 'string') continue;
61
- const row = bySource.get(source as ContextDaySource);
62
- if (!row) continue;
63
- // Do not attribute an external hook to a seat (or a seat event to an external origin).
64
- if ((event.surface === 'context:external') !== (rows.indexOf(row) >= 4)) continue;
65
- row.lastAt = event.ts;
66
- row.status = '들어옴';
67
- const kind = kindFor(event.surface, event.kind);
68
- if (kind) row.counts[kind]++;
69
- }
71
+ AND direction='outbound' AND ts>=? AND ts<=? ORDER BY ts ASC`).all(from, until) as JournalEvent[];
72
+ return { since: from, until, events };
70
73
  } finally { db.close(); }
74
+ }
75
+
76
+ export function contextDay(since = '24h', deps: { now?: () => Date; dbPath?: string } = {}): ContextDayReport {
77
+ const { since: from, until, events } = readDay(since, deps);
78
+ const rows: ContextDayRow[] = CONTEXT_DAY_SOURCES.map(source => ({
79
+ source, counts: { 'task-claimed': 0, done: 0, asked: 0, 'guidance-changed': 0 }, lastAt: null, status: '안 들어옴',
80
+ }));
81
+ const bySource = new Map(rows.map(row => [row.source, row]));
82
+ for (const event of events) {
83
+ const attribution = sourceFor(event);
84
+ if (!attribution) continue;
85
+ const row = bySource.get(attribution.source)!;
86
+ row.lastAt = event.ts;
87
+ row.status = '들어옴';
88
+ const kind = kindFor(event.surface, event.kind);
89
+ if (kind) row.counts[kind]++;
90
+ }
71
91
  return { since: from, until, rows };
72
92
  }
73
93
 
94
+ export function contextDayTimeline(since = '24h', deps: DayDeps = {}): ContextDayTimelineReport {
95
+ if (deps.seat !== undefined && !CONTEXT_DAY_SOURCES.some(source => source === deps.seat)) {
96
+ throw new Error(`unknown --seat: ${deps.seat}`);
97
+ }
98
+ const { since: from, until, events } = readDay(since, deps);
99
+ const timeline: ContextDayTimelineEvent[] = [];
100
+ for (const event of events) {
101
+ const attribution = sourceFor(event);
102
+ if (!attribution || (deps.seat && attribution.source !== deps.seat)) continue;
103
+ const { url, source } = attribution.refs;
104
+ const link = [url, source].find(value => typeof value === 'string' && /^(?:https?:\/\/|elanous:\/\/)[^\s]+$/.test(value));
105
+ timeline.push({ at: event.ts, source: attribution.source, kind: kindFor(event.surface, event.kind) ?? 'other',
106
+ summary: event.summary ?? '', link: typeof link === 'string' ? link : null });
107
+ }
108
+ return { since: from, until, events: timeline };
109
+ }
110
+
111
+ const kstFormatter = new Intl.DateTimeFormat('en-CA', {
112
+ timeZone: 'Asia/Seoul', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hourCycle: 'h23',
113
+ });
114
+ function kst(at: string): string {
115
+ const parts = Object.fromEntries(kstFormatter.formatToParts(new Date(at)).map(part => [part.type, part.value]));
116
+ return `${parts.year}-${parts.month}-${parts.day} ${parts.hour}:${parts.minute} KST`;
117
+ }
118
+
119
+ export function renderContextDayTimeline(report: ContextDayTimelineReport, dbPath = surfaceEventsDbPath()): string {
120
+ const lines = [`맥락 사건 ${kst(report.since)} ~ ${kst(report.until)}`];
121
+ if (!report.events.length) lines.push(`이 창에 사건 0건 — 원장 경로: ${dbPath}`);
122
+ else lines.push(...report.events.map(event => `${kst(event.at).slice(11)} · ${event.source} · ${event.kind} · ${event.summary.replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029]+/g, ' ')} · ${event.link ?? '—'}`));
123
+ lines.push(`${report.events.length}건 · 출처 ${new Set(report.events.map(event => event.source)).size}곳`);
124
+ return lines.join('\n');
125
+ }
126
+
74
127
  export function renderContextDay(report: ContextDayReport): string {
75
128
  return [
76
129
  `맥락 이벤트 ${report.since} ~ ${report.until} (UTC)`,
@@ -0,0 +1,33 @@
1
+ import type { ContextNowAnswer, ContextNowDeps } from './context-now.js';
2
+
3
+ const CONTEXT_FIRST_GAP_MS = 6 * 60 * 60 * 1000;
4
+ const CONTEXT_UNREADABLE = '맥락 못 읽음';
5
+
6
+ /** Track every allowed utterance, consuming a pending summary only on a normal turn. */
7
+ export function createContextFirstGate({ now, gapMs = CONTEXT_FIRST_GAP_MS }: { now: () => number; gapMs?: number }) {
8
+ const lastUtteranceAt = new Map<string, number>();
9
+ const pending = new Set<string>();
10
+ return {
11
+ note(key: string): void {
12
+ const at = now();
13
+ const previous = lastUtteranceAt.get(key);
14
+ lastUtteranceAt.set(key, at);
15
+ if (previous === undefined || at - previous >= gapMs) pending.add(key);
16
+ },
17
+ take(key: string): boolean {
18
+ return pending.delete(key);
19
+ },
20
+ };
21
+ }
22
+
23
+ export function renderContextFirst(
24
+ read: (options: {}, deps?: ContextNowDeps) => ContextNowAnswer,
25
+ deps: ContextNowDeps | undefined,
26
+ render: (answer: ContextNowAnswer) => string,
27
+ ): string {
28
+ try {
29
+ return render(read({}, deps));
30
+ } catch {
31
+ return CONTEXT_UNREADABLE;
32
+ }
33
+ }
@@ -0,0 +1,45 @@
1
+ /** A rebuildable view of the ledger and code, never an authoritative record. */
2
+ export interface ContextCard {
3
+ readonly id: string;
4
+ readonly kind: 'task' | 'goal' | 'run' | 'pr' | 'cell' | 'doc';
5
+ readonly conclusion: string;
6
+ readonly why: string;
7
+ readonly verdict: string;
8
+ readonly remaining: readonly string[];
9
+ readonly pointers: readonly string[];
10
+ readonly source: string;
11
+ readonly updatedAt: string;
12
+ readonly supersedes?: string;
13
+ }
14
+
15
+ /** Serialized code points, including JSON escaping; callers may supply a smaller screen budget. */
16
+ export const CONTEXT_CARD_SCREEN_CHARS = 1_600;
17
+ const length = (text: string): number => [...text].length;
18
+
19
+ /** Preserve the decision and provenance; fold only the two expandable lists. Never mutate the card. */
20
+ export function serializeContextCard(card: ContextCard, maxChars = CONTEXT_CARD_SCREEN_CHARS): string {
21
+ if (!Number.isSafeInteger(maxChars) || maxChars < 1) throw new RangeError('maxChars must be a positive safe integer');
22
+ if (/[\r\n\u2028\u2029]/u.test(card.conclusion)) throw new Error('conclusion must be one line');
23
+ const remaining = [...card.remaining];
24
+ const pointers = [...card.pointers];
25
+ const render = () => JSON.stringify({
26
+ id: card.id, kind: card.kind, conclusion: card.conclusion, why: card.why,
27
+ verdict: card.verdict, remaining: remaining.length < card.remaining.length
28
+ ? [...remaining, `외 ${card.remaining.length - remaining.length}개`] : remaining,
29
+ pointers: pointers.length < card.pointers.length
30
+ ? [...pointers, `외 ${card.pointers.length - pointers.length}개`] : pointers,
31
+ source: card.source, updatedAt: card.updatedAt,
32
+ ...(card.supersedes === undefined ? {} : { supersedes: card.supersedes }),
33
+ });
34
+ let text = render();
35
+ while (length(text) > maxChars && (remaining.length || pointers.length)) {
36
+ // Remove the larger last entry first, then break ties by preferring remaining.
37
+ const remainingCost = remaining.length ? length(JSON.stringify(remaining.at(-1))) : -1;
38
+ const pointerCost = pointers.length ? length(JSON.stringify(pointers.at(-1))) : -1;
39
+ if (remainingCost >= pointerCost) remaining.pop();
40
+ else pointers.pop();
41
+ text = render();
42
+ }
43
+ if (length(text) > maxChars) throw new RangeError('card core and omission counts exceed maxChars');
44
+ return text;
45
+ }
@@ -0,0 +1,76 @@
1
+ import type { RunLedgerEntry } from '../self-implement/run-ledger.js';
2
+ import type { ContextCard } from './index.js';
3
+
4
+ /** An injected, read-only view of the JSONL run ledger. No writer or store mutation is available. */
5
+ export interface TaskLedgerReader {
6
+ readRuns(): readonly { readonly runId: string; readonly entries: readonly RunLedgerEntry[] }[];
7
+ /** Returns the verbatim original ask from the goal document named by start.goalFile. */
8
+ readOriginalAsk?(goalFile: string): string | null;
9
+ /** Returns must-fix text from the review artifact named by reviewed.artifactPath. */
10
+ readReviewMustFix?(artifactPath: string): readonly string[] | null;
11
+ }
12
+
13
+ export interface TaskContextCard extends ContextCard {
14
+ readonly kind: 'task';
15
+ readonly originalAsk: string;
16
+ readonly authoredGoal: { readonly path: string; readonly sha256: string } | null;
17
+ readonly runIds: readonly string[];
18
+ readonly mustFix: readonly string[];
19
+ readonly decisionCards: readonly string[];
20
+ readonly nextAction: string;
21
+ }
22
+
23
+ const text = (value: unknown): string | null => typeof value === 'string' && value.trim() ? value : null;
24
+ const strings = (value: unknown): string[] | null => Array.isArray(value) && value.every((item) => typeof item === 'string') ? [...value] : null;
25
+
26
+ /** Produce a task card from source events, not from the previous card or wall-clock time. */
27
+ export function buildTaskCard(taskId: string, reader: TaskLedgerReader): TaskContextCard {
28
+ const runs = reader.readRuns().filter((run) => run.entries.some((entry) => entry.event === 'start' && entry.goalId === taskId))
29
+ .map((run) => ({ runId: run.runId, entries: run.entries.filter((entry) => entry.runId === run.runId && (entry.goalId === undefined || entry.goalId === taskId)) }))
30
+ .sort((a, b) => a.runId < b.runId ? -1 : a.runId > b.runId ? 1 : 0);
31
+ if (!runs.length) throw new Error(`no run-ledger start for task: ${taskId}`);
32
+ const events = runs.flatMap(({ entries }) => entries).sort((a, b) =>
33
+ (a.timestamp ?? '').localeCompare(b.timestamp ?? '') || a.runId.localeCompare(b.runId));
34
+ const start = events.find((event) => event.event === 'start')!;
35
+ const goalFile = text(start.data.goalFile);
36
+ const originalAsk = text(start.data.originalAsk)
37
+ ?? (goalFile ? reader.readOriginalAsk?.(goalFile) ?? null : null);
38
+ if (!originalAsk) throw new Error(`original ask unavailable for task: ${taskId}`);
39
+
40
+ const authored = [...events].reverse().find((event) => event.event === 'goal-authored'
41
+ && text(event.data.path) !== null && /^[a-f0-9]{64}$/i.test(text(event.data.sha256) ?? ''));
42
+ const authoredGoal = authored ? { path: text(authored.data.path)!, sha256: text(authored.data.sha256)! } : null;
43
+ const reviews = events.filter((event) => event.event === 'reviewed' && event.data.reviewed === true);
44
+ const review = reviews.at(-1);
45
+ const artifactPath = review ? text(review.data.artifactPath) : null;
46
+ const mustFix = review
47
+ ? strings(review.data.mustFix) ?? (artifactPath ? reader.readReviewMustFix?.(artifactPath) ?? null : null)
48
+ : [];
49
+ if (mustFix === null && review?.data.mustFix !== 0) throw new Error(`review must-fix unavailable for task: ${taskId}`);
50
+ const findings = mustFix ?? [];
51
+ const decisionCards = events.filter((event) => event.event === 'decision-card')
52
+ .flatMap((event) => {
53
+ const cardId = text(event.data.cardId);
54
+ return cardId ? [cardId] : [];
55
+ });
56
+ const nextAction = [...events].reverse().map((event) => text(event.data.nextAction)).find((value): value is string => value !== null)
57
+ ?? findings[0] ?? null;
58
+ if (!nextAction) throw new Error(`next action unavailable for task: ${taskId}`);
59
+ const updatedAt = events.at(-1)?.timestamp;
60
+ if (!updatedAt) throw new Error(`timestamp unavailable for task: ${taskId}`);
61
+ const pointers = [
62
+ ...(goalFile ? [goalFile] : []),
63
+ ...(authoredGoal ? [`${authoredGoal.path}#sha256=${authoredGoal.sha256}`] : []),
64
+ ...runs.map(({ runId }) => `run:${runId}`),
65
+ ...(artifactPath ? [artifactPath] : []),
66
+ ...decisionCards,
67
+ ];
68
+ return {
69
+ id: `task:${taskId}`, kind: 'task', conclusion: nextAction,
70
+ why: findings.length ? `최근 리뷰 must-fix ${findings.length}개` : '원문과 런 원장에 따른 다음 행동',
71
+ verdict: text(review?.data.verdict) ?? '리뷰 판정 없음',
72
+ remaining: [...findings], pointers,
73
+ source: `run-ledger:${runs.map(({ runId }) => runId).join(',')}`, updatedAt,
74
+ originalAsk, authoredGoal, runIds: runs.map(({ runId }) => runId), mustFix: [...findings], decisionCards, nextAction,
75
+ };
76
+ }
@@ -2442,6 +2442,8 @@ export function buildDashboardSlashRegistry(nowDeps?: ContextNowDeps): SlashComm
2442
2442
  cwd: () => process.cwd(),
2443
2443
  now: () => Date.now(),
2444
2444
  isInteractive: () => ctx.surfaceUx !== undefined,
2445
+ selfResolveClarification: (await import('../../self-implement/goal-author-clarification.js')).defaultGoalAuthorSelfResolve,
2446
+ writeGoalDocument: (await import('../../self-implement/goal-document-write.js')).writeGoalDocumentAtomic,
2445
2447
  buildPreflightDeps: askIo.buildAskPreflightDeps,
2446
2448
  priorBlockSamples: () => askIo.priorBlockSamplesFrom(rows),
2447
2449
  recentAuthoringSamples: () => askIo.recentAuthoringSamplesFrom(rows),
@@ -93,7 +93,16 @@ function validate(input: RaiseInput, historical = false): RaiseInput {
93
93
  const c = scqaField(input.scqa.c, 'c');
94
94
  const q = input.scqa.q?.trim() ? scqaField(input.scqa.q, 'q') : undefined;
95
95
  const a = input.scqa.a?.trim() ? scqaField(input.scqa.a, 'a') : undefined;
96
- if (q && q === c) throw new Error('SCQA Q repeats C — leave Q empty');
96
+ if (q && q === c && input.raisedBy.agent !== 'seat-loop') throw new Error('SCQA Q repeats C — leave Q empty');
97
+ if (input.raisedBy.agent === 'seat-loop') {
98
+ if (!q || !a || !input.pendingQuestion?.trim()) throw new Error('seat-loop cards require --q, --a and --pending-question');
99
+ if ('skipped' in input.recommendation && !(['money', 'security', 'secret'].includes(input.category)
100
+ || (['publish', 'irreversible'].includes(input.category)
101
+ && /공개|게시|발행|출시|publish|release/i.test(`${title} ${input.pendingQuestion} ${input.recommendation.reason}`)
102
+ && /되돌릴 수 없|철회 불가|복구 불가|irreversib|cannot (?:undo|retract)/i.test(`${input.pendingQuestion} ${input.recommendation.reason}`)))) {
103
+ throw new Error('seat-loop recommendation may only be skipped for money, security or irreversible publication');
104
+ }
105
+ }
97
106
  if (!Array.isArray(input.options) || (input.options.length < 2 && !(historical && input.options.length === 0))) throw new Error('at least two options required');
98
107
  const options = input.options.map(o => ({ key: single(o.key, 'option key'), label: safe(single(o.label, 'option label')), consequence: safe(single(o.consequence, 'option consequence')) }));
99
108
  if (options.some(o => !/^[a-z]$/.test(o.key)) || new Set(options.map(o => o.key)).size !== options.length) throw new Error('option keys must be unique lowercase letters');
@@ -65,7 +65,9 @@ import { detectCard, runCardFollowup, NotACardError } from './card-followup/core
65
65
  import { localOcrText } from './telegram-card-followup.js';
66
66
  import type { runGraph } from './graph-runner/runner.js';
67
67
  import { readSlashContextNow, renderTelegramNow } from './context-bus/context-now-surfaces.js';
68
- import type { ContextNowDeps } from './context-bus/context-now.js';
68
+ import { contextNow, type ContextNowDeps } from './context-bus/context-now.js';
69
+ import { createContextFirstGate, renderContextFirst } from './context-bus/context-first.js';
70
+ import { telegramLoopsStatus, type LoopsSources } from './telegram-loops-command.js';
69
71
  import { botUnavailableSlashReply, deriveTuiSlashAvailability, type TuiSlashAvailability } from './maturity/tui-slash-availability.js';
70
72
 
71
73
  const SLASH_FOCUS_TURNS_DEFAULT = 8;
@@ -143,6 +145,9 @@ export interface DiscordSelfMessageDeps {
143
145
  log?: (msg: string) => void;
144
146
  cardFollowupDeps?: { ocrText?: (path: string) => Promise<string | null>; runGraph?: typeof runGraph; rootDir?: () => string };
145
147
  nowDeps?: ContextNowDeps;
148
+ /** Override the clock for the context-first gate in isolated message-path tests. */
149
+ contextFirstNow?: () => number;
150
+ loopsSources?: LoopsSources;
146
151
  /** Override the TUI slash projection for isolated message-path tests. */
147
152
  tuiSlashAvailability?: readonly TuiSlashAvailability[];
148
153
  }
@@ -151,6 +156,15 @@ export interface DiscordSelfMessageDeps {
151
156
  export function buildDiscordSelfOnMessage(deps: DiscordSelfMessageDeps): DcMessageHandler {
152
157
  const log = deps.log ?? ((): void => {});
153
158
  const cfg = deps.userConfig;
159
+ const contextFirstGate = createContextFirstGate({ now: deps.contextFirstNow ?? Date.now });
160
+ const maybeSendContextFirst = async (channelId: string): Promise<void> => {
161
+ if (!contextFirstGate.take(channelId) || cfg.discord?.contextFirst === false) return;
162
+ try {
163
+ await deps.getBot()?.sendMessage(channelId, renderContextFirst(contextNow, deps.nowDeps, renderTelegramNow));
164
+ } catch (err) {
165
+ log(`discord context-first failed: ${err instanceof Error ? err.message : String(err)}`);
166
+ }
167
+ };
154
168
  // C2+ (2026-07-12 dogfood): 디스코드 첨부는 메시지 단위라 "사진 먼저,
155
169
  // !cdx 명령은 다음 메시지" 패턴에서 위임 턴이 빈손이 된다. 채널별로
156
170
  // 마지막 첨부를 기억해 두고, 첨부 없는 위임 명령이 2분 내 같은
@@ -359,6 +373,7 @@ export function buildDiscordSelfOnMessage(deps: DiscordSelfMessageDeps): DcMessa
359
373
 
360
374
  return async (ctx, streamer) => {
361
375
  if (deps.channelScope && ctx.channelId !== deps.channelScope) return undefined;
376
+ contextFirstGate.note(ctx.channelId);
362
377
  // Voice text commands pass the guild gate for the voice adapter's
363
378
  // sake — never route them into the LLM turn (legacy behavior).
364
379
  if (/^\/voice-(join|leave|status)\b/.test(ctx.text.trim())) return undefined;
@@ -367,6 +382,8 @@ export function buildDiscordSelfOnMessage(deps: DiscordSelfMessageDeps): DcMessa
367
382
  const args = nowCommand[1]?.trim().split(/\s+/).filter(Boolean) ?? [];
368
383
  return renderTelegramNow(readSlashContextNow(args, deps.nowDeps));
369
384
  }
385
+ if (/^[/!]loops$/.test(ctx.text.trim())) return telegramLoopsStatus([], deps.loopsSources);
386
+ if (/^[/!]loops\s+\S/.test(ctx.text.trim())) return telegramLoopsStatus(['extra'], deps.loopsSources);
370
387
  if (!ctx.text.trim() && ctx.attachments.length === 1 && ctx.attachments[0]!.contentType?.startsWith('image/')) {
371
388
  const photo = ctx.attachments[0]!;
372
389
  if (detectCard({ width: photo.width, height: photo.height, ocrText: null }).decision !== 'skip') {
@@ -408,6 +425,8 @@ export function buildDiscordSelfOnMessage(deps: DiscordSelfMessageDeps): DcMessa
408
425
  const unavailable = botUnavailableSlashReply(entry, 'discord');
409
426
  if (unavailable) return unavailable;
410
427
  }
428
+ const cmd = parseDiscordAcpCommand(ctx.text);
429
+ if (!cmd && !/^[!/]/.test(ctx.text.trim())) await maybeSendContextFirst(ctx.channelId);
411
430
  log(`◀ ${ctx.userName ?? ctx.userId}: ${ctx.text.slice(0, 80)}${ctx.attachments.length ? ` (+첨부 ${ctx.attachments.length})` : ''}`);
412
431
  debug.log('discord.self.turn', 'inbound', { channelId: ctx.channelId, chars: ctx.text.length, attachments: ctx.attachments.length });
413
432
  // C2+ — 첨부 기억(사진만 먼저 올리는 패턴 지원). 명령 여부와 무관.
@@ -416,7 +435,6 @@ export function buildDiscordSelfOnMessage(deps: DiscordSelfMessageDeps): DcMessa
416
435
  const delegationKey = delegationChatKey('dc', ctx.channelId);
417
436
 
418
437
  // 1) Explicit text commands — /cc·/cdx·/gem delegate, /brain exits.
419
- const cmd = parseDiscordAcpCommand(ctx.text);
420
438
  if (cmd?.kind === 'brain') {
421
439
  clearActiveDelegation(delegationKey);
422
440
  return '🧠 브레인 모드로 복귀했습니다 (active delegation 해제).';
@@ -48,6 +48,7 @@ export const ELANOUS_SLASH_COMMANDS: readonly SlashCommandSchema[] = [
48
48
  { name: 'now', description: '지금 판·칸·결정·자리·최근 맥락 보기', options: [
49
49
  { name: 'topic', description: '좁혀 볼 주제 (선택)', type: 3, required: false },
50
50
  ] },
51
+ { name: 'loops', description: '루프·크론 현황 (늦음·실패 먼저)' },
51
52
  { name: 'sessions', description: '바인딩된 세션 목록' },
52
53
  { name: 'new', description: '새 세션으로 시작 (이전 대화 보존)' },
53
54
  { name: 'attach', description: '이 채널을 다른 세션에 연결', options: [