elanous 0.2.13 → 0.2.14

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 (445) 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/{7334.a6679d9168f37aa8.js → 1268.0840ff91e083fcbd.js} +1 -1
  4. package/apps/pwa/out/_next/static/chunks/1307-6e7e0377c1db0bdc.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/{9656-c1540088f5859c80.js → 1844-9ec4b1d12f6e80eb.js} +1 -1
  7. package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/{390.c373e31b3b9d0c41.js → 1948.15f356b15a625597.js} +2 -2
  9. package/apps/pwa/out/_next/static/chunks/211-9c9090c59e2c5907.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/4554-3565eea1f1495ba7.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/4562-071d8bac44a01069.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/{4685-c3c1c625b71accd1.js → 4614-21403aac730d4586.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/{8039-df92e08fede5de8f.js → 4795-f372490b178583fc.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/5135-44008fe8b1f2aa41.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/5233-bf28bd6bd7a32099.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/5977-2df489622a52a33a.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/{1452.93700483b9d4fc61.js → 6110.1297d4093fdfbb0d.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/6158.9c0449a9a8d0064b.js +2 -0
  31. package/apps/pwa/out/_next/static/chunks/6877-7c6208705c284124.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/726.83532d69452ee24b.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/{1829-b453a1b22d3999bb.js → 7327-2d69fb321ba73389.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/7395-164e238f471b5d7b.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/7787-02a1c0b04e25e811.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/8827-e2f5b09a8d37e83b.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +25 -0
  42. package/apps/pwa/out/_next/static/chunks/{8421-1c4acfb1395a0edf.js → 8949-ecb4128a0858b911.js} +1 -1
  43. package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/9103-7a3ae7f60af3b99f.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/{3869-3cd46526879f454c.js → 9247-29d889b809288e65.js} +1 -1
  46. package/apps/pwa/out/_next/static/chunks/935.4ddaa99fc1d1126c.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/{79f8be9e-957d68913b590b08.js → a9f2b54d-7865aba0f4d2ee49.js} +1 -1
  48. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/approvals/page-74383f890f075511.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-7b2b4f378354869a.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/board/page-c496bb16da85385f.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-f14987d5850a31e0.js → page-1abfe3aec3be6bdc.js} +1 -1
  53. package/apps/pwa/out/_next/static/chunks/app/bots/page-1512d91c7491ed62.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/chat/page-a627db94ec6904a0.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/consult/page-fb260b9283dbf646.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/control/page-e6b84ab07d3567d5.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/design-check/page-61ec1d2e92bdd50a.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/editor/page-fed69f9759d248d0.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/exec/page-15374909d8f1aebd.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/field/page-012a742f81218b01.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/inside/page-fd7e200d4d83fbd7.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/intake/page-99effd9d893a43dc.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/layout-938de57ae0315e46.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/app/live/page-d47266766766d227.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/app/loops/page-160089123a40dd91.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/app/market/page-629e1ede482ec6eb.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-66c27608f651b866.js → page-2ce5471926761fdd.js} +1 -1
  69. package/apps/pwa/out/_next/static/chunks/app/morning/page-27e99b4e54a849d4.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/app/not-found-e995d30f6800b6bf.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/app/observatory/page-39f89c53038a51d4.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-32cde713be47cddb.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-e7e10cd3386222e5.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-5a753bb4ec0cc511.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/app/outputs/page-04250f0e2c0d03df.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/app/page-341480ed8e3c1ea8.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/app/reflection/page-e64caff084f7ce21.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-db07eaffba0df7e3.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/app/sessions/page-8ac8d71c2adf9c8a.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-78b40a344ed22f24.js → page-e1e47d8d940aba3d.js} +1 -1
  81. package/apps/pwa/out/_next/static/chunks/app/settings/page-86f91034bd5cabe5.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-94dc383d3d54db1a.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/app/setup/page-b3759cc14da80908.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/app/share/page-ce4d8a1215ce53a3.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/app/showroom/page-58043db78ba519ae.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/app/tasks/page-357750dfb91bf960.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/app/term/{page-63d14a4d8fe1e1c1.js → page-274703bc41b71333.js} +1 -1
  89. package/apps/pwa/out/_next/static/chunks/app/today/page-194dce4a83143fce.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/app/trace/page-35bdeae2e2c17147.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/app/vault/page-3b7e821dc492bdfd.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-16bef3da05a900c3.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/app/workflows/page-f8da5dc6f3752276.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/app/workspace/page-2f810b18d89ecba6.js +1 -0
  95. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-134c1bc762aee62c.js +1 -0
  96. package/apps/pwa/out/_next/static/chunks/{5cd7e3a9-404fd6b8d0ecd44c.js → f6231284-45726550aba08fca.js} +1 -1
  97. package/apps/pwa/out/_next/static/chunks/{f48170cc-d05d28bf87a43bf4.js → f6b7c9ba.64a895b8b2fedbdc.js} +1 -1
  98. package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +1 -0
  99. package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +1 -0
  100. package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +1 -0
  101. package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +1 -0
  102. package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/webpack-539cde99554be20f.js +1 -0
  104. package/apps/pwa/out/_next/static/css/2e33713c444d5b39.css +1 -0
  105. package/apps/pwa/out/_next/static/x6mjI6vqOkVVu0jFuDkz1/_buildManifest.js +1 -0
  106. package/apps/pwa/out/approvals/index.html +1 -1
  107. package/apps/pwa/out/approvals/index.txt +23 -23
  108. package/apps/pwa/out/autopilot/index.html +1 -1
  109. package/apps/pwa/out/autopilot/index.txt +23 -23
  110. package/apps/pwa/out/board/index.html +1 -1
  111. package/apps/pwa/out/board/index.txt +23 -23
  112. package/apps/pwa/out/botlab/index.html +1 -1
  113. package/apps/pwa/out/botlab/index.txt +22 -22
  114. package/apps/pwa/out/bots/index.html +1 -1
  115. package/apps/pwa/out/bots/index.txt +22 -22
  116. package/apps/pwa/out/chat/index.html +1 -1
  117. package/apps/pwa/out/chat/index.txt +22 -22
  118. package/apps/pwa/out/consult/index.html +1 -1
  119. package/apps/pwa/out/consult/index.txt +23 -23
  120. package/apps/pwa/out/control/index.html +1 -1
  121. package/apps/pwa/out/control/index.txt +23 -23
  122. package/apps/pwa/out/design-check/index.html +1 -1
  123. package/apps/pwa/out/design-check/index.txt +23 -23
  124. package/apps/pwa/out/editor/index.html +1 -1
  125. package/apps/pwa/out/editor/index.txt +23 -23
  126. package/apps/pwa/out/exec/index.html +1 -1
  127. package/apps/pwa/out/exec/index.txt +23 -23
  128. package/apps/pwa/out/field/index.html +1 -1
  129. package/apps/pwa/out/field/index.txt +23 -23
  130. package/apps/pwa/out/healthz +1 -1
  131. package/apps/pwa/out/index.html +1 -1
  132. package/apps/pwa/out/index.txt +22 -22
  133. package/apps/pwa/out/inside/index.html +1 -1
  134. package/apps/pwa/out/inside/index.txt +22 -22
  135. package/apps/pwa/out/intake/index.html +1 -1
  136. package/apps/pwa/out/intake/index.txt +23 -23
  137. package/apps/pwa/out/live/index.html +1 -1
  138. package/apps/pwa/out/live/index.txt +23 -23
  139. package/apps/pwa/out/loops/index.html +1 -0
  140. package/apps/pwa/out/loops/index.txt +30 -0
  141. package/apps/pwa/out/market/index.html +1 -1
  142. package/apps/pwa/out/market/index.txt +22 -22
  143. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  144. package/apps/pwa/out/missions/placeholder/index.txt +22 -22
  145. package/apps/pwa/out/morning/index.html +1 -1
  146. package/apps/pwa/out/morning/index.txt +23 -23
  147. package/apps/pwa/out/observatory/index.html +1 -1
  148. package/apps/pwa/out/observatory/index.txt +23 -23
  149. package/apps/pwa/out/ops/checklist/index.html +1 -1
  150. package/apps/pwa/out/ops/checklist/index.txt +22 -22
  151. package/apps/pwa/out/ops/release/index.html +1 -1
  152. package/apps/pwa/out/ops/release/index.txt +22 -22
  153. package/apps/pwa/out/ops/seats/index.html +1 -1
  154. package/apps/pwa/out/ops/seats/index.txt +22 -22
  155. package/apps/pwa/out/outputs/index.html +1 -1
  156. package/apps/pwa/out/outputs/index.txt +22 -22
  157. package/apps/pwa/out/reflection/index.html +1 -1
  158. package/apps/pwa/out/reflection/index.txt +23 -23
  159. package/apps/pwa/out/scheduler/index.html +1 -1
  160. package/apps/pwa/out/scheduler/index.txt +23 -23
  161. package/apps/pwa/out/sessions/index.html +1 -1
  162. package/apps/pwa/out/sessions/index.txt +22 -22
  163. package/apps/pwa/out/settings/devices/index.html +1 -1
  164. package/apps/pwa/out/settings/devices/index.txt +23 -23
  165. package/apps/pwa/out/settings/index.html +1 -1
  166. package/apps/pwa/out/settings/index.txt +23 -23
  167. package/apps/pwa/out/setup/done/index.html +1 -1
  168. package/apps/pwa/out/setup/done/index.txt +23 -23
  169. package/apps/pwa/out/setup/index.html +1 -1
  170. package/apps/pwa/out/setup/index.txt +23 -23
  171. package/apps/pwa/out/share/index.html +1 -1
  172. package/apps/pwa/out/share/index.txt +23 -23
  173. package/apps/pwa/out/showroom/index.html +1 -1
  174. package/apps/pwa/out/showroom/index.txt +22 -22
  175. package/apps/pwa/out/tasks/index.html +1 -1
  176. package/apps/pwa/out/tasks/index.txt +23 -23
  177. package/apps/pwa/out/term/index.html +1 -1
  178. package/apps/pwa/out/term/index.txt +23 -23
  179. package/apps/pwa/out/today/index.html +1 -1
  180. package/apps/pwa/out/today/index.txt +23 -23
  181. package/apps/pwa/out/trace/index.html +1 -1
  182. package/apps/pwa/out/trace/index.txt +23 -23
  183. package/apps/pwa/out/vault/index.html +1 -1
  184. package/apps/pwa/out/vault/index.txt +23 -23
  185. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  186. package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
  187. package/apps/pwa/out/workflows/index.html +1 -1
  188. package/apps/pwa/out/workflows/index.txt +23 -23
  189. package/apps/pwa/out/workspace/index.html +1 -1
  190. package/apps/pwa/out/workspace/index.txt +23 -23
  191. package/apps/pwa/out/worktrees/index.html +1 -1
  192. package/apps/pwa/out/worktrees/index.txt +23 -23
  193. package/graphs/orchestrator/orchestrator.yaml +59 -0
  194. package/graphs/orchestrator/recipes.yaml +18 -0
  195. package/graphs/release/recipes.yaml +3 -0
  196. package/graphs/release/release-loop.yaml +4 -1
  197. package/graphs/rhythm/daily.yaml +38 -0
  198. package/graphs/rhythm/recipes.yaml +18 -0
  199. package/graphs/rhythm/weekly.yaml +39 -0
  200. package/graphs/video/recipes.yaml +3 -0
  201. package/graphs/video/youtube-summary.yaml +17 -0
  202. package/package.json +1 -1
  203. package/scripts/audit-test-state-writes.ts +72 -17
  204. package/scripts/brand/check.ts +186 -0
  205. package/scripts/coord-post.sh +11 -1
  206. package/scripts/docs-cli-check.ts +23 -6
  207. package/scripts/expose-rubric-check.ts +118 -0
  208. package/scripts/expose-rubric-suggest.ts +163 -0
  209. package/scripts/heal/run-role.ts +14 -0
  210. package/scripts/hq/standby-verify.ts +11 -7
  211. package/scripts/lessons/handbook-page.ts +85 -0
  212. package/scripts/release-loop/gate-node.ts +9 -0
  213. package/scripts/release-loop/publish-node.ts +14 -1
  214. package/scripts/release-loop/unattended-release.ts +23 -1
  215. package/scripts/release-loop/vault-note-node.ts +196 -0
  216. package/scripts/release-story/draft.ts +172 -0
  217. package/scripts/rhythm/daily.ts +383 -0
  218. package/scripts/rhythm/weekly.ts +294 -0
  219. package/scripts/skills/public-check.ts +169 -0
  220. package/scripts/test-deterministic.ts +4 -8
  221. package/scripts/test-diet/effectiveness.ts +195 -0
  222. package/scripts/test-diet/lib.ts +16 -8
  223. package/scripts/test-diet/node.ts +77 -9
  224. package/scripts/ux/demo-rehearsal.ts +2 -2
  225. package/scripts/ux/lib/rehearsal-verdict.ts +1 -0
  226. package/scripts/video/youtube-summary.ts +133 -0
  227. package/skills/diagram-master/SKILL.md +42 -49
  228. package/skills/diagram-master/references/pyproject.toml +4 -1
  229. package/skills/diagram-master/references/routing-guide.md +4 -2
  230. package/skills/grill-me/SKILL.md +1 -0
  231. package/skills/youtube-master/SKILL.md +13 -13
  232. package/skills/youtube-master/package-lock.json +0 -545
  233. package/skills/youtube-master/package.json +2 -5
  234. package/skills/youtube-master/scripts/main.ts +2 -2
  235. package/skills/youtube-master/scripts/transcribe-local.ts +1 -1
  236. package/skills/youtube-master/src/util.ts +30 -7
  237. package/src/agent-mission/driver.ts +3 -3
  238. package/src/autopilot/build/llm-conflict-merge.ts +45 -8
  239. package/src/autopilot/build/merge-intent.ts +53 -0
  240. package/src/chat/index.ts +2 -0
  241. package/src/claims/claims-ledger.ts +232 -0
  242. package/src/claims/claims-render.ts +157 -0
  243. package/src/cli/claims-cli.ts +94 -0
  244. package/src/cli/decisions-cli.ts +35 -3
  245. package/src/cli/docs-cli.ts +2 -0
  246. package/src/cli/flow-cli.ts +11 -0
  247. package/src/cli/freeze-cli.ts +45 -0
  248. package/src/cli/lesson-cli.ts +108 -0
  249. package/src/cli/logs-cli.ts +104 -2
  250. package/src/cli/logs-model-input.ts +131 -0
  251. package/src/cli/pr-cli.ts +29 -1
  252. package/src/cli/private-ledger-commands.ts +54 -4
  253. package/src/cli/pty-drive-verdict.ts +11 -2
  254. package/src/cli/release-cli.ts +144 -21
  255. package/src/cli/rfc-status.ts +91 -0
  256. package/src/connectors/linear.ts +5 -3
  257. package/src/context-bus/condense.ts +15 -20
  258. package/src/context-bus/context-now-public.ts +86 -0
  259. package/src/context-bus/context-now-surfaces.ts +55 -2
  260. package/src/context-bus/context-now.ts +4 -3
  261. package/src/context-bus/long-term-memory.ts +92 -11
  262. package/src/dashboard/index.ts +75 -22
  263. package/src/dashboard/slash-runtime/chat-persona.ts +66 -0
  264. package/src/dashboard/slash-runtime/dashboard-handlers.ts +99 -0
  265. package/src/dashboard/slash-runtime/loops-table.ts +77 -0
  266. package/src/decisions/decision-ledger.ts +40 -1
  267. package/src/discord/sprint21-runtime.ts +16 -0
  268. package/src/discord-self-message.ts +8 -0
  269. package/src/discord-slash-wire.ts +31 -2
  270. package/src/domains/outbound-alert.ts +1 -1
  271. package/src/elanous-config-dir.ts +29 -4
  272. package/src/execution-loop/launch-gate.ts +29 -14
  273. package/src/flow/cards.ts +239 -0
  274. package/src/graph-runner/graph-tick.ts +4 -0
  275. package/src/graph-runner/runner.ts +30 -5
  276. package/src/harness/harness-cli-command.ts +152 -27
  277. package/src/harness/harness-incidents.ts +43 -12
  278. package/src/harness/harness-pod-dispatch.ts +2 -0
  279. package/src/harness/harness-queue-child.ts +3 -3
  280. package/src/harness/harness-queue.ts +216 -28
  281. package/src/harness/helper-scan.ts +139 -0
  282. package/src/harness/model-input-observation.ts +37 -0
  283. package/src/hq/hq.ts +1 -1
  284. package/src/hq/promote.ts +301 -0
  285. package/src/index.ts +83 -21
  286. package/src/intake-plane/linear-wish.ts +39 -11
  287. package/src/intake-plane/wish-benchmark-ledger.ts +129 -0
  288. package/src/intake-plane/wish-card.ts +53 -2
  289. package/src/intake-plane/wish-folder.ts +5 -2
  290. package/src/intake-plane/wish-reply.ts +72 -0
  291. package/src/lessons/lesson-import.ts +106 -0
  292. package/src/lessons/lesson-ledger.ts +206 -0
  293. package/src/loops/activity.ts +203 -0
  294. package/src/loops/budget.ts +32 -0
  295. package/src/loops/checker.ts +5 -3
  296. package/src/loops/contract/examples/orchestrator.loop.yaml +1 -1
  297. package/src/loops/loop-cli.ts +36 -4
  298. package/src/loops/neighbors.ts +40 -0
  299. package/src/loops/observe.ts +10 -0
  300. package/src/loops/orchestrator/author-depth.ts +164 -0
  301. package/src/loops/orchestrator/author-input-check.ts +77 -0
  302. package/src/loops/orchestrator/author-ledger.ts +230 -0
  303. package/src/loops/orchestrator/finish-rate.ts +175 -0
  304. package/src/loops/orchestrator/overlap.ts +51 -12
  305. package/src/loops/orchestrator/resync-shadow.ts +227 -0
  306. package/src/loops/orchestrator/tick.ts +302 -0
  307. package/src/loops/orchestrator/traffic.ts +18 -3
  308. package/src/loops/registry.ts +454 -9
  309. package/src/loops/status-rows.ts +118 -0
  310. package/src/loops/verdict.ts +17 -5
  311. package/src/maturity/bot-slash-catalog.ts +66 -0
  312. package/src/maturity/feature-maturity.ts +7 -4
  313. package/src/nexus/api/dashboard.ts +6 -1
  314. package/src/nexus/api/http-server.ts +18 -2
  315. package/src/nexus/api/merge-approvals-gate.ts +1 -1
  316. package/src/nexus/api/schedules-read.ts +5 -1
  317. package/src/nexus/api/task-cards-api.ts +44 -5
  318. package/src/nexus/api/workflows.ts +2 -1
  319. package/src/release-loop/auto-start.ts +48 -0
  320. package/src/release-loop/checklist.ts +13 -4
  321. package/src/release-loop/feature-store.ts +28 -16
  322. package/src/release-loop/landing-freeze.ts +121 -0
  323. package/src/release-loop/placement.ts +242 -0
  324. package/src/release-loop/release-schedule.ts +3 -1
  325. package/src/seat-dispatch/ceo-commands.ts +2 -2
  326. package/src/seat-dispatch/seat-ask.ts +16 -3
  327. package/src/seat-dispatch/seat-questions.ts +58 -0
  328. package/src/seat-loop/seat-loop.ts +406 -31
  329. package/src/self-dev/dev-pipeline.ts +6 -2
  330. package/src/self-dev/draft-sweep.ts +76 -0
  331. package/src/self-dev/run-store.ts +73 -1
  332. package/src/self-dev/run-supervisor.ts +8 -0
  333. package/src/self-dev/self-orchestrate-runtime.ts +5 -1
  334. package/src/self-implement/budget-gate.ts +29 -0
  335. package/src/self-implement/expose-gate.ts +47 -0
  336. package/src/self-implement/frozen-merges.ts +294 -0
  337. package/src/self-implement/gate-cli.ts +10 -4
  338. package/src/self-implement/gate-scope.ts +46 -9
  339. package/src/self-implement/goal-archive.ts +222 -0
  340. package/src/self-implement/goal-lookup.ts +147 -0
  341. package/src/self-implement/heal-intake.ts +79 -0
  342. package/src/self-implement/host-regate.ts +25 -2
  343. package/src/self-implement/orchestrator.ts +114 -10
  344. package/src/self-implement/revert-guard.ts +62 -0
  345. package/src/self-implement/run-ledger.ts +1 -1
  346. package/src/self-implement/seams.ts +28 -8
  347. package/src/session/index.ts +2 -0
  348. package/src/task-cards/card-cli.ts +10 -2
  349. package/src/task-cards/card-store.ts +10 -6
  350. package/src/task-orchestrator/surfaces/pod-lease.ts +5 -2
  351. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +46 -6
  352. package/src/task-orchestrator/surfaces/self-implement-pod.ts +80 -16
  353. package/src/telegram-commands.ts +27 -3
  354. package/src/telegram-loops-command.ts +44 -0
  355. package/src/user-config.ts +120 -16
  356. package/src/version/packed-revision.json +1 -1
  357. package/src/workflow-runtime/storage.ts +31 -13
  358. package/apps/pwa/out/_next/static/D51l8jUBplJrIpWvENsDK/_buildManifest.js +0 -1
  359. package/apps/pwa/out/_next/static/chunks/1159-0c5ef8bc21cb6afb.js +0 -1
  360. package/apps/pwa/out/_next/static/chunks/1394-931c9827067b2276.js +0 -1
  361. package/apps/pwa/out/_next/static/chunks/2176-c379e3ebce874151.js +0 -1
  362. package/apps/pwa/out/_next/static/chunks/2426.8c6ee0c668d53def.js +0 -1
  363. package/apps/pwa/out/_next/static/chunks/3170-7ec60a56fa0e4728.js +0 -1
  364. package/apps/pwa/out/_next/static/chunks/3278-ae6ef084769f6195.js +0 -1
  365. package/apps/pwa/out/_next/static/chunks/3454-988a53dac60a1763.js +0 -1
  366. package/apps/pwa/out/_next/static/chunks/3638-aa80b4641ea439db.js +0 -1
  367. package/apps/pwa/out/_next/static/chunks/3735-fe3402553ac1c9c6.js +0 -1
  368. package/apps/pwa/out/_next/static/chunks/3992-fe5c06d6b31f8b25.js +0 -25
  369. package/apps/pwa/out/_next/static/chunks/4215-c9d91dd644ec1719.js +0 -1
  370. package/apps/pwa/out/_next/static/chunks/4508-5d0161d1e724d735.js +0 -1
  371. package/apps/pwa/out/_next/static/chunks/4733-623a79345b171e1c.js +0 -1
  372. package/apps/pwa/out/_next/static/chunks/4883.3bd969ea1d3aaaff.js +0 -2
  373. package/apps/pwa/out/_next/static/chunks/5250-c78ca295585bbf95.js +0 -1
  374. package/apps/pwa/out/_next/static/chunks/5532.6899a12958a89b72.js +0 -1
  375. package/apps/pwa/out/_next/static/chunks/5845-e6c003ce8d5fd36a.js +0 -1
  376. package/apps/pwa/out/_next/static/chunks/5978-b27004c677119115.js +0 -1
  377. package/apps/pwa/out/_next/static/chunks/6048-4c5c33579bc4ef78.js +0 -1
  378. package/apps/pwa/out/_next/static/chunks/6303-422dbf0f67c72aac.js +0 -1
  379. package/apps/pwa/out/_next/static/chunks/6511-ff8360be3d65aaa4.js +0 -1
  380. package/apps/pwa/out/_next/static/chunks/6588-ba380869b67c4fa7.js +0 -1
  381. package/apps/pwa/out/_next/static/chunks/6751-bb3595948b2e311b.js +0 -1
  382. package/apps/pwa/out/_next/static/chunks/7274-19b7279f18b47ab3.js +0 -1
  383. package/apps/pwa/out/_next/static/chunks/7551-a0e0707b79164ddd.js +0 -1
  384. package/apps/pwa/out/_next/static/chunks/7758-85c2b53f731da024.js +0 -1
  385. package/apps/pwa/out/_next/static/chunks/7777-79f845045aa1ed86.js +0 -1
  386. package/apps/pwa/out/_next/static/chunks/8025-cc24ee5d8f2fb6f8.js +0 -1
  387. package/apps/pwa/out/_next/static/chunks/8206-c6684dcde12be721.js +0 -1
  388. package/apps/pwa/out/_next/static/chunks/8605-9da4678f8a310d18.js +0 -1
  389. package/apps/pwa/out/_next/static/chunks/8725.bf46bb1395dd0fa2.js +0 -1
  390. package/apps/pwa/out/_next/static/chunks/8734-252d511cd276c0dc.js +0 -1
  391. package/apps/pwa/out/_next/static/chunks/8834-ca1cb09c1cb1a6bf.js +0 -1
  392. package/apps/pwa/out/_next/static/chunks/9269.d2a4795bc6812717.js +0 -1
  393. package/apps/pwa/out/_next/static/chunks/9538-2816b90923606459.js +0 -1
  394. package/apps/pwa/out/_next/static/chunks/9906-e7b8b5310a169dfe.js +0 -1
  395. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-d16993889b125c42.js +0 -1
  396. package/apps/pwa/out/_next/static/chunks/app/approvals/page-e5b4e1b256f41076.js +0 -1
  397. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-e49b9b76c565a0b1.js +0 -1
  398. package/apps/pwa/out/_next/static/chunks/app/board/page-b4f032c893b21cc9.js +0 -1
  399. package/apps/pwa/out/_next/static/chunks/app/bots/page-b433308aad13ceb6.js +0 -1
  400. package/apps/pwa/out/_next/static/chunks/app/chat/page-4c1dbb21861beab2.js +0 -1
  401. package/apps/pwa/out/_next/static/chunks/app/consult/page-c85b816e3b269dbb.js +0 -1
  402. package/apps/pwa/out/_next/static/chunks/app/control/page-f6bc66b919b2ea7d.js +0 -1
  403. package/apps/pwa/out/_next/static/chunks/app/design-check/page-60603c1e056c9b25.js +0 -1
  404. package/apps/pwa/out/_next/static/chunks/app/editor/page-a0ea8f5a2341ee6b.js +0 -1
  405. package/apps/pwa/out/_next/static/chunks/app/exec/page-45c5bf04b4a605e6.js +0 -1
  406. package/apps/pwa/out/_next/static/chunks/app/field/page-36c6c4183353ac9e.js +0 -1
  407. package/apps/pwa/out/_next/static/chunks/app/healthz/route-d16993889b125c42.js +0 -1
  408. package/apps/pwa/out/_next/static/chunks/app/inside/page-cb2eb0fd809980da.js +0 -1
  409. package/apps/pwa/out/_next/static/chunks/app/intake/page-31012264da8a341f.js +0 -1
  410. package/apps/pwa/out/_next/static/chunks/app/layout-50309aeb0bdd554a.js +0 -1
  411. package/apps/pwa/out/_next/static/chunks/app/live/page-bc00e964e855f4df.js +0 -1
  412. package/apps/pwa/out/_next/static/chunks/app/market/page-0696ee3fac9fe56c.js +0 -1
  413. package/apps/pwa/out/_next/static/chunks/app/morning/page-6ddd2109b7fc3a80.js +0 -1
  414. package/apps/pwa/out/_next/static/chunks/app/not-found-4e50b7298c1f69a5.js +0 -1
  415. package/apps/pwa/out/_next/static/chunks/app/observatory/page-7a2bf830038a2264.js +0 -1
  416. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1f327f18694fabca.js +0 -1
  417. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-e5cef2fe6e4028df.js +0 -1
  418. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cf596128d2b420ba.js +0 -1
  419. package/apps/pwa/out/_next/static/chunks/app/outputs/page-f672536f14f0969f.js +0 -1
  420. package/apps/pwa/out/_next/static/chunks/app/page-b52365662232db89.js +0 -1
  421. package/apps/pwa/out/_next/static/chunks/app/reflection/page-e7a6c80f288b1948.js +0 -1
  422. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-29a709616df9b050.js +0 -1
  423. package/apps/pwa/out/_next/static/chunks/app/sessions/page-fee842d7c2151129.js +0 -1
  424. package/apps/pwa/out/_next/static/chunks/app/settings/page-75d199f448171014.js +0 -1
  425. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-a011dd5b0bff29c5.js +0 -1
  426. package/apps/pwa/out/_next/static/chunks/app/setup/layout-d16993889b125c42.js +0 -1
  427. package/apps/pwa/out/_next/static/chunks/app/setup/page-d92e32d51ab5157e.js +0 -1
  428. package/apps/pwa/out/_next/static/chunks/app/share/page-5f21aed6932ef375.js +0 -1
  429. package/apps/pwa/out/_next/static/chunks/app/showroom/page-1e1a53946804debf.js +0 -1
  430. package/apps/pwa/out/_next/static/chunks/app/tasks/page-a1833cd43d832601.js +0 -1
  431. package/apps/pwa/out/_next/static/chunks/app/today/page-2a511d320f762e06.js +0 -1
  432. package/apps/pwa/out/_next/static/chunks/app/trace/page-1c3e03f88dac1e21.js +0 -1
  433. package/apps/pwa/out/_next/static/chunks/app/vault/page-c4d5289c2ea97a89.js +0 -1
  434. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b91f8952deaeaf27.js +0 -1
  435. package/apps/pwa/out/_next/static/chunks/app/workflows/page-71b43a2588489987.js +0 -1
  436. package/apps/pwa/out/_next/static/chunks/app/workspace/page-2b256feed331546f.js +0 -1
  437. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-505bb96d69a8622c.js +0 -1
  438. package/apps/pwa/out/_next/static/chunks/framework-172630e6aa5e2d7f.js +0 -1
  439. package/apps/pwa/out/_next/static/chunks/main-app-f0fe2d7477845181.js +0 -1
  440. package/apps/pwa/out/_next/static/chunks/main-fc98ac70b44eccb0.js +0 -1
  441. package/apps/pwa/out/_next/static/chunks/pages/_app-5bd7f10271f72413.js +0 -1
  442. package/apps/pwa/out/_next/static/chunks/pages/_error-97d0d85ea05fef9b.js +0 -1
  443. package/apps/pwa/out/_next/static/chunks/webpack-9e4d02d567b98240.js +0 -1
  444. package/apps/pwa/out/_next/static/css/128fb7fb670b2fbf.css +0 -1
  445. /package/apps/pwa/out/_next/static/{D51l8jUBplJrIpWvENsDK → x6mjI6vqOkVVu0jFuDkz1}/_ssgManifest.js +0 -0
@@ -0,0 +1,301 @@
1
+ // HQ promotion runbook: read-only preview by default; all remote mutations are behind --apply.
2
+ import { spawnSync } from 'node:child_process';
3
+ import { getUserConfig } from '../user-config.js';
4
+ import { parseLease, sshLeaseStore, defaultSshRunner, localShellRunner } from './lease.js';
5
+
6
+ export interface PromoteRunner {
7
+ lease(): { holder: string | null; generation: number | null; expired: boolean };
8
+ run(host: string, script: string): { status: number | null; stdout: string; stderr: string };
9
+ }
10
+
11
+ export interface PromoteLine { step: string; status: 'ready' | 'done' | 'failed'; measurement: string; command: string }
12
+ export interface PromoteResult { ok: boolean; apply: boolean; host: string; lines: PromoteLine[] }
13
+
14
+ const install = '"$HOME/.local/share/elanous/current/node_modules/elanous"';
15
+ const state = '"$HOME/.elanous-hqstate"';
16
+ const cli = 'elanous';
17
+ // A bind to the same address as nexus distinguishes an available socket from an
18
+ // occupied or uninspectable one; lsof's exit 1 alone cannot make that distinction.
19
+ const portProbe = `command -v lsof >/dev/null || { echo 'lsof unavailable' >&2; exit 1; }
20
+ python3 -c 'import os,socket,subprocess,sys; s=socket.socket(); s.bind(("127.0.0.1",0)); s.listen(); r=subprocess.run(["lsof","-nP","-t","-iTCP:"+str(s.getsockname()[1]),"-sTCP:LISTEN"],capture_output=True,text=True); s.close(); sys.exit(0 if r.returncode == 0 and r.stdout.strip() == str(os.getpid()) else "lsof control listener unreadable: "+r.stderr.strip())' || exit 1
21
+ port_detail=$(lsof -nP -t -iTCP:31416 -sTCP:LISTEN 2>&1); port_rc=$?
22
+ if [ "$port_rc" -ne 1 ] || [ -n "$port_detail" ]; then echo "port=31416 occupied or unreadable: $port_detail (lsof=$port_rc)" >&2; exit 1; fi
23
+ python3 -c 'import socket; s=socket.socket(); s.bind(("127.0.0.1", 31416)); s.close(); print("port=31416 available")'`;
24
+ // The drill uses short SSH aliases; host-local hq.hostName may use either the short or full name.
25
+ const hostNamesFor = (host: string): string[] => host === 'node-b' ? ['node-b', 'MacStudioB1']
26
+ : host === 'mbp' ? ['mbp', 'MacBookProM5'] : [host];
27
+ const checks: Array<[string, string]> = [
28
+ ['PWA build', `test -f "$HOME/.local/share/elanous/current/node_modules/elanous/apps/pwa/out/index.html" && echo 'index.html present'`],
29
+ ['MCP list', `set -o pipefail; codex mcp list --json | python3 -c 'import json,sys; data=json.load(sys.stdin); print("MCP entries="+str(len(data)))'`],
30
+ ['installed hq', `${cli} hq promote --help >/dev/null && python3 -c 'import json,pathlib; p=pathlib.Path.home()/".elanous-hqcfg/config.json"; print("host="+json.loads(p.read_text())["hq"]["hostName"])'`],
31
+ ];
32
+ const stages: Array<[string, string]> = [
33
+ ['standby checksums', `cd ${install} && bun -e 'import("node:fs/promises").then(async ({ readlink }) => { const root = process.env.HOME + "/.elanous-standby"; const generations = { core: await readlink(root + "/core/latest"), big: await readlink(root + "/big/latest") }; const { verifyStandby } = await import("./scripts/hq/standby-verify.ts"); const result = await verifyStandby({ root, tiers: ["core", "big"], generations }); console.log(JSON.stringify(result)); if (!result.ok) process.exitCode = 1 }).catch(error => { console.error(error); process.exitCode = 1 })'`],
34
+ ['copy to promotion universe', `python3 - <<'PY'
35
+ import hashlib, json, os, pathlib, shutil, tempfile
36
+ home = pathlib.Path.home()
37
+ standby = home / '.elanous-standby'
38
+ target = home / '.elanous-hqstate'
39
+ parent = target.parent
40
+ stage = pathlib.Path(tempfile.mkdtemp(prefix='.elanous-hqstate-promote-', dir=parent))
41
+ backup = None
42
+ try:
43
+ copied = 0
44
+ for tier in ('core', 'big'):
45
+ generation = os.environ['HQ_PROMOTE_' + tier.upper()]
46
+ source = standby / tier / generation
47
+ if not source.is_dir() or source.is_symlink():
48
+ raise ValueError('missing or symlinked pinned generation: ' + tier)
49
+ manifest_bytes = (source / 'MANIFEST.json').read_bytes()
50
+ expected_manifest = os.environ['HQ_PROMOTE_' + tier.upper() + '_MANIFEST_SHA256']
51
+ if hashlib.sha256(manifest_bytes).hexdigest() != expected_manifest:
52
+ raise ValueError('verified manifest changed: ' + tier)
53
+ manifest = json.loads(manifest_bytes)
54
+ if manifest['tier'] != tier or manifest['generation'] != generation:
55
+ raise ValueError('generation changed: ' + tier)
56
+ for entry in manifest['entries']:
57
+ name = entry['path']
58
+ rel = pathlib.PurePosixPath(name)
59
+ if not name or rel.is_absolute() or '..' in rel.parts or name.startswith('./') or chr(92) in name or name in ('MANIFEST.json', 'SHA256SUMS'):
60
+ raise ValueError('invalid manifest path: ' + name)
61
+ src = source
62
+ for part in rel.parts:
63
+ src = src / part
64
+ if src.is_symlink():
65
+ raise ValueError('symlinked source: ' + name)
66
+ if not src.is_file():
67
+ raise ValueError('missing source: ' + name)
68
+ dest = stage / name
69
+ if dest.parent.is_symlink():
70
+ raise ValueError('symlinked destination parent: ' + name)
71
+ dest.parent.mkdir(parents=True, exist_ok=True)
72
+ if dest.is_symlink():
73
+ raise ValueError('symlinked destination: ' + name)
74
+ shutil.copy2(src, dest)
75
+ digest = hashlib.sha256(dest.read_bytes()).hexdigest()
76
+ if digest != entry['sha256']:
77
+ raise ValueError('checksum changed during copy: ' + name)
78
+ copied += 1
79
+ if hashlib.sha256((source / 'MANIFEST.json').read_bytes()).hexdigest() != expected_manifest:
80
+ raise ValueError('verified manifest changed during copy: ' + tier)
81
+ for tier in ('core', 'big'):
82
+ source = standby / tier / os.environ['HQ_PROMOTE_' + tier.upper()]
83
+ expected_manifest = os.environ['HQ_PROMOTE_' + tier.upper() + '_MANIFEST_SHA256']
84
+ if hashlib.sha256((source / 'MANIFEST.json').read_bytes()).hexdigest() != expected_manifest:
85
+ raise ValueError('verified manifest changed before promotion: ' + tier)
86
+ if not (stage / 'release/features.sqlite').is_file() or not (stage / 'config.json').is_file():
87
+ raise ValueError('core promotion files missing')
88
+ if target.is_symlink() or (target.exists() and not target.is_dir()):
89
+ raise ValueError('promotion target is not a directory')
90
+ if target.exists():
91
+ backup = pathlib.Path(tempfile.mkdtemp(prefix='.elanous-hqstate-old-', dir=parent))
92
+ backup.rmdir()
93
+ target.rename(backup)
94
+ try:
95
+ stage.rename(target)
96
+ except BaseException:
97
+ if backup is not None:
98
+ backup.rename(target)
99
+ backup = None
100
+ raise
101
+ bytes_count = sum(p.stat().st_size for p in target.rglob('*') if p.is_file())
102
+ if backup is not None:
103
+ shutil.rmtree(backup)
104
+ backup = None
105
+ print('promotion files=' + str(copied) + ' bytes=' + str(bytes_count) + 'B')
106
+ finally:
107
+ if stage.exists():
108
+ shutil.rmtree(stage)
109
+ PY`],
110
+ ['target hq config', `python3 - <<'PY'
111
+ import json, os, pathlib, tempfile
112
+ source = pathlib.Path.home() / '.elanous-hqcfg/config.json'
113
+ target = pathlib.Path.home() / '.elanous-hqstate/config.json'
114
+ with source.open() as f: hq = json.load(f)['hq']
115
+ with target.open() as f: config = json.load(f)
116
+ assert isinstance(hq, dict) and hq.get('hostName') == os.environ['HQ_PROMOTE_HOST'], 'host-specific hq config mismatch'
117
+ config['hq'] = hq
118
+ with tempfile.NamedTemporaryFile(mode='w', dir=target.parent, prefix='config.promote-', delete=False) as f:
119
+ tmp = pathlib.Path(f.name)
120
+ json.dump(config, f)
121
+ f.write(chr(10))
122
+ try:
123
+ os.chmod(tmp, 0o600)
124
+ os.replace(tmp, target)
125
+ finally:
126
+ tmp.unlink(missing_ok=True)
127
+ print('hq.hostName=' + hq['hostName'])
128
+ PY`],
129
+ ['nexus health', `command -v lsof >/dev/null || { echo 'lsof unavailable: cannot identify nexus listener' >&2; exit 1; }
130
+ ${portProbe} || { echo 'port=31416 already occupied or unreadable' >&2; exit 1; }
131
+ ELANOUS_STATE_DIR=${state} ${cli} --config-dir ${state} nexus run --port 31416 --http-host 127.0.0.1 --no-mcp --no-auto-build --no-auto-install --no-auto-restart --no-watch > "$HOME/.elanous-hqstate/nexus-promote.log" 2>&1 & pid=$!
132
+ descendants() {
133
+ for child in $(pgrep -P "$1" 2>/dev/null); do descendants "$child"; echo "$child"; done
134
+ }
135
+ cleanup() {
136
+ tree=$(descendants "$pid")
137
+ for proc in $tree; do kill "$proc" 2>/dev/null || true; done
138
+ kill "$pid" 2>/dev/null || true
139
+ wait "$pid" 2>/dev/null || true
140
+ n=0
141
+ until ${portProbe} >/dev/null 2>&1; do
142
+ if [ "$n" -ge 10 ]; then echo 'port=31416 still occupied after cleanup' >&2; return 1; fi
143
+ n=$((n+1)); sleep 1
144
+ done
145
+ echo 'nexus stopped port=31416 released' >&2
146
+ }
147
+ trap 'cleanup' EXIT
148
+ n=0
149
+ code=000
150
+ owner=''
151
+ owned=0
152
+ while [ "$n" -lt 15 ]; do
153
+ code=$(curl -s --max-time 2 -o /dev/null -w '%{http_code}' http://127.0.0.1:31416/v1/health) || code=000
154
+ listeners=$(lsof -nP -t -iTCP:31416 -sTCP:LISTEN 2> /dev/null) || listeners=''
155
+ owner=$(printf '%s\\n' "$listeners" | head -n 1)
156
+ ancestor=$owner
157
+ owned=0
158
+ while [ -n "$ancestor" ] && [ "$ancestor" != 1 ]; do
159
+ if [ "$ancestor" = "$pid" ]; then owned=1; break; fi
160
+ ancestor=$(ps -o ppid= -p "$ancestor" | tr -d '[:space:]')
161
+ done
162
+ if [ "$code" = 200 ] && [ "$owned" = 1 ] && [ "$listeners" = "$owner" ] && kill -0 "$pid" 2>/dev/null; then
163
+ trap - EXIT
164
+ echo "health=$code port=31416 pid=$pid listener_pid=$owner"; exit 0
165
+ fi
166
+ if ! kill -0 "$pid" 2>/dev/null; then break; fi
167
+ n=$((n+1)); sleep 2
168
+ done
169
+ echo "health=$code port=31416 nexus_pid=$pid listener_pid=$owner owned=$owned" >&2; exit 1`],
170
+ ['steward rescue', `ELANOUS_STATE_DIR=${state} ${cli} --config-dir ${state} steward mode rescue && ELANOUS_STATE_DIR=${state} ${cli} --config-dir ${state} steward mode --json`],
171
+ ];
172
+
173
+ // One --apply per target host: stages run as separate ssh calls, so the lock is a directory that outlives each call.
174
+ const lockDir = '"$HOME/.elanous-hq/promote.lock"';
175
+ export const promoteLockAcquire = `mkdir -p "$HOME/.elanous-hq" && if mkdir ${lockDir} 2>/dev/null; then printf '%s %s\\n' "$PPID" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > ${lockDir}/owner; else echo "promote lock held: $(cat ${lockDir}/owner 2>/dev/null) · remove ${lockDir} only after confirming no promote is running" >&2; exit 1; fi`;
176
+ export const promoteLockRelease = `rm -rf ${lockDir}`;
177
+
178
+ export function defaultPromoteRunner(): PromoteRunner {
179
+ const config = getUserConfig().hq ?? {};
180
+ const arbiter = config.arbiter ?? 'cloud-vm';
181
+ const store = sshLeaseStore(arbiter, arbiter === 'local' ? localShellRunner : defaultSshRunner);
182
+ return {
183
+ lease() {
184
+ const { now, raw } = store.read();
185
+ const record = parseLease(raw);
186
+ return { holder: record?.holder ?? null, generation: record?.generation ?? null,
187
+ expired: !record || now - record.renewedAt > record.ttlSeconds };
188
+ },
189
+ run(host, script) {
190
+ const r = spawnSync('ssh', ['-o', 'BatchMode=yes', host, `bash -c '${script.replaceAll("'", "'\\''")}'`], { encoding: 'utf8', timeout: 120_000 });
191
+ return { status: r.status, stdout: r.stdout ?? '', stderr: r.stderr ?? String(r.error ?? '') };
192
+ },
193
+ };
194
+ }
195
+
196
+ export function promoteHq(host: string, apply = false, runner: PromoteRunner = defaultPromoteRunner()): PromoteResult {
197
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9.-]*$/.test(host) || host.includes('..')) throw new Error('hq promote: invalid host');
198
+ const lines: PromoteLine[] = [];
199
+ const acceptedHostNames = hostNamesFor(host);
200
+ const result = (step: string, command: string, status: PromoteLine['status'], measurement: string) => {
201
+ lines.push({ step, status, measurement, command });
202
+ };
203
+ let preflightOk = true;
204
+ for (const [name, command] of checks) {
205
+ let r: ReturnType<PromoteRunner['run']>;
206
+ try { r = runner.run(host, command); } catch (error) { r = { status: null, stdout: '', stderr: String(error) }; }
207
+ const ok = r.status === 0 && (name !== 'installed hq' || acceptedHostNames.some(name => r.stdout.trim() === `host=${name}`));
208
+ if (!ok) preflightOk = false;
209
+ result(`preflight ${name}`, command, ok ? 'done' : 'failed', (r.status === 0 ? r.stdout : r.stderr).trim() || `exit=${r.status}`);
210
+ }
211
+ let lease: ReturnType<PromoteRunner['lease']>;
212
+ try { lease = runner.lease(); } catch (error) {
213
+ result('① lease', 'read arbiter lease', 'failed', String(error));
214
+ return { ok: false, apply, host, lines };
215
+ }
216
+ if (!acceptedHostNames.includes(lease.holder ?? '') || lease.expired) {
217
+ result('① lease', 'read arbiter lease', 'failed', `holder=${lease.holder ?? 'none'} generation=${lease.generation ?? 'none'} expired=${lease.expired}`);
218
+ return { ok: false, apply, host, lines };
219
+ }
220
+ result('① lease', 'read arbiter lease', 'done', `holder=${lease.holder} generation=${lease.generation} expired=false`);
221
+ const targetHostName = lines.find(line => line.step === 'preflight installed hq')?.measurement.trim().replace(/^host=/, '') ?? '';
222
+ if (!acceptedHostNames.includes(targetHostName) || targetHostName !== lease.holder) {
223
+ result('① lease', 'compare arbiter holder to target hq.hostName', 'failed', `holder=${lease.holder} hq.hostName=${targetHostName}`);
224
+ return { ok: false, apply, host, lines };
225
+ }
226
+ if (apply && !preflightOk) return { ok: false, apply, host, lines };
227
+ let pinned: { core: string; big: string; coreManifestSha256: string; bigManifestSha256: string } | null = null;
228
+ let locked = false;
229
+ try {
230
+ for (let i = 0; i < stages.length; i++) {
231
+ const [name, script] = stages[i];
232
+ const command = i === 1 && pinned
233
+ ? `HQ_PROMOTE_CORE=${pinned.core} HQ_PROMOTE_BIG=${pinned.big} HQ_PROMOTE_CORE_MANIFEST_SHA256=${pinned.coreManifestSha256} HQ_PROMOTE_BIG_MANIFEST_SHA256=${pinned.bigManifestSha256} ${script}`
234
+ : i === 2 ? `HQ_PROMOTE_HOST=${targetHostName} ${script}` : script;
235
+ if (apply && i > 0) {
236
+ try {
237
+ const current = runner.lease();
238
+ if (current.holder !== targetHostName || current.generation !== lease.generation || current.expired) {
239
+ result(`${'②③④⑤⑥'[i]} ${name}`, command, 'failed', `lease changed: holder=${current.holder ?? 'none'} generation=${current.generation ?? 'none'} expired=${current.expired}`);
240
+ return { ok: false, apply, host, lines };
241
+ }
242
+ } catch (error) {
243
+ result(`${'②③④⑤⑥'[i]} ${name}`, command, 'failed', `lease read failed: ${String(error)}`);
244
+ return { ok: false, apply, host, lines };
245
+ }
246
+ }
247
+ if (!apply && i > 0) {
248
+ result(`${'②③④⑤⑥'[i]} ${name}`, command, 'ready', `dry-run: would run on ${host}; no writes`);
249
+ continue;
250
+ }
251
+ if (apply && i === 1) {
252
+ // The host lock and the port probe share one call: the lock is held from before the copy until the finally below.
253
+ const lockedProbe = `${promoteLockAcquire} && ${portProbe}`;
254
+ let probe: ReturnType<PromoteRunner['run']>;
255
+ try { probe = runner.run(host, lockedProbe); } catch (error) { probe = { status: null, stdout: '', stderr: String(error) }; }
256
+ locked = !probe.stderr.includes('promote lock held');
257
+ if (!locked) {
258
+ result('③ copy to promotion universe', lockedProbe, 'failed', probe.stderr.trim());
259
+ return { ok: false, apply, host, lines };
260
+ }
261
+ if (probe.status !== 0 || probe.stdout.trim() !== 'port=31416 available') {
262
+ result('③ copy to promotion universe', portProbe, 'failed', `port=31416 occupied or unreadable before copy: ${probe.stderr.trim() || probe.stdout.trim() || `exit=${probe.status}`}`);
263
+ return { ok: false, apply, host, lines };
264
+ }
265
+ }
266
+ let r: ReturnType<PromoteRunner['run']>;
267
+ try { r = runner.run(host, command); } catch (error) { r = { status: null, stdout: '', stderr: String(error) }; }
268
+ let measurement = (r.status === 0 ? r.stdout : r.stderr).trim() || `exit=${r.status}`;
269
+ let ok = r.status === 0;
270
+ if (i === 1 && ok) ok = /promotion files=\d+ bytes=\d+B/.test(r.stdout);
271
+ if (i === 2 && ok) ok = r.stdout.trim() === `hq.hostName=${targetHostName}`;
272
+ if (i === 3 && ok) ok = /health=200 port=31416 pid=\d+ listener_pid=\d+\b/.test(r.stdout);
273
+ if (i === 4 && ok) ok = /"mode"\s*:\s*"rescue"/.test(r.stdout);
274
+ if (i === 0 && ok) {
275
+ try {
276
+ const verification = JSON.parse(r.stdout) as { ok?: boolean; tiers?: Array<{ tier: string; generation: string | null; manifestSha256: string | null; checked: number; entries: number | null }> };
277
+ ok = verification.ok === true && verification.tiers?.length === 2
278
+ && verification.tiers.map(t => t.tier).join(',') === 'core,big'
279
+ && verification.tiers.every(t => t.generation && /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(t.generation)
280
+ && typeof t.manifestSha256 === 'string' && /^[a-f0-9]{64}$/.test(t.manifestSha256)
281
+ && t.entries !== null && t.entries > 0 && t.checked === t.entries) === true;
282
+ if (ok) pinned = { core: verification.tiers![0].generation!, big: verification.tiers![1].generation!,
283
+ coreManifestSha256: verification.tiers![0].manifestSha256!, bigManifestSha256: verification.tiers![1].manifestSha256! };
284
+ } catch { ok = false; }
285
+ if (!ok) measurement = `checksum verification failed · ${measurement}`;
286
+ }
287
+ result(`${'②③④⑤⑥'[i]} ${name}`, command, ok ? 'done' : 'failed', measurement);
288
+ if (!ok) return { ok: false, apply, host, lines };
289
+ }
290
+ return { ok: preflightOk, apply, host, lines };
291
+ } finally {
292
+ if (locked) {
293
+ try { runner.run(host, promoteLockRelease); } catch { /* the lock names its owner for manual release */ }
294
+ }
295
+ }
296
+ }
297
+
298
+ export function formatPromote(result: PromoteResult): string {
299
+ return [`hq promote --to ${result.host} ${result.apply ? '--apply' : '(dry-run)'}`,
300
+ ...result.lines.map(line => `${line.step}: ${line.status} · ${line.measurement}${line.status === 'ready' ? ` · ${line.command}` : ''}`)].join('\n');
301
+ }
package/src/index.ts CHANGED
@@ -63,6 +63,8 @@ import { registerUsageCommand } from './cli/usage-cli.js';
63
63
  import { registerLiveCommands } from './cli/live-cli.js';
