elanous 0.2.14 → 0.2.16

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 (350) 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-6e7e0377c1db0bdc.js → 1307-b46d45b705e8c9ae.js} +1 -1
  4. package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/899-46946fe8363e1262.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/{9103-7a3ae7f60af3b99f.js → 9103-60fbef33e66f0a46.js} +1 -1
  7. package/apps/pwa/out/_next/static/chunks/app/approvals/page-79c3ed84baaedd03.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-7b2b4f378354869a.js → page-3cb52e5ea8fc2fa5.js} +1 -1
  9. package/apps/pwa/out/_next/static/chunks/app/board/page-a424ef80885d1743.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/app/botlab/page-c1e20d7d338713f1.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/app/bots/{page-1512d91c7491ed62.js → page-482c5dbde699947c.js} +1 -1
  12. package/apps/pwa/out/_next/static/chunks/app/ceo/page-1e962b31d1f11081.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/app/chat/{page-a627db94ec6904a0.js → page-8c2d9526164f5d09.js} +1 -1
  14. package/apps/pwa/out/_next/static/chunks/app/consult/page-ffff6d819fbc6e0f.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/app/control/{page-e6b84ab07d3567d5.js → page-a0e2a6d1bfb52fc5.js} +1 -1
  16. package/apps/pwa/out/_next/static/chunks/app/design-check/page-c53598cfce6156f2.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/app/editor/{page-fed69f9759d248d0.js → page-13a63953f53c6a23.js} +1 -1
  18. package/apps/pwa/out/_next/static/chunks/app/exec/{page-15374909d8f1aebd.js → page-5d3c8429d04f3831.js} +1 -1
  19. package/apps/pwa/out/_next/static/chunks/app/field/{page-012a742f81218b01.js → page-3193bdc0bbab047a.js} +1 -1
  20. package/apps/pwa/out/_next/static/chunks/app/inside/page-76270efaee638f17.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/app/intake/{page-99effd9d893a43dc.js → page-6107ca8ddd5e3bdd.js} +1 -1
  22. package/apps/pwa/out/_next/static/chunks/app/{layout-938de57ae0315e46.js → layout-dce686e582c23937.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/app/live/page-d3d53b146416e422.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/app/loops/{page-160089123a40dd91.js → page-1a61c7ca1ceb3ae7.js} +1 -1
  25. package/apps/pwa/out/_next/static/chunks/app/market/{page-629e1ede482ec6eb.js → page-51f811fa119308b6.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-dacff46049e1557e.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/app/morning/{page-27e99b4e54a849d4.js → page-4167348049166852.js} +1 -1
  28. package/apps/pwa/out/_next/static/chunks/app/not-found-de52e8fdf9095f7a.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/app/observatory/page-36a7d994f4ca0299.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-32cde713be47cddb.js → page-ca3952cd78a83ca2.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/app/ops/release/{page-e7e10cd3386222e5.js → page-5b042736aa113756.js} +1 -1
  32. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-acb749f1e432bcf3.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/app/outputs/page-cd78862707999911.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/app/page-54b9ef8afe6278fd.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/app/reflection/{page-e64caff084f7ce21.js → page-1ebfe049c640e171.js} +1 -1
  36. package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-db07eaffba0df7e3.js → page-57924393e3e4e539.js} +1 -1
  37. package/apps/pwa/out/_next/static/chunks/app/sessions/page-9e0ce6c8129fa641.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-31fa1ce4c4aff9ae.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/app/settings/{page-86f91034bd5cabe5.js → page-0f5cd57ca928f42b.js} +1 -1
  40. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-abe8e3b74bea1ffc.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/app/setup/page-4364ef9f4e7c7f0f.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/app/share/{page-ce4d8a1215ce53a3.js → page-d28573d6792b4275.js} +1 -1
  43. package/apps/pwa/out/_next/static/chunks/app/showroom/{page-58043db78ba519ae.js → page-59f03fda76c03d22.js} +1 -1
  44. package/apps/pwa/out/_next/static/chunks/app/tasks/{page-357750dfb91bf960.js → page-d751de5f8062ff5e.js} +1 -1
  45. package/apps/pwa/out/_next/static/chunks/app/term/{page-274703bc41b71333.js → page-fe1759f94003b32b.js} +1 -1
  46. package/apps/pwa/out/_next/static/chunks/app/today/page-d64c984e855c8c68.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/app/trace/page-86914b0dee8ab7f8.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/vault/{page-3b7e821dc492bdfd.js → page-9afdee66b820c76e.js} +1 -1
  49. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d4aef67abf264874.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/workflows/{page-f8da5dc6f3752276.js → page-1ea64acadd704ace.js} +1 -1
  51. package/apps/pwa/out/_next/static/chunks/app/workspace/{page-2f810b18d89ecba6.js → page-3544f154c87b43ee.js} +1 -1
  52. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-30c2073bfc21c004.js +1 -0
  53. package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +1 -0
  54. package/apps/pwa/out/_next/static/css/{04dabcbcc4bfa6db.css → 085673d826b98b62.css} +1 -1
  55. package/apps/pwa/out/_next/static/rv9cierV0jSOvDBm1g0XB/_buildManifest.js +1 -0
  56. package/apps/pwa/out/approvals/index.html +1 -1
  57. package/apps/pwa/out/approvals/index.txt +15 -15
  58. package/apps/pwa/out/autopilot/index.html +1 -1
  59. package/apps/pwa/out/autopilot/index.txt +15 -15
  60. package/apps/pwa/out/board/index.html +1 -1
  61. package/apps/pwa/out/board/index.txt +15 -15
  62. package/apps/pwa/out/botlab/index.html +1 -1
  63. package/apps/pwa/out/botlab/index.txt +15 -15
  64. package/apps/pwa/out/bots/index.html +1 -1
  65. package/apps/pwa/out/bots/index.txt +15 -15
  66. package/apps/pwa/out/ceo/index.html +1 -0
  67. package/apps/pwa/out/ceo/index.txt +33 -0
  68. package/apps/pwa/out/chat/index.html +1 -1
  69. package/apps/pwa/out/chat/index.txt +15 -15
  70. package/apps/pwa/out/consult/index.html +1 -1
  71. package/apps/pwa/out/consult/index.txt +15 -15
  72. package/apps/pwa/out/control/index.html +1 -1
  73. package/apps/pwa/out/control/index.txt +15 -15
  74. package/apps/pwa/out/design-check/index.html +1 -1
  75. package/apps/pwa/out/design-check/index.txt +15 -15
  76. package/apps/pwa/out/editor/index.html +1 -1
  77. package/apps/pwa/out/editor/index.txt +15 -15
  78. package/apps/pwa/out/exec/index.html +1 -1
  79. package/apps/pwa/out/exec/index.txt +15 -15
  80. package/apps/pwa/out/field/index.html +1 -1
  81. package/apps/pwa/out/field/index.txt +15 -15
  82. package/apps/pwa/out/healthz +1 -1
  83. package/apps/pwa/out/index.html +1 -1
  84. package/apps/pwa/out/index.txt +15 -15
  85. package/apps/pwa/out/inside/index.html +1 -1
  86. package/apps/pwa/out/inside/index.txt +15 -15
  87. package/apps/pwa/out/intake/index.html +1 -1
  88. package/apps/pwa/out/intake/index.txt +15 -15
  89. package/apps/pwa/out/live/index.html +1 -1
  90. package/apps/pwa/out/live/index.txt +15 -15
  91. package/apps/pwa/out/loops/index.html +1 -1
  92. package/apps/pwa/out/loops/index.txt +15 -15
  93. package/apps/pwa/out/market/index.html +1 -1
  94. package/apps/pwa/out/market/index.txt +15 -15
  95. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  96. package/apps/pwa/out/missions/placeholder/index.txt +15 -15
  97. package/apps/pwa/out/morning/index.html +1 -1
  98. package/apps/pwa/out/morning/index.txt +15 -15
  99. package/apps/pwa/out/observatory/index.html +1 -1
  100. package/apps/pwa/out/observatory/index.txt +15 -15
  101. package/apps/pwa/out/ops/checklist/index.html +1 -1
  102. package/apps/pwa/out/ops/checklist/index.txt +15 -15
  103. package/apps/pwa/out/ops/release/index.html +1 -1
  104. package/apps/pwa/out/ops/release/index.txt +15 -15
  105. package/apps/pwa/out/ops/seats/index.html +1 -1
  106. package/apps/pwa/out/ops/seats/index.txt +15 -15
  107. package/apps/pwa/out/outputs/index.html +1 -1
  108. package/apps/pwa/out/outputs/index.txt +15 -15
  109. package/apps/pwa/out/reflection/index.html +1 -1
  110. package/apps/pwa/out/reflection/index.txt +16 -16
  111. package/apps/pwa/out/scheduler/index.html +1 -1
  112. package/apps/pwa/out/scheduler/index.txt +15 -15
  113. package/apps/pwa/out/sessions/index.html +1 -1
  114. package/apps/pwa/out/sessions/index.txt +15 -15
  115. package/apps/pwa/out/settings/devices/index.html +1 -1
  116. package/apps/pwa/out/settings/devices/index.txt +15 -15
  117. package/apps/pwa/out/settings/index.html +1 -1
  118. package/apps/pwa/out/settings/index.txt +15 -15
  119. package/apps/pwa/out/setup/done/index.html +1 -1
  120. package/apps/pwa/out/setup/done/index.txt +15 -15
  121. package/apps/pwa/out/setup/index.html +1 -1
  122. package/apps/pwa/out/setup/index.txt +15 -15
  123. package/apps/pwa/out/share/index.html +1 -1
  124. package/apps/pwa/out/share/index.txt +15 -15
  125. package/apps/pwa/out/showroom/index.html +1 -1
  126. package/apps/pwa/out/showroom/index.txt +15 -15
  127. package/apps/pwa/out/tasks/index.html +1 -1
  128. package/apps/pwa/out/tasks/index.txt +15 -15
  129. package/apps/pwa/out/term/index.html +1 -1
  130. package/apps/pwa/out/term/index.txt +15 -15
  131. package/apps/pwa/out/today/index.html +1 -1
  132. package/apps/pwa/out/today/index.txt +15 -15
  133. package/apps/pwa/out/trace/index.html +1 -1
  134. package/apps/pwa/out/trace/index.txt +15 -15
  135. package/apps/pwa/out/vault/index.html +1 -1
  136. package/apps/pwa/out/vault/index.txt +15 -15
  137. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  138. package/apps/pwa/out/workflows/chat-ui/index.txt +15 -15
  139. package/apps/pwa/out/workflows/index.html +1 -1
  140. package/apps/pwa/out/workflows/index.txt +15 -15
  141. package/apps/pwa/out/workspace/index.html +1 -1
  142. package/apps/pwa/out/workspace/index.txt +15 -15
  143. package/apps/pwa/out/worktrees/index.html +1 -1
  144. package/apps/pwa/out/worktrees/index.txt +15 -15
  145. package/graphs/doc-gen/doc-gen.yaml +24 -0
  146. package/graphs/doc-gen/recipes.yaml +3 -0
  147. package/graphs/heal/heal-loop.yaml +1 -1
  148. package/graphs/implement-loop.yaml +1 -1
  149. package/graphs/orchestrator/orchestrator.yaml +7 -0
  150. package/graphs/orchestrator/recipes.yaml +3 -0
  151. package/graphs/release/recipes.yaml +3 -0
  152. package/graphs/release/release-loop.yaml +4 -2
  153. package/graphs/steward/recipes.yaml +2 -1
  154. package/package.json +1 -1
  155. package/scripts/brand/check.ts +7 -5
  156. package/scripts/ci-public-export-import-gate.ts +154 -0
  157. package/scripts/ci-test-interference-gate.ts +5 -1
  158. package/scripts/ci-typecheck-changed.ts +82 -31
  159. package/scripts/docs/doc-rot.ts +164 -0
  160. package/scripts/docs/generate-graph-anatomy.ts +437 -0
  161. package/scripts/docs-cli-check.ts +4 -4
  162. package/scripts/expose-rubric-suggest.ts +18 -12
  163. package/scripts/goals/goal-census.ts +343 -0
  164. package/scripts/lecture-note/node.ts +31 -11
  165. package/scripts/lessons/handbook-page.ts +30 -3
  166. package/scripts/marketing/generate-ax-brief.ts +140 -0
  167. package/scripts/marketing/generate-feature-map.ts +118 -0
  168. package/scripts/measure/author-section-contribution.ts +84 -0
  169. package/scripts/release-loop/checklist-gate-node.ts +53 -14
  170. package/scripts/release-loop/cut-branch.ts +43 -13
  171. package/scripts/release-loop/gate-node.ts +82 -14
  172. package/scripts/release-loop/vault-note-node.ts +257 -8
  173. package/scripts/release-story/draft.ts +57 -9
  174. package/scripts/rhythm/daily.ts +263 -76
  175. package/scripts/rhythm/monthly.ts +97 -0
  176. package/scripts/rhythm/weekly.ts +30 -9
  177. package/scripts/smart-merge/resync-shadow.ts +147 -0
  178. package/src/acp/client.ts +17 -2
  179. package/src/agent-env/profile.ts +161 -0
  180. package/src/agent-mission/driver.ts +51 -1
  181. package/src/agent-mission/supervisor-metrics-cli.ts +40 -0
  182. package/src/agent-mission/supervisor-metrics.ts +218 -0
  183. package/src/agent-substrate/pr-reviewer.ts +2 -0
  184. package/src/ask-user-question/pending-questions.ts +5 -0
  185. package/src/auth/github-app-token.ts +112 -13
  186. package/src/autopilot/build/llm-conflict-merge.ts +32 -6
  187. package/src/autopilot/track-agent-shadow.ts +2 -1
  188. package/src/briefing/brief-items.ts +276 -0
  189. package/src/budget/usage-outcomes.ts +204 -0
  190. package/src/claims/claims-render.ts +14 -5
  191. package/src/cli/agent-env-cli.ts +21 -0
  192. package/src/cli/brief-cli.ts +73 -0
  193. package/src/cli/decisions-cli.ts +9 -0
  194. package/src/cli/doctor-cli.ts +23 -1
  195. package/src/cli/doctor-node-modules.ts +147 -0
  196. package/src/cli/intake-cli.ts +46 -17
  197. package/src/cli/know-cli.ts +20 -0
  198. package/src/cli/logs-cli.ts +43 -1
  199. package/src/cli/pod-cli.ts +2 -0
  200. package/src/cli/pr-cli.ts +80 -12
  201. package/src/cli/release-cli.ts +66 -29
  202. package/src/cli/seat-requests-cli.ts +52 -0
  203. package/src/cli/self-update.ts +200 -1
  204. package/src/cli/usage-cli.ts +26 -0
  205. package/src/context-bus/context-now-public.ts +16 -6
  206. package/src/context-bus/context-now.ts +3 -1
  207. package/src/context-bus/coord-events.ts +6 -4
  208. package/src/decisions/decision-cards.ts +33 -7
  209. package/src/decisions/proact-meter.ts +286 -0
  210. package/src/decisions/proact1-lite.ts +86 -0
  211. package/src/decisions/web-push-decision-cards.ts +1 -1
  212. package/src/discord-self-message.ts +9 -0
  213. package/src/domains/alert-audit.ts +271 -0
  214. package/src/domains/core-tools.ts +4 -3
  215. package/src/domains/ops-facts-tool.ts +31 -1
  216. package/src/flow-loop/split.ts +24 -0
  217. package/src/harness/harness-cli-command.ts +147 -17
  218. package/src/harness/harness-incidents.ts +2 -0
  219. package/src/harness/harness-pod-dispatch.ts +67 -8
  220. package/src/harness/harness-queue.ts +380 -50
  221. package/src/harness/harness-seams.ts +13 -3
  222. package/src/harness/helper-repair.ts +218 -0
  223. package/src/harness/helper-scan.ts +27 -4
  224. package/src/harness/launch-stamp.ts +236 -0
  225. package/src/harness/ledger-write-guard-child.fixture.ts +12 -0
  226. package/src/harness/ledger-write-guard.ts +35 -0
  227. package/src/harness/nested-elanous-policy.ts +114 -0
  228. package/src/harness/pod-memory-policy.ts +15 -0
  229. package/src/harness/repository-name.ts +55 -0
  230. package/src/hq/fence-audit.ts +90 -0
  231. package/src/hq/hq.ts +70 -6
  232. package/src/hq/lease.ts +9 -7
  233. package/src/hq/move-plan.ts +126 -0
  234. package/src/hq/promote.ts +31 -0
  235. package/src/hq/transfer.ts +185 -0
  236. package/src/index.ts +189 -13
  237. package/src/intake-plane/check.ts +63 -0
  238. package/src/intake-plane/digest.ts +65 -21
  239. package/src/intake-plane/front-classifier.ts +6 -0
  240. package/src/intake-plane/items.ts +3 -2
  241. package/src/intake-plane/lens.ts +177 -0
  242. package/src/intake-plane/route.ts +145 -1
  243. package/src/knowledge/know-find.ts +105 -0
  244. package/src/llm.ts +75 -19
  245. package/src/loops/budget.ts +36 -5
  246. package/src/loops/checker.ts +2 -2
  247. package/src/loops/orchestrator/author-depth.ts +65 -9
  248. package/src/loops/orchestrator/author-input-check.ts +8 -3
  249. package/src/loops/orchestrator/author-ledger.ts +3 -1
  250. package/src/loops/orchestrator/finish-rate.ts +14 -3
  251. package/src/loops/orchestrator/seat-share.ts +56 -0
  252. package/src/loops/orchestrator/tick.ts +107 -29
  253. package/src/loops/orchestrator/traffic.ts +117 -11
  254. package/src/loops/registry.ts +142 -61
  255. package/src/maturity/bot-slash-catalog.ts +11 -2
  256. package/src/maturity/feature-maturity.ts +4 -3
  257. package/src/maturity/tui-slash-availability.ts +40 -0
  258. package/src/mss/logging/log-store.ts +35 -4
  259. package/src/nexus/api/http-server.ts +28 -14
  260. package/src/nexus/api/loop-edges.ts +173 -0
  261. package/src/nexus/api/schedules-read.ts +13 -0
  262. package/src/nexus/api/seat-requests.ts +79 -56
  263. package/src/oauth/overload-failover-launch.ts +75 -0
  264. package/src/oauth/overload-failover.ts +137 -0
  265. package/src/prompt-enhance/enhance.ts +6 -1
  266. package/src/release-loop/auto-start.ts +6 -3
  267. package/src/release-loop/checklist.ts +19 -5
  268. package/src/release-loop/feature-store.ts +59 -31
  269. package/src/release-loop/merged-pr-checklist.ts +125 -0
  270. package/src/release-loop/placement.ts +98 -29
  271. package/src/seat-dispatch/seat-ask.ts +33 -6
  272. package/src/seat-dispatch/seat-request-eligibility.ts +19 -0
  273. package/src/seat-dispatch/seat-request-ledger.ts +133 -0
  274. package/src/seat-loop/seat-loop.ts +215 -12
  275. package/src/self-dev/ask-launch-flow.ts +22 -1
  276. package/src/self-dev/authored-goal-trace.ts +64 -0
  277. package/src/self-dev/dev-cli.ts +19 -1
  278. package/src/self-dev/dev-pipeline.ts +14 -9
  279. package/src/self-dev/draft-sweep.ts +90 -10
  280. package/src/self-dev/draft-triage-rules.ts +25 -8
  281. package/src/self-dev/entrance-registry.ts +10 -6
  282. package/src/self-dev/old-door.ts +73 -0
  283. package/src/self-dev/orchestrate.ts +15 -1
  284. package/src/self-dev/review-provider-fallback.ts +6 -1
  285. package/src/self-dev/run-supervisor.ts +49 -2
  286. package/src/self-implement/app-api-budget.ts +166 -0
  287. package/src/self-implement/budget-gate.ts +7 -5
  288. package/src/self-implement/follow-up-goals.ts +289 -0
  289. package/src/self-implement/frozen-merges.ts +3 -2
  290. package/src/self-implement/goal-author.ts +50 -6
  291. package/src/self-implement/goal-clarification-escalation.ts +5 -0
  292. package/src/self-implement/heal-intake.ts +120 -2
  293. package/src/self-implement/heal-triage.ts +44 -0
  294. package/src/self-implement/host-regate.ts +9 -2
  295. package/src/self-implement/l8-merge-queue.ts +132 -0
  296. package/src/self-implement/orchestrator.ts +325 -47
  297. package/src/self-implement/pipeline-shape.ts +1 -1
  298. package/src/self-implement/revert-guard.ts +42 -7
  299. package/src/self-implement/run-ledger.ts +6 -0
  300. package/src/self-implement/run-status-mapping.ts +2 -0
  301. package/src/self-implement/seams.ts +45 -16
  302. package/src/self-implement/self-implement-cli.ts +1 -0
  303. package/src/self-implement/speed-claims.ts +338 -0
  304. package/src/session-runtime/index.ts +2 -0
  305. package/src/session-runtime/retry-api.ts +87 -0
  306. package/src/session-runtime/retry-policy.ts +107 -6
  307. package/src/steward/triage.ts +74 -3
  308. package/src/steward/working-backwards.ts +20 -2
  309. package/src/task-cards/card-cli.ts +25 -0
  310. package/src/task-cards/card-store.ts +5 -3
  311. package/src/task-orchestrator/surfaces/pod-artifact-return.ts +63 -48
  312. package/src/task-orchestrator/surfaces/pod-command-job.ts +84 -9
  313. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +48 -0
  314. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +4 -3
  315. package/src/task-orchestrator/surfaces/pod-ledger-prod-sink.ts +75 -0
  316. package/src/task-orchestrator/surfaces/pod-pool.ts +70 -9
  317. package/src/task-orchestrator/surfaces/pod-source-receive.ts +37 -1
  318. package/src/task-orchestrator/surfaces/self-implement-pod.ts +166 -46
  319. package/src/task-orchestrator/surfaces/self-implement.ts +7 -1
  320. package/src/telegram-commands.ts +5 -0
  321. package/src/telegram.ts +72 -4
  322. package/src/user-config.ts +64 -7
  323. package/src/version/packed-revision.json +1 -1
  324. package/src/workflow-runtime/nodes/bash.ts +9 -1
  325. package/apps/pwa/out/_next/static/chunks/4562-071d8bac44a01069.js +0 -1
  326. package/apps/pwa/out/_next/static/chunks/5977-2df489622a52a33a.js +0 -1
  327. package/apps/pwa/out/_next/static/chunks/app/approvals/page-74383f890f075511.js +0 -1
  328. package/apps/pwa/out/_next/static/chunks/app/board/page-c496bb16da85385f.js +0 -1
  329. package/apps/pwa/out/_next/static/chunks/app/botlab/page-1abfe3aec3be6bdc.js +0 -1
  330. package/apps/pwa/out/_next/static/chunks/app/consult/page-fb260b9283dbf646.js +0 -1
  331. package/apps/pwa/out/_next/static/chunks/app/design-check/page-61ec1d2e92bdd50a.js +0 -1
  332. package/apps/pwa/out/_next/static/chunks/app/inside/page-fd7e200d4d83fbd7.js +0 -1
  333. package/apps/pwa/out/_next/static/chunks/app/live/page-d47266766766d227.js +0 -1
  334. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-2ce5471926761fdd.js +0 -1
  335. package/apps/pwa/out/_next/static/chunks/app/not-found-e995d30f6800b6bf.js +0 -1
  336. package/apps/pwa/out/_next/static/chunks/app/observatory/page-39f89c53038a51d4.js +0 -1
  337. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-5a753bb4ec0cc511.js +0 -1
  338. package/apps/pwa/out/_next/static/chunks/app/outputs/page-04250f0e2c0d03df.js +0 -1
  339. package/apps/pwa/out/_next/static/chunks/app/page-341480ed8e3c1ea8.js +0 -1
  340. package/apps/pwa/out/_next/static/chunks/app/sessions/page-8ac8d71c2adf9c8a.js +0 -1
  341. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-e1e47d8d940aba3d.js +0 -1
  342. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-94dc383d3d54db1a.js +0 -1
  343. package/apps/pwa/out/_next/static/chunks/app/setup/page-b3759cc14da80908.js +0 -1
  344. package/apps/pwa/out/_next/static/chunks/app/today/page-194dce4a83143fce.js +0 -1
  345. package/apps/pwa/out/_next/static/chunks/app/trace/page-35bdeae2e2c17147.js +0 -1
  346. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-16bef3da05a900c3.js +0 -1
  347. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-134c1bc762aee62c.js +0 -1
  348. package/apps/pwa/out/_next/static/css/2e33713c444d5b39.css +0 -1
  349. package/apps/pwa/out/_next/static/x6mjI6vqOkVVu0jFuDkz1/_buildManifest.js +0 -1
  350. /package/apps/pwa/out/_next/static/{x6mjI6vqOkVVu0jFuDkz1 → rv9cierV0jSOvDBm1g0XB}/_ssgManifest.js +0 -0
@@ -0,0 +1,166 @@
1
+ import { execFile as execFileCb } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ import { debug } from '../debug/log.js';
4
+ import { releaseGitDiffPaths, releasePrFilePaths } from '../self-dev/release-path-guard.js';
5
+
6
+ const defaultExecFile = promisify(execFileCb);
7
+ const GIT_TIMEOUT = 20_000;
8
+
9
+ export type AppApiExecFile = (
10
+ file: string,
11
+ args: readonly string[],
12
+ opts: { cwd: string; encoding: 'utf8'; timeout: number; maxBuffer?: number; env?: NodeJS.ProcessEnv },
13
+ ) => Promise<{ stdout: string; stderr?: string }>;
14
+
15
+ let execFileImpl: AppApiExecFile = (file, args, opts) => defaultExecFile(file, [...args], opts);
16
+
17
+ /** Tests inject the `gh` runner. Production keeps `execFile`. */
18
+ export function setAppApiExecFileForTest(impl: AppApiExecFile | undefined): void {
19
+ execFileImpl = impl ?? ((file, args, opts) => defaultExecFile(file, [...args], opts));
20
+ }
21
+
22
+ /** App-token `gh` calls only. Caller names are fixed strings; the token value is never logged. */
23
+ const GH_APP_CALLERS = {
24
+ 'post-pr-comment-history': 'postPrComment',
25
+ 'post-pr-comment': 'postPrComment',
26
+ 'find-pr-comment': 'findPrComment',
27
+ 'edit-pr-comment': 'editPrComment',
28
+ 'add-pr-label-list': 'addPrLabel',
29
+ 'add-pr-label-create': 'addPrLabel',
30
+ 'add-pr-label': 'addPrLabel',
31
+ 'read-pr-files': 'readPrFiles',
32
+ } as const;
33
+
34
+ export type GhAppCaller = keyof typeof GH_APP_CALLERS;
35
+
36
+ export const APP_API_RATE_LIMIT_RETRY_CAP_MS = 15 * 60_000;
37
+
38
+ export class AppApiRateLimitError extends Error {
39
+ readonly rateLimited = true as const;
40
+ readonly status: 403 | 429;
41
+ readonly resetAtMs: number | undefined;
42
+ constructor(message: string, status: 403 | 429, resetAtMs: number | undefined) {
43
+ super(message);
44
+ this.name = 'AppApiRateLimitError';
45
+ this.status = status;
46
+ this.resetAtMs = resetAtMs;
47
+ }
48
+ }
49
+
50
+ export function isAppApiRateLimitFailure(status: number | undefined, text: string, remaining?: string): boolean {
51
+ if (remaining === '0') return true;
52
+ if (status !== undefined && status !== 403 && status !== 429) return false;
53
+ return /API rate limit exceeded|rate limit/i.test(text) && (status === 403 || status === 429 || status === undefined);
54
+ }
55
+
56
+ function ghHeader(stderr: string, name: string): string | undefined {
57
+ const match = new RegExp(`^${name}:\\s*(.*)$`, 'im').exec(stderr);
58
+ return match?.[1]?.trim();
59
+ }
60
+
61
+ function ghStatusCode(stderr: string, error: unknown): number | undefined {
62
+ const fromHeader = ghHeader(stderr, 'HTTP') ?? ghHeader(stderr, 'status');
63
+ const headerCode = fromHeader ? Number.parseInt(fromHeader, 10) : NaN;
64
+ if (Number.isFinite(headerCode)) return headerCode;
65
+ const text = `${stderr}\n${error instanceof Error ? error.message : String(error ?? '')}`;
66
+ const match = /\b(403|429)\b/.exec(text);
67
+ return match ? Number(match[1]) : undefined;
68
+ }
69
+
70
+ /** Wait until X-RateLimit-Reset, never longer than 15 minutes. Returns false when the reset is already past or unreadable. */
71
+ export async function waitForAppApiRateLimitReset(
72
+ resetAtMs: number | undefined,
73
+ now = Date.now(),
74
+ sleep: (ms: number) => Promise<void> = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
75
+ ): Promise<boolean> {
76
+ if (resetAtMs === undefined || !Number.isFinite(resetAtMs)) return false;
77
+ const waitMs = Math.min(Math.max(0, resetAtMs - now), APP_API_RATE_LIMIT_RETRY_CAP_MS);
78
+ if (waitMs <= 0) return false;
79
+ await sleep(waitMs);
80
+ return true;
81
+ }
82
+
83
+ export async function runGhApp(
84
+ caller: GhAppCaller,
85
+ args: readonly string[],
86
+ opts: { cwd: string; encoding: 'utf8'; timeout: number; maxBuffer?: number; env: NodeJS.ProcessEnv },
87
+ ): Promise<{ stdout: string; stderr: string }> {
88
+ let stdout = '';
89
+ let stderr = '';
90
+ let status: number | undefined;
91
+ let error: unknown;
92
+ try {
93
+ const result = await execFileImpl('gh', args, opts);
94
+ stdout = result.stdout;
95
+ stderr = result.stderr ?? '';
96
+ status = 200;
97
+ } catch (caught) {
98
+ error = caught;
99
+ const failed = caught as { stdout?: string; stderr?: string };
100
+ stdout = typeof failed.stdout === 'string' ? failed.stdout : '';
101
+ stderr = typeof failed.stderr === 'string' ? failed.stderr : '';
102
+ status = ghStatusCode(stderr, caught);
103
+ }
104
+ const remaining = ghHeader(stderr, 'X-RateLimit-Remaining');
105
+ debug.log('github.app-api', 'call', {
106
+ caller: GH_APP_CALLERS[caller],
107
+ endpoint: args.filter((arg) => !arg.startsWith('-')).at(-1) ?? 'gh',
108
+ status: status ?? 0,
109
+ remaining: remaining ?? '',
110
+ });
111
+ if (error) {
112
+ const text = `${stderr}\n${error instanceof Error ? error.message : String(error)}`;
113
+ if (isAppApiRateLimitFailure(status, text, remaining)) {
114
+ const reset = ghHeader(stderr, 'X-RateLimit-Reset');
115
+ const resetSeconds = reset ? Number(reset) : NaN;
116
+ throw new AppApiRateLimitError(
117
+ text.trim() || 'API rate limit exceeded',
118
+ status === 429 ? 429 : 403,
119
+ Number.isFinite(resetSeconds) ? resetSeconds * 1000 : undefined,
120
+ );
121
+ }
122
+ throw error;
123
+ }
124
+ return { stdout, stderr };
125
+ }
126
+
127
+ export async function readPrFilesWithRateLimitFallback(input: {
128
+ number: number;
129
+ cwd: string;
130
+ base?: string;
131
+ env: NodeJS.ProcessEnv;
132
+ sleep?: (ms: number) => Promise<void>;
133
+ now?: () => number;
134
+ }): Promise<string[]> {
135
+ const env = input.env;
136
+ const endpoint = [
137
+ 'api', '--paginate', '--slurp', '--method', 'GET', '-f', 'per_page=100',
138
+ `repos/{owner}/{repo}/pulls/${input.number}/files`,
139
+ ];
140
+ const ghOpts = { cwd: input.cwd, encoding: 'utf8' as const, timeout: GIT_TIMEOUT, maxBuffer: 64 * 1024 * 1024, env };
141
+ const fromApi = async () => releasePrFilePaths(JSON.parse((await runGhApp('read-pr-files', endpoint, ghOpts)).stdout));
142
+ const fromGit = async (): Promise<string[]> => {
143
+ const base = input.base ?? 'origin/main';
144
+ const { stdout } = await execFileImpl('git', ['diff', '--find-renames', '--name-status', '-z', `${base}...HEAD`], {
145
+ cwd: input.cwd, encoding: 'utf8', timeout: GIT_TIMEOUT, maxBuffer: 64 * 1024 * 1024,
146
+ });
147
+ return releaseGitDiffPaths(stdout);
148
+ };
149
+ try {
150
+ return await fromApi();
151
+ } catch (error) {
152
+ if (!(error instanceof AppApiRateLimitError)) throw error;
153
+ try {
154
+ return await fromGit();
155
+ } catch (gitError) {
156
+ const waited = await waitForAppApiRateLimitReset(error.resetAtMs, input.now?.() ?? Date.now(), input.sleep);
157
+ if (!waited) throw error;
158
+ try {
159
+ return await fromApi();
160
+ } catch (retryError) {
161
+ if (retryError instanceof AppApiRateLimitError) throw retryError;
162
+ throw gitError;
163
+ }
164
+ }
165
+ }
166
+ }
@@ -9,6 +9,7 @@ import type { UsageSnapshot } from '../budget/types.js';
9
9
  import { debug } from '../debug/log.js';
10
10
  import {
11
11
  DEFAULT_BUDGET_GATE_MAX_USED_PERCENT,
12
+ LAUNCH_GROK_USED_PERCENT_CAP,
12
13
  type BudgetGateMaxUsedPercent,
13
14
  type BudgetGateOnShortfall,
14
15
  } from '../user-config.js';
@@ -137,16 +138,17 @@ function slotUsable(
137
138
  return { usable: measuredUsable || unmeasured, reason: formatCodexReason(input.codexCandidates, cap), ...(unmeasured ? { unmeasured: true } : {}) };
138
139
  }
139
140
  if (provider === GROK_PROVIDER) {
140
- const cap = capFor(input.maxUsedPercent, GROK_PROVIDER);
141
+ const configured = capFor(input.maxUsedPercent, GROK_PROVIDER);
142
+ // 발사 관문은 `elanous usage` 와 같은 주간 사용률에 80% 상한을 실제로 적용한다.
143
+ // «못 쟀다»(undefined)는 통과가 아니다. decideBudget 은 설정 상한을 그대로 쓴다.
144
+ const cap = allowUnmeasured ? LAUNCH_GROK_USED_PERCENT_CAP : configured;
141
145
  const known = typeof input.grokUsedPercent === 'number' && Number.isFinite(input.grokUsedPercent);
142
- const unmeasured = allowUnmeasured && !known;
143
- const usable = unmeasured || (cap === undefined
146
+ const usable = cap === undefined
144
147
  ? known
145
- : known && (input.grokUsedPercent as number) < cap);
148
+ : known && (input.grokUsedPercent as number) < cap;
146
149
  return {
147
150
  usable,
148
151
  reason: formatGrokReason(input.grokUsedPercent, cap),
149
- ...(unmeasured ? { unmeasured: true } : {}),
150
152
  };
151
153
  }
152
154
  return { usable: false, reason: `${provider}: 예산 판정 없음` };
@@ -0,0 +1,289 @@
1
+ // FOLLOWUP-LOOP① — after a harness PR merges, leftover findings become one
2
+ // follow-up goal draft. Default mode is shadow: record only. `live` also
3
+ // enqueues through the same function as `harness queue add`.
4
+ //
5
+ // The test process injects every store. Nothing here opens the operational
6
+ // ledger or queue unless the caller passes those seams.
7
+
8
+ import { createHash } from 'node:crypto';
9
+ import { appendFileSync, mkdirSync, readFileSync } from 'node:fs';
10
+ import { dirname, join } from 'node:path';
11
+ import { addHarnessQueue, type QueueSeat } from '../harness/harness-queue.js';
12
+ import { debug } from '../debug/log.js';
13
+ import { withFileLockSync } from '../storage/file-lock.js';
14
+ import { elanousStateRoot } from '../autopilot/state-paths.js';
15
+ import { devVersion, listChecklist, parseOwner } from '../release-loop/checklist.js';
16
+ import { getUserConfig } from '../user-config.js';
17
+
18
+ export const FOLLOW_UP_DEPTH_LIMIT = 3;
19
+ export const FOLLOW_UP_OP_CARD_REQUIRED = 'OP 카드 필요';
20
+
21
+ const REMAINING_SECTION = '남은 조각';
22
+ const SEATS = ['OP', 'TC', 'MK', 'UX'] as const;
23
+
24
+ export type FollowUpMode = 'shadow' | 'live';
25
+
26
+ export interface FollowUpDraftRecord {
27
+ prNumber: number;
28
+ cellId: string;
29
+ depth: number;
30
+ draftHash: string;
31
+ kind: 'draft' | 'op-card-required';
32
+ originalAskFirstLine: string;
33
+ remainings: readonly string[];
34
+ draft?: string;
35
+ note?: string;
36
+ queued: boolean;
37
+ /** live 대기열 넣기가 실패했을 때의 이유(초안은 그대로 남는다). */
38
+ enqueueError?: string;
39
+ seat?: QueueSeat;
40
+ }
41
+
42
+ export interface FollowUpMergeInput {
43
+ prNumber: number;
44
+ /** Result field set when the review budget is exhausted. */
45
+ followUpMustFix?: readonly string[];
46
+ /** Last review's non-blocking findings. */
47
+ shouldFix?: readonly string[];
48
+ /** Opened PR body — the «남은 조각» section is read verbatim. */
49
+ prBody?: string;
50
+ /** Original request. Only its first line enters the draft. */
51
+ feature: string;
52
+ /** Release-cell id. Absent means the goal file's GoalId, else the feature's first token. */
53
+ cellId?: string;
54
+ goalFile?: string;
55
+ }
56
+
57
+ export interface FollowUpSeams {
58
+ stateRoot?: string;
59
+ mode?: FollowUpMode;
60
+ /**
61
+ * Prior follow-up records for this cell, oldest first.
62
+ * Absent means the caller did not hand over a store — the test process writes nothing.
63
+ * Production `defaultSeams` always sets this to the state-root ledger.
64
+ */
65
+ readDrafts?: (cellId: string) => readonly FollowUpDraftRecord[];
66
+ appendDraft?: (record: FollowUpDraftRecord) => void;
67
+ /** Same function `harness queue add` calls. Injected in tests so the real queue is never opened. */
68
+ enqueue?: (input: { seat: QueueSeat; say: string; idempotencyKey: string }) => Promise<unknown> | unknown;
69
+ /** Release-cell owner (`TC`, `TC/rel`, …). Absent means look up the developing checklist. */
70
+ cellOwner?: (cellId: string) => string | undefined;
71
+ log?: (category: string, event: string, data?: unknown) => void;
72
+ }
73
+
74
+ export function followUpDraftsPath(stateRoot = elanousStateRoot()): string {
75
+ return join(stateRoot, 'follow-up', 'drafts.jsonl');
76
+ }
77
+
78
+ export function readFollowUpMode(read: () => { harness?: { followUp?: unknown } } = getUserConfig): FollowUpMode {
79
+ return read().harness?.followUp === 'live' ? 'live' : 'shadow';
80
+ }
81
+
82
+ /** Lines under a markdown heading «남은 조각», verbatim, without the bullet mark. */
83
+ export function remainingPiecesFromPrBody(body: string | undefined): string[] {
84
+ if (!body) return [];
85
+ const lines = body.split(/\r?\n/);
86
+ const start = lines.findIndex((line) => line.trim() === `## ${REMAINING_SECTION}`);
87
+ if (start < 0) return [];
88
+ const out: string[] = [];
89
+ for (const line of lines.slice(start + 1)) {
90
+ if (/^#{1,6}\s/.test(line)) break;
91
+ const item = line.match(/^\s*[-*]\s+(.+?)\s*$/)?.[1];
92
+ if (item) out.push(item);
93
+ }
94
+ return out;
95
+ }
96
+
97
+ export function collectRemainings(input: Pick<FollowUpMergeInput, 'followUpMustFix' | 'shouldFix' | 'prBody'>): string[] {
98
+ const seen = new Set<string>();
99
+ const out: string[] = [];
100
+ for (const item of [
101
+ ...(input.followUpMustFix ?? []),
102
+ ...(input.shouldFix ?? []),
103
+ ...remainingPiecesFromPrBody(input.prBody),
104
+ ]) {
105
+ const text = item.trim();
106
+ if (!text || seen.has(text)) continue;
107
+ seen.add(text);
108
+ out.push(text);
109
+ }
110
+ return out;
111
+ }
112
+
113
+ export function followUpCellId(input: Pick<FollowUpMergeInput, 'cellId' | 'goalFile' | 'feature'>, readGoal: (path: string) => string = (path) => readFileSync(path, 'utf8')): string | undefined {
114
+ const explicit = input.cellId?.trim();
115
+ if (explicit) return explicit;
116
+ if (input.goalFile) {
117
+ try {
118
+ const goalId = readGoal(input.goalFile).match(/^- GoalId:\s*(\S+)\s*$/m)?.[1];
119
+ if (goalId) return goalId;
120
+ } catch { /* a missing goal file is not a cell id */ }
121
+ }
122
+ // 자리 발사 머리(`[MK 자리 · 0.2.16 체크리스트 칸 FOLLOWUP-LOOP · …]`)가 칸을 «명시»한 경우만 읽는다.
123
+ // 추정(요청문 첫 단어)으로는 기록하지 않는다 — 다른 골이 한 칸으로 합쳐져 깊이·담당이 틀린다.
124
+ // 자리 머리 줄만 본다 — 본문이 다른 칸을 언급해도 그 칸으로 기록하지 않는다.
125
+ // 저작 Pod 규칙(OP 10-06)에서 첫 줄은 `대상 경로:` 이고 머리는 둘째 줄이라, 앞의 `대상 경로:` 줄 하나는 건너뛴다.
126
+ const lines = input.feature.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
127
+ const header = /^대상 경로:/u.test(lines[0] ?? '') ? lines[1] : lines[0];
128
+ return header?.match(/^\[(?:OP|MK|TC|UX) 자리 · [^\]]*?체크리스트 칸 ([A-Z][A-Z0-9-]*[A-Z0-9])(?: ·|\])/u)?.[1];
129
+ }
130
+
131
+ export function originalAskFirstLine(feature: string): string {
132
+ return feature.split(/\r?\n/).map((line) => line.trim()).find(Boolean) ?? '';
133
+ }
134
+
135
+ export function followUpDraftText(firstLine: string, remainings: readonly string[]): string {
136
+ return [
137
+ firstLine,
138
+ ...remainings,
139
+ 'origin/main 에서 시작 · 남은 것만',
140
+ ].join('\n');
141
+ }
142
+
143
+ export function followUpDraftHash(text: string): string {
144
+ return createHash('sha256').update(text).digest('hex').slice(0, 16);
145
+ }
146
+
147
+ /** Chain length of follow-up goals already recorded for this cell, before this one. */
148
+ export function followUpChainDepth(prior: readonly Pick<FollowUpDraftRecord, 'kind' | 'prNumber'>[]): number {
149
+ // 한 PR 이 «기록 → 대기열 확정» 두 줄을 남길 수 있으니 PR 번호로 센다.
150
+ return new Set(prior.filter((record) => record.kind === 'draft' || record.kind === 'op-card-required').map((record) => record.prNumber)).size;
151
+ }
152
+
153
+ /** Seat = the cell's owner. `TC/rel` keeps the seat `TC`. Unknown or absent owners go to OP (ONEDOOR). */
154
+ export function seatForCellOwner(owner: string | undefined): QueueSeat {
155
+ if (!owner) return 'OP';
156
+ try {
157
+ const seat = parseOwner(owner.trim()).seat;
158
+ if ((SEATS as readonly string[]).includes(seat)) return seat as QueueSeat;
159
+ } catch { /* not a seat */ }
160
+ const head = owner.trim().split(/[/\s·]/)[0] ?? '';
161
+ return (SEATS as readonly string[]).includes(head) ? head as QueueSeat : 'OP';
162
+ }
163
+
164
+ function defaultReadDrafts(stateRoot: string, cellId: string): FollowUpDraftRecord[] {
165
+ let text = '';
166
+ try { text = readFileSync(followUpDraftsPath(stateRoot), 'utf8'); } catch { return []; }
167
+ const out: FollowUpDraftRecord[] = [];
168
+ for (const line of text.split('\n')) {
169
+ if (!line.trim()) continue;
170
+ try {
171
+ const record = JSON.parse(line) as FollowUpDraftRecord;
172
+ if (record.cellId === cellId) out.push(record);
173
+ } catch { /* a torn line does not invent a chain */ }
174
+ }
175
+ return out;
176
+ }
177
+
178
+ /** 같은 PR 은 칸과 무관하게 한 번 — 칸 id 가 달리 들어와도 다시 만들지 않는다. */
179
+ function defaultPrRecorded(stateRoot: string, prNumber: number): boolean {
180
+ let text = '';
181
+ try { text = readFileSync(followUpDraftsPath(stateRoot), 'utf8'); } catch { return false; }
182
+ return text.split('\n').some((line) => {
183
+ if (!line.trim()) return false;
184
+ try { return (JSON.parse(line) as FollowUpDraftRecord).prNumber === prNumber; } catch { return false; }
185
+ });
186
+ }
187
+
188
+ function defaultAppendDraft(stateRoot: string, record: FollowUpDraftRecord): void {
189
+ const path = followUpDraftsPath(stateRoot);
190
+ mkdirSync(dirname(path), { recursive: true });
191
+ appendFileSync(path, `${JSON.stringify(record)}\n`);
192
+ }
193
+
194
+ function defaultCellOwner(cellId: string): string | undefined {
195
+ try {
196
+ return listChecklist(devVersion()).items.find((item) => item.id === cellId)?.owner;
197
+ } catch {
198
+ return undefined;
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Record one follow-up draft for a merged PR. Same PR is recorded once.
204
+ * Depth above {@link FOLLOW_UP_DEPTH_LIMIT} records «OP 카드 필요» instead of a draft.
205
+ * `live` enqueues; `shadow` (the default) only records. Returns null when nothing remains.
206
+ * Every failure is `debug.log` only — the merge result is the caller's to return unchanged.
207
+ */
208
+ export async function recordFollowUpOnMerge(input: FollowUpMergeInput, seams: FollowUpSeams = {}): Promise<FollowUpDraftRecord | null> {
209
+ const log = seams.log ?? ((category, event, data) => { debug.log(category, event, data); });
210
+ try {
211
+ const remainings = collectRemainings(input);
212
+ if (remainings.length === 0) return null;
213
+ // No injected store and no explicit state root: this is the test process.
214
+ // Do not open the operational ledger or the harness queue.
215
+ // A test process handed the operational root (defaultSeams) is refused the same way.
216
+ const testProcess = process.env.NODE_ENV === 'test' || !!process.env.ELANOUS_TEST_HOME;
217
+ if (!seams.readDrafts && !seams.appendDraft && (!seams.stateRoot || (testProcess && seams.stateRoot === elanousStateRoot()))) return null;
218
+ const stateRoot = seams.stateRoot ?? elanousStateRoot();
219
+ const cellId = followUpCellId(input);
220
+ if (!cellId) {
221
+ log('self-implement.follow-up', 'draft-skipped-no-cell', { prNumber: input.prNumber });
222
+ return null;
223
+ }
224
+ const prior = (seams.readDrafts ?? ((id) => defaultReadDrafts(stateRoot, id)))(cellId);
225
+ if (prior.some((record) => record.prNumber === input.prNumber)) return null;
226
+ if (!seams.readDrafts && defaultPrRecorded(stateRoot, input.prNumber)) return null;
227
+ const depth = followUpChainDepth(prior) + 1;
228
+ const firstLine = originalAskFirstLine(input.feature);
229
+ const mode = seams.mode ?? readFollowUpMode();
230
+ const overDepth = depth > FOLLOW_UP_DEPTH_LIMIT;
231
+ const draft = overDepth ? undefined : followUpDraftText(firstLine, remainings);
232
+ const record: FollowUpDraftRecord = {
233
+ prNumber: input.prNumber,
234
+ cellId,
235
+ depth,
236
+ draftHash: followUpDraftHash(draft ?? `${firstLine}\n${FOLLOW_UP_OP_CARD_REQUIRED}`),
237
+ kind: overDepth ? 'op-card-required' : 'draft',
238
+ originalAskFirstLine: firstLine,
239
+ remainings,
240
+ ...(draft ? { draft } : {}),
241
+ ...(overDepth ? { note: FOLLOW_UP_OP_CARD_REQUIRED } : {}),
242
+ queued: false,
243
+ };
244
+ // 운영 원장은 «같은 PR 확인 ⊕ 첫 기록»을 한 잠금 안에서 한다 — 동시 병합 둘이 초안 둘을 만들지 않게.
245
+ const append = seams.appendDraft ?? ((row: FollowUpDraftRecord) => defaultAppendDraft(stateRoot, row));
246
+ const appendFirst = seams.appendDraft
247
+ ? (row: FollowUpDraftRecord) => { append(row); return true; }
248
+ : (row: FollowUpDraftRecord) => (mkdirSync(dirname(followUpDraftsPath(stateRoot)), { recursive: true }), withFileLockSync(`${followUpDraftsPath(stateRoot)}.lock`, () => {
249
+ if (defaultPrRecorded(stateRoot, row.prNumber)) return false;
250
+ defaultAppendDraft(stateRoot, row);
251
+ return true;
252
+ }));
253
+ if (!overDepth && mode === 'live' && draft) record.seat = seatForCellOwner((seams.cellOwner ?? defaultCellOwner)(cellId));
254
+ // 원장 기록을 먼저 확정한다 — 기록이 실패하면 대기열에 넣지 않는다(같은 PR 한 번 · 원장 밖 연결골 0).
255
+ if (!appendFirst(record)) return null;
256
+ if (!overDepth && mode === 'live' && draft && record.seat) {
257
+ const seat = record.seat;
258
+ if (testProcess && !seams.enqueue) return record;
259
+ try {
260
+ await (seams.enqueue ?? ((item) => addHarnessQueue({ seat: item.seat, say: item.say, idempotencyKey: item.idempotencyKey })))({
261
+ seat,
262
+ say: draft,
263
+ idempotencyKey: `follow-up:${input.prNumber}`,
264
+ });
265
+ record.queued = true;
266
+ } catch (error) {
267
+ // 대기열 넣기가 실패해도 초안은 남긴다 — 같은 PR 은 다시 오지 않으므로 여기서 잃으면 영영 없다.
268
+ // queued=false ⊕ enqueueError 로 원장에 남기고, 재투입은 같은 idempotencyKey 로 사람이·다음 수리 노드가 한다.
269
+ record.enqueueError = (error instanceof Error ? error.message : String(error)).slice(0, 300);
270
+ log('self-implement.follow-up', 'enqueue-failed', { prNumber: input.prNumber, seat, error: record.enqueueError });
271
+ }
272
+ // 대기열 결과를 원장에 한 줄 더 — 이 쓰기의 실패는 대기열 실패와 다른 사건이다.
273
+ try {
274
+ append({ ...record });
275
+ } catch (error) {
276
+ log('self-implement.follow-up', 'ledger-confirm-failed', {
277
+ prNumber: input.prNumber, seat, queued: record.queued, error: error instanceof Error ? error.message : String(error),
278
+ });
279
+ }
280
+ }
281
+ return record;
282
+ } catch (error) {
283
+ log('self-implement.follow-up', 'draft-failed', {
284
+ prNumber: input.prNumber,
285
+ error: error instanceof Error ? error.message : String(error),
286
+ });
287
+ return null;
288
+ }
289
+ }
@@ -7,7 +7,7 @@ import { debug } from '../debug/log.js';
7
7
  import { beginLandingMerge, readLandingFreeze, type LandingFreeze } from '../release-loop/landing-freeze.js';
8
8
 
9
9
  /** `manual`: the owning repository could not be determined — a sweep never resumes it; a person does. */
10
- export interface FrozenMerge { prNumber: number; headCommit: string; repoRoot: string; manual?: true }
10
+ export interface FrozenMerge { prNumber: number; headCommit: string; repoRoot: string; manual?: true; /** Host goal document, so a resumed merge can still apply its «이 칸 완료» declaration (CL-AUTO). */ goalFile?: string }
11
11
  /** What a resume sweep hands its merge function: the held entry, marked as already claimed by this sweep. */
12
12
  export type ResumedMerge = FrozenMerge & { resumed: true };
13
13
 
@@ -65,7 +65,8 @@ function readQueue(root: string): FrozenMerge[] {
65
65
  Number.isSafeInteger(item.prNumber) && item.prNumber > 0 &&
66
66
  typeof item.headCommit === 'string' && /^[a-f0-9]{40}$/i.test(item.headCommit) &&
67
67
  typeof item.repoRoot === 'string' && item.repoRoot.length > 0 &&
68
- (item.manual === undefined || item.manual === true))) throw new Error(`invalid frozen merge queue: ${path}`);
68
+ (item.manual === undefined || item.manual === true) &&
69
+ (item.goalFile === undefined || (typeof item.goalFile === 'string' && item.goalFile.length > 0)))) throw new Error(`invalid frozen merge queue: ${path}`);
69
70
  return data as FrozenMerge[];