64
64
  import { registerResearchCommand } from './cli/research-cli.js';
65
65
  import { registerReleaseCommands } from './cli/release-cli.js';
66
+ import { registerFreezeCommands } from './cli/freeze-cli.js';
67
+ import { registerFlowCommands } from './cli/flow-cli.js';
66
68
  import { registerModelWatchCommand } from './cli/model-watch-cli.js';
67
69
  import { registerIntakeCommands } from './cli/intake-cli.js';
68
70
  import { registerTasksCommands } from './cli/tasks-cli.js';
@@ -869,6 +871,8 @@ registerUsageCommand(program);
869
871
  registerLiveCommands(program);
870
872
  registerResearchCommand(program);
871
873
  registerReleaseCommands(program);
874
+ registerFreezeCommands(program);
875
+ registerFlowCommands(program);
872
876
  // HQ-HB · HQ-FENCE (10-04): one writer across mbp · node-b · cloud-vm — lease on the arbiter, 2-of-3 quorum, generation fencing.
873
877
  const hqCmd = program.command('hq').description('본부 임대 — 하트비트·중재·단일 작성자 래퍼');
874
878
  async function hqSink(): Promise<void> {
@@ -897,6 +901,19 @@ hqCmd.command('lease').argument('<action>', 'acquire|renew|status|release')
897
901
  if (!result.ok) process.exitCode = 2;
898
902
  } catch (error) { if (error instanceof CliUserError) throw error; console.error(`hq lease: ${error instanceof Error ? error.message : String(error)}`); process.exitCode = 1; }