70
71
  }
71
72
 
@@ -6,6 +6,7 @@ import { dirname, isAbsolute, join, normalize, relative, resolve, sep } from 'no
6
6
  import { groundMissionInCodebase, isRepositoryImplementationCandidate, type CodebaseGrounding } from '../autopilot/mission-codebase-gate.js';
7
7
  import { extractSlugSource, generateMissionSlug, slugify } from '../autopilot/mission-registry.js';
8
8
  import { enhancePrompt, type EnhanceOpts, type EnhanceResult } from '../prompt-enhance/enhance.js';
9
+ import type { LLMUsage } from '../prompt-cache/types.js';
9
10
  import { debug } from '../debug/log.js';
10
11
 
11
12
  function observeGoalAuthor(
@@ -324,9 +325,12 @@ function startGoalAuthorPhase(phase: GoalAuthorTimedPhase, authorRunId: string,
324
325
  return Date.now();
325
326
  }
326
327
 
327
- function endGoalAuthorPhase(phase: GoalAuthorTimedPhase, startedAt: number, authorRunId: string, onProgress?: GoalAuthorDeps['onProgress']): void {
328
+ type GoalAuthorTokens = { inputTokens: number | null; outputTokens: number | null };
329
+ const UNKNOWN_GOAL_AUTHOR_TOKENS: GoalAuthorTokens = { inputTokens: null, outputTokens: null };
330
+
331
+ function endGoalAuthorPhase(phase: GoalAuthorTimedPhase, startedAt: number, authorRunId: string, onProgress?: GoalAuthorDeps['onProgress'], tokens: GoalAuthorTokens = UNKNOWN_GOAL_AUTHOR_TOKENS): void {
328
332
  const elapsedMs = Date.now() - startedAt;
329
- observeGoalAuthor('goal-author', 'phase-end', { phase, authorRunId, elapsedMs });
333
+ observeGoalAuthor('goal-author', 'phase-end', { phase, authorRunId, elapsedMs, ...tokens });
330
334
  if (GOAL_AUTHOR_PHASES.includes(phase as GoalAuthorPhase)) {
331
335
  if (onProgress) onProgress(phase as GoalAuthorPhase, 'end');
332
336
  else process.stderr.write(`[goal-author] ${phase} ended in ${elapsedMs}ms\n`);
@@ -1156,6 +1160,21 @@ function observeAuthoredGroundingPathSections(document: string, paths: readonly
1156
1160
  }
1157
1161
  }
1158
1162
 
1163
+ function authoredGoalSectionChars(document: string): Record<string, number> {
1164
+ const headings = linesOutsideFencedCode(document)
1165
+ .filter(({ text }) => /^## (?:\S)/.test(text))
1166
+ .map(({ text, start }) => ({ heading: text.slice(3).trim(), start }));
1167
+ const sections: Record<string, number> = { preamble: [...document.slice(0, headings[0]?.start ?? document.length)].length };
1168
+ for (let index = 0; index < headings.length; index += 1) {
1169
+ const { heading, start } = headings[index];
1170
+ sections[heading] = (sections[heading] ?? 0) + [...document.slice(start, headings[index + 1]?.start ?? document.length)].length;
1171
+ }
1172
+ for (const heading of ['PROBLEM', 'WHAT TO BUILD', 'ACCEPTANCE CRITERIA', 'TRACED PATHS', 'SCOPE BOUNDARY', 'STEPS', '답하지 못하는 것']) {
1173
+ sections[heading] ??= 0;
1174
+ }
1175
+ return sections;
1176
+ }
1177
+
1159
1178
  function canonicalBlocks(document: string): string[] {
1160
1179
  return linesOutsideFencedCode(document)
1161
1180
  .filter((line) => /^##(?:\s|$)/.test(line.text))
@@ -4906,6 +4925,13 @@ async function authorGoalWithSupersededRootIntent(
4906
4925
  }
4907
4926
  // ⭐ 이 저작 «한 번»의 id. goalId 보다 «먼저» 있어야 ground·enhance 의 소요를 그 골에 붙일 수 있다.
4908
4927
  const authorRunId = generateGoalId();
4928
+ const authorStartedAt = Date.now();
4929
+ const enhanceTokens: GoalAuthorTokens = { inputTokens: null, outputTokens: null };
4930
+ const onEnhanceUsage = (usage: LLMUsage): void => {
4931
+ if (usage.inputTokens !== undefined) enhanceTokens.inputTokens = (enhanceTokens.inputTokens ?? 0) + usage.inputTokens;
4932
+ if (usage.outputTokens !== undefined) enhanceTokens.outputTokens = (enhanceTokens.outputTokens ?? 0) + usage.outputTokens;
4933
+ try { deps.enhanceOpts?.onUsage?.(usage); } catch { /* usage observation is fail-soft */ }
4934
+ };
4909
4935
  let facts: CodebaseGrounding | null = null;
4910
4936
  let groundingError = false;
4911
4937
  const persistentGrounding = resolveGoalAuthorPersistentGrounding('persistentGrounding' in deps ? deps.persistentGrounding : undefined);
@@ -4937,8 +4963,8 @@ async function authorGoalWithSupersededRootIntent(
4937
4963
  ? await deps.enhance(
4938
4964
  enhanceAsk,
4939
4965
  persistentFacts.length > 0
4940
- ? { ...deps.enhanceOpts, groundedFacts: persistentFacts }
4941
- : deps.enhanceOpts,
4966
+ ? { ...deps.enhanceOpts, groundedFacts: persistentFacts, onUsage: onEnhanceUsage }
4967
+ : { ...deps.enhanceOpts, onUsage: onEnhanceUsage },
4942
4968
  )
4943
4969
  : { original: ask, checklist: [], verbatimPreserved: true };
4944
4970
  // ⭐ goal-context 규범이 «어디에도 안 실린다»는 것을 관측으로 남긴다.
@@ -4956,7 +4982,7 @@ async function authorGoalWithSupersededRootIntent(
4956
4982
  placement: 'omitted',
4957
4983
  });
4958
4984
  } finally {
4959
- endGoalAuthorPhase('enhance', enhanceStartedAt, authorRunId, deps.onProgress);
4985
+ endGoalAuthorPhase('enhance', enhanceStartedAt, authorRunId, deps.onProgress, enhanceTokens);
4960
4986
  }
4961
4987
  if (!enhancement.verbatimPreserved || (enhanceAsk && enhancement.original !== enhanceAsk)) {
4962
4988
  throw new Error('goal author refused an enhancement that does not preserve its input verbatim');
@@ -5426,10 +5452,28 @@ async function authorGoalWithSupersededRootIntent(
5426
5452
  assembleCompleted = true;
5427
5453
  endGoalAuthorPhase('assemble', assembleStartedAt, authorRunId, deps.onProgress);
5428
5454
  const lintStartedAt = startGoalAuthorPhase('lint', authorRunId, deps.onProgress);
5455
+ let lintPassed = false;
5429
5456
  try {
5430
5457
  assertRequiredBlocks(document);
5458
+ lintPassed = true;
5431
5459
  } finally {
5432
- endGoalAuthorPhase('lint', lintStartedAt, authorRunId, deps.onProgress);
5460
+ try {
5461
+ endGoalAuthorPhase('lint', lintStartedAt, authorRunId, deps.onProgress);
5462
+ } finally {
5463
+ try {
5464
+ observeGoalAuthor('goal-author', 'summary', {
5465
+ authorRunId,
5466
+ elapsedMs: Date.now() - authorStartedAt,
5467
+ inputTokens: enhanceTokens.inputTokens,
5468
+ outputTokens: enhanceTokens.outputTokens,
5469
+ totalTokens: enhanceTokens.inputTokens === null || enhanceTokens.outputTokens === null
5470
+ ? null : enhanceTokens.inputTokens + enhanceTokens.outputTokens,
5471
+ goalChars: [...document].length,
5472
+ sectionChars: authoredGoalSectionChars(document),
5473
+ ...(!lintPassed ? { outcome: 'lint-failed' } : {}),
5474
+ });
5475
+ } catch { /* observation is fail-soft */ }
5476
+ }
5433
5477
  }
5434
5478
  return { document, facts, grounded: !groundingError && hasEvidence, authorRunId };
5435
5479
  } finally {
@@ -35,6 +35,8 @@ type GoalClarificationAnsweredBy = 'human' | 'agent' | 'none' | 'mixed';
35
35
 
36
36
  export interface GoalClarificationEscalationInput {
37
37
  goalFile: string;
38
+ /** Explicit provenance for isolated test fixtures; never inferred from goalFile. */
39
+ testOrigin?: true;
38
40
  delivery?: GoalClarificationDelivery;
39
41
  resolvedDelivery?: GoalClarificationObservedDelivery;
40
42
  dispatch?: (
@@ -577,6 +579,9 @@ export async function escalateGoalDocumentClarifications(
577
579
  }],
578
580
  },
579
581
  input.dispatchContext?.sessionId,
582
+ {},
583
+ undefined,
584
+ { testOrigin: input.testOrigin },
580
585
  );
581
586
  pendingQuestionPersistence.write(pending);
582
587
  pendingWritten += 1;