899
903
  });
904
+ hqCmd.command('promote').description('HQ 승격 런북 (기본 dry-run · --apply 일 때만 변경)')
905
+ .requiredOption('--to <host>', '승격 대상 호스트')
906
+ .option('--apply', '대기 사본을 승격 우주에 반영하고 넥서스·rescue 실행')
907
+ .option('--json', 'JSON 출력')
908
+ .action(async (opts: { to: string; apply?: boolean; json?: boolean }) => {
909
+ try {
910
+ const { promoteHq, formatPromote } = await import('./hq/promote.js');
911
+ const result = promoteHq(opts.to, opts.apply === true);
912
+ if (opts.json) await writeStdoutJson(`${JSON.stringify(result)}\n`);
913
+ else console.log(formatPromote(result));
914
+ if (!result.ok) process.exitCode = 1;
915
+ } catch (error) { console.error(`hq promote: ${error instanceof Error ? error.message : String(error)}`); process.exitCode = 1; }
916
+ });
900
917
  hqCmd.command('heartbeat').description('보유자는 갱신 · 아니면 대기(보유자에 닿는지 중재자에 보고)').option('--json', 'JSON 출력')
901
918
  .action(async (opts: { json?: boolean }) => {
902
919
  try {
@@ -3034,6 +3051,8 @@ export async function runHarnessOrchestrateExecution(
3034
3051
  prior,
3035
3052
  goals,
3036
3053
  pid,
3054
+ ...(['OP', 'TC', 'MK', 'UX'].includes(process.env.ELANOUS_HARNESS_SEAT ?? '')
3055
+ ? { seat: process.env.ELANOUS_HARNESS_SEAT as SelfDevRunState['seat'] } : {}),
3037
3056
  runIdSource,
3038
3057
  now,
3039
3058
  onPersistenceFailure: reportPersistenceFailure,
@@ -3151,7 +3170,7 @@ contextCmd.command('condense')
3151
3170
  if (!/^[1-9]\d*h$/.test(opts.since)) throw new Error('--since requires positive hours, e.g. 24h');
3152
3171
  const { runContextCondense } = await import('./context-bus/condense.js');
3153
3172
  const report = await runContextCondense({ hours: Number(opts.since.slice(0, -1)), apply: opts.apply === true });
3154
- await writeStdoutJson(JSON.stringify({ since: report.since, until: report.until, cards: report.cards, conflicts: report.conflicts, skipped: report.skipped, folded: report.folded, ...(report.applied ? { applied: report.applied } : {}) }) + '\n');
3173
+ await writeStdoutJson(JSON.stringify({ since: report.since, until: report.until, cards: report.cards, conflicts: report.conflicts, skipped: report.skipped, folded: report.folded, droppedHarnessChild: report.droppedHarnessChild, funnel: report.funnel, ...(report.applied ? { applied: report.applied } : {}) }) + '\n');
3155
3174
  if (!opts.json) await writeStdoutFully(report.markdown);
3156
3175
  } catch (error) {
3157
3176
  console.error(`context condense: ${error instanceof Error ? error.message : String(error)}`);
@@ -4300,6 +4319,35 @@ selfCmd
4300
4319
  } catch (e) { console.error(`⛔ ${(e as Error).message}`); process.exitCode = 1; }
4301
4320
  });
4302
4321
 
4322
+ export async function announceLaunchQuotaPolicy(input: {
4323
+ runId: string;
4324
+ current: import('./oauth/codex-quota-policy.js').ResolvedCodexQuotaPolicy;
4325
+ universe: { kind: 'prod' | 'test'; root: string };
4326
+ production?: import('./oauth/codex-quota-policy.js').ResolvedCodexQuotaPolicy;
4327
+ }): Promise<{ line: string; warning?: string; quotaPolicy: {
4328
+ policy: import('./oauth/codex-quota-policy.js').CodexQuotaPolicy;
4329
+ source: import('./oauth/codex-quota-policy.js').ResolvedCodexQuotaPolicy['source'];
4330
+ universeKind: 'prod' | 'test';
4331
+ } }> {
4332
+ const { describeLaunchQuotaPolicy } = await import('./oauth/codex-quota-policy.js');
4333
+ const { appendRunLedgerEntry } = await import('./self-implement/run-ledger.js');
4334
+ const described = describeLaunchQuotaPolicy(input);
4335
+ const data = { policy: input.current.policy, source: input.current.source, universe: input.universe,
4336
+ ...(described.warning ? { warning: described.warning } : {}) };
4337
+ try {
4338
+ appendRunLedgerEntry({ runId: input.runId, timestamp: new Date().toISOString(), event: 'launch-quota-policy', data });
4339
+ } catch (error) {
4340
+ debug.log('pod.quota-policy', 'ledger-unavailable', { runId: input.runId, error: String(error) }, { level: 'warn' });
4341
+ throw new Error(`pod launch quota policy ledger write failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
4342
+ }
4343
+ debug.log('pod.quota-policy', 'announced', data);
4344
+ return { ...described, quotaPolicy: { policy: input.current.policy, source: input.current.source, universeKind: input.universe.kind } };
4345
+ }
4346
+
4347
+ export function withLaunchQuotaPolicy<T extends object>(results: readonly T[], quotaPolicy: Awaited<ReturnType<typeof announceLaunchQuotaPolicy>>['quotaPolicy'] | undefined): (T & { quotaPolicy?: Awaited<ReturnType<typeof announceLaunchQuotaPolicy>>['quotaPolicy'] })[] {
4348
+ return quotaPolicy ? results.map((result) => ({ ...result, quotaPolicy })) : [...results];
4349
+ }
4350
+
4303
4351
  // self orchestrate — 병렬 self-dev(S1·2026-07-21) — N개 독립 goal 을 각자 `elanous self implement` 서브프로세스로
4304
4352
  // TOX 디스패처 위에서 동시성캡 병렬 실행. 각 잡=자기 프로세스=자기 harness-space(병렬안전). 엔진(그래프/
4305
4353
  // 디스패처)은 기존 재사용·새 조각=self-implement surface 어댑터. [[PLAN-parallel-self-dev-orchestrator-2026-07-21]].
@@ -4458,6 +4506,8 @@ const selfOrchestrateCmd = selfCmd
4458
4506
  prior,
4459
4507
  goals,
4460
4508
  pid: process.pid,
4509
+ ...(['OP', 'TC', 'MK', 'UX'].includes(process.env.ELANOUS_HARNESS_SEAT ?? '')
4510
+ ? { seat: process.env.ELANOUS_HARNESS_SEAT as SelfDevRunState['seat'] } : {}),
4461
4511
  runIdSource,
4462
4512
  now: () => Date.now(),
4463
4513
  });
@@ -4492,6 +4542,7 @@ const selfOrchestrateCmd = selfCmd
4492
4542
  // coordinator 해석(decompose·run-identity·checkpoint·board)은 액션에 유지·감독 콜백은 runtime 으로 주입.
4493
4543
  // ☸️ 실행 칸 — pod 면 self-implement 자식을 k8s Job 으로(계약 동일 · 슈퍼바이저 무수정).
4494
4544
  const substrate = (opts as { substrate?: string }).substrate ?? 'local';
4545
+ let launchQuotaPolicy: Awaited<ReturnType<typeof announceLaunchQuotaPolicy>>['quotaPolicy'] | undefined;
4495
4546
  let podSpawn: import('./task-orchestrator/surfaces/self-implement.js').SelfImplementJobSpawn | undefined;
4496
4547
  let podTargets: { context: string; namespace: string }[] | undefined;
4497
4548
  if (substrate === 'pod') {
@@ -4574,30 +4625,39 @@ const selfOrchestrateCmd = selfCmd
4574
4625
  const resolvedQuota = resolveCodexQuotaPolicy(getUserConfig().llm);
4575
4626
  const quotaPolicy = resolvedQuota.policy;
4576
4627
  // POL1 — say which policy this launch uses and where it came from; warn when a test universe disagrees with production.
4577
- try {
4578
- const { effectiveInstanceRoot, prodInstanceRoot } = await import('./instance/resolve.js');
4579
- const { describeLaunchQuotaPolicy } = await import('./oauth/codex-quota-policy.js');
4580
- const root = effectiveInstanceRoot();
4581
- const kind = root === prodInstanceRoot() ? 'prod' as const : 'test' as const;
4582
- let production: ReturnType<typeof resolveCodexQuotaPolicy> | undefined;
4583
- if (kind === 'test') {
4584
- try {
4585
- const { readFileSync } = await import('node:fs');
4586
- const prodLlm = (JSON.parse(readFileSync(`${prodInstanceRoot()}/config.json`, 'utf8')) as { llm?: Record<string, unknown> }).llm;
4587
- production = resolveCodexQuotaPolicy(prodLlm);
4588
- } catch { /* no production config on this machine */ }
4628
+ const { effectiveInstanceRoot, prodInstanceRoot } = await import('./instance/resolve.js');
4629
+ const root = effectiveInstanceRoot();
4630
+ const kind = root === prodInstanceRoot() ? 'prod' as const : 'test' as const;
4631
+ let production: ReturnType<typeof resolveCodexQuotaPolicy> | undefined;
4632
+ if (kind === 'test') {
4633
+ try {
4634
+ const { readFileSync } = await import('node:fs');
4635
+ const prodLlm = (JSON.parse(readFileSync(`${prodInstanceRoot()}/config.json`, 'utf8')) as { llm?: Record<string, unknown> }).llm;
4636
+ production = resolveCodexQuotaPolicy(prodLlm);
4637
+ } catch { /* no production config on this machine */ }
4638
+ }
4639
+ const described = await announceLaunchQuotaPolicy({ runId, current: resolvedQuota, universe: { kind, root }, ...(production ? { production } : {}) });
4640
+ launchQuotaPolicy = described.quotaPolicy;
4641
+ debug.log('self-implement.pod', 'quota-policy', { policy: resolvedQuota.policy, source: resolvedQuota.source, universe: kind, productionPolicy: production?.policy ?? null });
4642
+ if (!opts.json) { ui.info(described.line); if (described.warning) ui.warn(described.warning); }
4643
+ // GROK-OPTIONAL (10-05): an expiring grok subscription the host cannot refresh is dropped from the chain here,
4644
+ // so the launch goes on with codex instead of dying later in hostGrokCredentials.
4645
+ let grokSubscriptionUsable = credential?.kind === 'subscription';
4646
+ if (grokSubscriptionUsable && codexPolicyAllowsFallback(quotaPolicy)) {
4647
+ const { podGrokSubscriptionUsable, podGrokSkippedLine } = await import('./task-orchestrator/surfaces/self-implement-pod.js');
4648
+ const grokCheck = podGrokSubscriptionUsable();
4649
+ if (!grokCheck.usable) {
4650
+ grokSubscriptionUsable = false;
4651
+ if (!opts.json) ui.warn(podGrokSkippedLine(grokCheck));
4589
4652
  }
4590
- const described = describeLaunchQuotaPolicy({ current: resolvedQuota, universe: { kind, root }, ...(production ? { production } : {}) });
4591
- debug.log('self-implement.pod', 'quota-policy', { policy: resolvedQuota.policy, source: resolvedQuota.source, universe: kind, productionPolicy: production?.policy ?? null });
4592
- if (!opts.json) { ui.info(described.line); if (described.warning) ui.warn(described.warning); }
4593
- } catch { /* the line is advice; the launch goes on */ }
4653
+ }
4594
4654
  const plan = planPodProvider({
4595
4655
  codexCandidates: inspectCodexRotation().candidates,
4596
4656
  // Per-account caps (대표 default:97) override the Pod default cap — without them default was dropped at 60%.
4597
4657
  thresholdPercentByAccount: getUserConfig().llm?.codexAccountRotationThresholdPercentByAccount,
4598
4658
  // 대표 한도 정책 한 값(`llm.codexQuotaPolicy`) — 크레딧·폴백을 같이 정한다.
4599
4659
  creditsAllowed: codexPolicyAllowsCredits(quotaPolicy),
4600
- grokSubscription: codexPolicyAllowsFallback(quotaPolicy) && credential?.kind === 'subscription',
4660
+ grokSubscription: codexPolicyAllowsFallback(quotaPolicy) && grokSubscriptionUsable,
4601
4661
  grokApiKey: codexPolicyAllowsFallback(quotaPolicy) && credential?.kind === 'api_key',
4602
4662
  grokApiKeyOptIn,
4603
4663
  });
@@ -4714,7 +4774,7 @@ const selfOrchestrateCmd = selfCmd
4714
4774
  debug.log('self-dev.reduce', 'orchestrate-reduce', { runId, shards: shards.length, kind: r.kind });
4715
4775
  }
4716
4776
  }
4717
- if (opts.json) { await writeStdoutJson(JSON.stringify(results) + '\n'); await reportRun(results); return; }
4777
+ if (opts.json) { await writeStdoutJson(JSON.stringify(withLaunchQuotaPolicy(results, launchQuotaPolicy)) + '\n'); await reportRun(results); return; }
4718
4778
  const done = results.filter((r) => r.status === 'done').length;
4719
4779
  const promoted = results.filter((r) => r.prUrl).length;
4720
4780
  const incomplete = results.length - done;
@@ -6811,7 +6871,8 @@ const selfDevCmd = program
6811
6871
  const { dispatchHarnessOnPod } = await import('./harness/harness-pod-dispatch.js');
6812
6872
  const { devAskPodDispatchInput } = await import('./harness/harness-substrate-default.js');
6813
6873
  const { classifyHarnessPodExit, harnessPodRunId, recordClassifiedHarnessPodExit, warnRecentIncidentBurst } = await import('./harness/harness-cli-command.js');
6814
- warnRecentIncidentBurst();
6874
+ warnRecentIncidentBurst(undefined, undefined, undefined,
6875
+ { entrance: 'cli-dev-ask', seat: opts.seat });
6815
6876
  let podOutput = '';
6816
6877
  let podRunId: string | undefined;
6817
6878
  const previousLeaseId = process.env.ELANOUS_POD_RESERVED_LEASE;
@@ -6831,7 +6892,8 @@ const selfDevCmd = program
6831
6892
  }
6832
6893
  if (status !== 0) {
6833
6894
  const classified = classifyHarnessPodExit({ status }, podOutput, { ...(podRunId ? { runId: podRunId } : {}) });
6834
- recordClassifiedHarnessPodExit(podRunId ?? harnessPodRunId(podOutput), classified.reason, status);
6895
+ recordClassifiedHarnessPodExit(podRunId ?? harnessPodRunId(podOutput), classified.reason, status,
6896
+ { entrance: 'cli-dev-ask', seat: opts.seat, output: podOutput });
6835
6897
  for (const line of classified.lines) console.error(line);
6836
6898
  }
6837
6899
  completionGuard.conclude();
@@ -6,24 +6,51 @@ import { createWishCard } from './wish-card.js';
6
6
 
7
7
  export const LINEAR_WISH_KEY_MISSING = 'Linear 키가 없습니다 — elanous connector linear 로 먼저 등록하세요';
8
8
 
9
- export async function scanLinearWishes({ teamKey, label = 'wish', store, deps = {} }: {
10
- teamKey: string;
11
- label?: string;
12
- store: CardStore;
13
- deps?: {
14
- getApiKey?: () => Promise<string | undefined>;
15
- fetchIssues?: typeof fetchLinearIssues;
16
- };
17
- }): Promise<{ added: number; duplicate: number; failed: number }> {
9
+ type LinearWishDeps = {
10
+ getApiKey?: () => Promise<string | undefined>;
11
+ fetchIssues?: typeof fetchLinearIssues;
12
+ };
13
+
14
+ async function readLinearWishes(teamKey: string, options: { labelOrPrefix?: string; excludeLabel?: string }, deps: LinearWishDeps) {
18
15
  const apiKey = await (deps.getApiKey ?? (() => getSecretAsync('connector.linear.apiKey')))();
19
16
  if (!apiKey?.trim()) throw new Error(LINEAR_WISH_KEY_MISSING);
20
- let issues: Awaited<ReturnType<typeof fetchLinearIssues>>;
21
17
  try {
22
- issues = await (deps.fetchIssues ?? fetchLinearIssues)({ apiKey, teamKey, labelOrPrefix: label });
18
+ return await (deps.fetchIssues ?? fetchLinearIssues)({ apiKey, teamKey, ...options });
23
19
  } catch (error) {
24
20
  const message = error instanceof Error ? error.message : String(error);
25
21
  throw new Error(message.replaceAll(apiKey, '[redacted]'));
26
22
  }
23
+ }
24
+
25
+ export interface LinearWishSuggestion { identifier: string; title: string; reason: string }
26
+
27
+ const WISH_PHRASES = [
28
+ { pattern: /해\s*줘(?:요|라)?/, reason: '요청' },
29
+ { pattern: /만들어(?:\s*줘(?:요)?)?/, reason: '제작' },
30
+ { pattern: /원해(?:요)?/, reason: '희망' },
31
+ { pattern: /있으면\s*좋겠다/, reason: '희망' },
32
+ ];
33
+
34
+ export async function suggestLinearWishes({ teamKey, deps = {} }: {
35
+ teamKey: string;
36
+ deps?: LinearWishDeps;
37
+ }): Promise<LinearWishSuggestion[]> {
38
+ const issues = await readLinearWishes(teamKey, { excludeLabel: 'wish' }, deps);
39
+ return issues.flatMap(issue => {
40
+ const match = WISH_PHRASES.find(({ pattern }) => pattern.test(`${issue.title}\n${issue.body}`));
41
+ return match && issue.identifier
42
+ ? [{ identifier: issue.identifier, title: Array.from(issue.title).slice(0, 40).join(''), reason: match.reason }]
43
+ : [];
44
+ });
45
+ }
46
+
47
+ export async function scanLinearWishes({ teamKey, label = 'wish', store, deps = {} }: {
48
+ teamKey: string;
49
+ label?: string;
50
+ store: CardStore;
51
+ deps?: LinearWishDeps;
52
+ }): Promise<{ added: number; duplicate: number; failed: number }> {
53
+ const issues = await readLinearWishes(teamKey, { labelOrPrefix: label }, deps);
27
54
  let added = 0;
28
55
  let duplicate = 0;
29
56
  let failed = 0;
@@ -32,6 +59,7 @@ export async function scanLinearWishes({ teamKey, label = 'wish', store, deps =
32
59
  const result = createWishCard({
33
60
  text: `${issue.title}\n\n${issue.body}\n\n${issue.url}`,
34
61
  source: 'linear', ref: issue.identifier ?? '',
62
+ replyTo: { surface: 'linear', issueId: issue.ref },
35
63
  }, store);
36
64
  if (result.created) added++;
37
65
  else duplicate++;