elanous 0.2.20 → 0.2.21

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 (339) 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/MObU8ujkMciAKr6ljAV05/_buildManifest.js +1 -0
  4. package/apps/pwa/out/_next/static/chunks/088d8e8a.8069543d082bec12.js +160 -0
  5. package/apps/pwa/out/_next/static/chunks/1109.78a7a3288c0259d9.js +148 -0
  6. package/apps/pwa/out/_next/static/chunks/115.1c6690b8604f81c4.js +32 -0
  7. package/apps/pwa/out/_next/static/chunks/119fc678.6f591cdb84ca5791.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/1262.76a3f08e67c5f816.js +309 -0
  9. package/apps/pwa/out/_next/static/chunks/{1307-77fad80cf54477c6.js → 1307-c97eba9fb72a9a5e.js} +1 -1
  10. package/apps/pwa/out/_next/static/chunks/1358d292.5c87f12cad6eaa91.js +122 -0
  11. package/apps/pwa/out/_next/static/chunks/1438.27a1a7b8f2308004.js +149 -0
  12. package/apps/pwa/out/_next/static/chunks/1442.02d85cd88f4c1eac.js +79 -0
  13. package/apps/pwa/out/_next/static/chunks/1501-1aad662d82ba43f7.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/1584.9e2dd53da6e6d4b1.js +24 -0
  15. package/apps/pwa/out/_next/static/chunks/1586.b989b0614afb77e6.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/1609.2ad355062870151f.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/1612-f6df0fc0eed7157b.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/1923.37f1d5fe7ebe3860.js +27 -0
  19. package/apps/pwa/out/_next/static/chunks/1948.5f35b39eef222a94.js +5 -0
  20. package/apps/pwa/out/_next/static/chunks/1993.3fd074a2a246234e.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/2140.d2daf9ee96e295d1.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/2212.0359851457473efc.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/224.583e562365a52f74.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/2328.0f37465646bafa7d.js +36 -0
  25. package/apps/pwa/out/_next/static/chunks/2372.bf71372184991937.js +286 -0
  26. package/apps/pwa/out/_next/static/chunks/2549-a8515c86f9e8cb96.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/2655.72cc6a97276c5acc.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/2743-ef6e8c3ced74cce5.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/2840-5458ad50c49cac6a.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/2856.f75d5baef7f49b74.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/3344.0de4b0ec5a454b7a.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/335.a13f339e97893437.js +94 -0
  33. package/apps/pwa/out/_next/static/chunks/3394.646348f34b2500be.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/3443.6276909b53d72d53.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/3495.abd4254d808bf2dc.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/3522-1de1319d028ac89c.js +25 -0
  37. package/apps/pwa/out/_next/static/chunks/3721.ba5b82db0d9a5572.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/3919.9ff7dc25afefee33.js +74 -0
  39. package/apps/pwa/out/_next/static/chunks/3998.bd9a69d9b50e9bd2.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/4003.ab6db1dc266576c9.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/4216.e8ac94b9fe6b96ab.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/4327.c44dc2b55cb43ee5.js +34 -0
  43. package/apps/pwa/out/_next/static/chunks/4401.94257d497b6d26bc.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/4559.17ca2cdbaa8d414f.js +115 -0
  45. package/apps/pwa/out/_next/static/chunks/4588.ad3364cb7fb97ada.js +86 -0
  46. package/apps/pwa/out/_next/static/chunks/4706.97e734bd4c9816ca.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/4982.6e44352f751b9a1b.js +77 -0
  48. package/apps/pwa/out/_next/static/chunks/5342.49c5c5a530e62b50.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/5360-08a80abdf3a4da99.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/5431.66c58ab1327ee0b7.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/5717.bcfcb96e701b82e0.js +90 -0
  52. package/apps/pwa/out/_next/static/chunks/5755-30d102586789bf7f.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/5957.3f1a4012388dd962.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/5977-59fedea50d13c762.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/602.9bf02ac8e9502598.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/6020.3a1b347e2d2dfacc.js +8 -0
  57. package/apps/pwa/out/_next/static/chunks/{6158.af2e93b7adff709b.js → 6158.566c890adc06a459.js} +1 -1
  58. package/apps/pwa/out/_next/static/chunks/6375.fce1d174ba6d0324.js +64 -0
  59. package/apps/pwa/out/_next/static/chunks/6546.92a828aef441271f.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/658.957fdb0a8c6c6c8e.js +287 -0
  61. package/apps/pwa/out/_next/static/chunks/6706.323590aabb8b1293.js +226 -0
  62. package/apps/pwa/out/_next/static/chunks/6747.b67947f293a4c02d.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/6767.8e6c5f6e744fcd9f.js +38 -0
  64. package/apps/pwa/out/_next/static/chunks/6868.000b24a3045c5574.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/6946.bba74e9c96050f30.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/7033.a558f1b2f6912a53.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/7065.46029c26c53eac50.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/7087.6fae577d09249bc1.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/7175-350f4ce2873bc038.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/722.de8d9801e0f6d414.js +41 -0
  71. package/apps/pwa/out/_next/static/chunks/7497.57981e9d7a75a52a.js +24 -0
  72. package/apps/pwa/out/_next/static/chunks/7630.2b9a68659053ff4e.js +193 -0
  73. package/apps/pwa/out/_next/static/chunks/7672.4af122ad892117c7.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/771.6ab889a233e7dae7.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/7899.597af4a9996c907f.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/79b78388.663188133449d707.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/8096.f7da0776cfde6300.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/8194.484f51e273d9abd2.js +217 -0
  79. package/apps/pwa/out/_next/static/chunks/8769.815ca69879f608b4.js +104 -0
  80. package/apps/pwa/out/_next/static/chunks/8857-f1762140ef9a96b1.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/88729267.9c6c000157c83f2f.js +62 -0
  82. package/apps/pwa/out/_next/static/chunks/8f835b4f.048eeb02299e3e12.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/9068.5f2764fccf86c1b2.js +93 -0
  84. package/apps/pwa/out/_next/static/chunks/9247-7581825d511a64c0.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/9451.ebfb0dcc9c02250f.js +23 -0
  86. package/apps/pwa/out/_next/static/chunks/9497-b0e758d1cd4ae8d7.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/9559.e648369b1d57cf9a.js +62 -0
  88. package/apps/pwa/out/_next/static/chunks/9832.2ab2e818013574c7.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/a9f2b54d-40c16bd0efdb04b8.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/app/approvals/page-937c92292a4ab4f0.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-85b316bb4235980b.js → page-d781be3e3a9cfd74.js} +1 -1
  92. package/apps/pwa/out/_next/static/chunks/app/board/{page-9b2b26a33cb2cd42.js → page-b0a80997903852d8.js} +1 -1
  93. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-c9955fde67b1bcb5.js → page-2b1263165630d9c8.js} +1 -1
  94. package/apps/pwa/out/_next/static/chunks/app/bots/{page-7a852956c74015a3.js → page-ea998f7f5d156bea.js} +1 -1
  95. package/apps/pwa/out/_next/static/chunks/app/ceo/page-33c328b64ca508f8.js +1 -0
  96. package/apps/pwa/out/_next/static/chunks/app/chat/{page-4ba4abe096b6c2a8.js → page-e2d6bed48ac20120.js} +1 -1
  97. package/apps/pwa/out/_next/static/chunks/app/consult/{page-575d4a8b4f7becb2.js → page-111cabe4d1fd69ae.js} +1 -1
  98. package/apps/pwa/out/_next/static/chunks/app/control/{page-f5d56ff5f45ad164.js → page-544dd4bd4847eeb7.js} +1 -1
  99. package/apps/pwa/out/_next/static/chunks/app/decisions/{page-2260e9eef0357421.js → page-3ee191319bf97d87.js} +1 -1
  100. package/apps/pwa/out/_next/static/chunks/app/design-check/page-e4bcb2c503dab04b.js +1 -0
  101. package/apps/pwa/out/_next/static/chunks/app/editor/page-300897dcbbd5ed76.js +1 -0
  102. package/apps/pwa/out/_next/static/chunks/app/exec/{page-466bf0ee16ad1432.js → page-a449f1ed472b81c2.js} +1 -1
  103. package/apps/pwa/out/_next/static/chunks/app/field/{page-a23bc71713bcc08f.js → page-9045094d6d3b5cd0.js} +1 -1
  104. package/apps/pwa/out/_next/static/chunks/app/inside/page-c06673596849ef45.js +1 -0
  105. package/apps/pwa/out/_next/static/chunks/app/intake/page-103fa3191c1b2e08.js +1 -0
  106. package/apps/pwa/out/_next/static/chunks/app/layout-9f81233f29dcd409.js +1 -0
  107. package/apps/pwa/out/_next/static/chunks/app/live/page-be8b4615c919e36c.js +1 -0
  108. package/apps/pwa/out/_next/static/chunks/app/live-run/page-c685b104e0dfa609.js +1 -0
  109. package/apps/pwa/out/_next/static/chunks/app/loops/page-8d693331cfbb6add.js +1 -0
  110. package/apps/pwa/out/_next/static/chunks/app/market/page-cef4f30a4c22fae1.js +1 -0
  111. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66ea1b54fc83fa15.js +1 -0
  112. package/apps/pwa/out/_next/static/chunks/app/morning/{page-ec102c9b8b691e37.js → page-7a12f61be4a579d1.js} +1 -1
  113. package/apps/pwa/out/_next/static/chunks/app/not-found-c85b0589591a99c9.js +1 -0
  114. package/apps/pwa/out/_next/static/chunks/app/observatory/page-725a4da02dd129eb.js +1 -0
  115. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-f6055495e9ed7ea8.js → page-8cc707bd09a497d9.js} +1 -1
  116. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-90b55be1f15ce0e8.js +1 -0
  117. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cb16606e08be65db.js +1 -0
  118. package/apps/pwa/out/_next/static/chunks/app/outputs/page-16dc64b74ed6a40a.js +1 -0
  119. package/apps/pwa/out/_next/static/chunks/app/page-21bec4afeb2f0917.js +1 -0
  120. package/apps/pwa/out/_next/static/chunks/app/reflection/{page-db03ae3589a81c9e.js → page-dc39ee7a36d26385.js} +1 -1
  121. package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-4a6463b97f0ad94f.js → page-84ae759f76053672.js} +1 -1
  122. package/apps/pwa/out/_next/static/chunks/app/sessions/{page-75bffecc9aa1004e.js → page-3db58ddb61fe2be9.js} +1 -1
  123. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-c5315e30f3528dc4.js +1 -0
  124. package/apps/pwa/out/_next/static/chunks/app/settings/{page-31d5b875760bb62d.js → page-2fe368efb6de3b6a.js} +1 -1
  125. package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-c26fa10aac6c21c4.js → page-8d04132dd0ac5e10.js} +1 -1
  126. package/apps/pwa/out/_next/static/chunks/app/setup/{page-68793c6e10331d66.js → page-f23484de467c9ea3.js} +1 -1
  127. package/apps/pwa/out/_next/static/chunks/app/share/{page-46d5201d2207a511.js → page-c69762987b751174.js} +1 -1
  128. package/apps/pwa/out/_next/static/chunks/app/showroom/page-5456692bda39aafb.js +1 -0
  129. package/apps/pwa/out/_next/static/chunks/app/tasks/{page-be1c4e8697f22360.js → page-d75f7cd65845ce98.js} +1 -1
  130. package/apps/pwa/out/_next/static/chunks/app/term/page-5603f5e568b25ce3.js +1 -0
  131. package/apps/pwa/out/_next/static/chunks/app/today/page-d2cc0f4e89f42028.js +1 -0
  132. package/apps/pwa/out/_next/static/chunks/app/trace/{page-0fd895fab9b62c1b.js → page-a6eb73b8a4840f1c.js} +1 -1
  133. package/apps/pwa/out/_next/static/chunks/app/vault/page-c0a3f4d10edb83fa.js +1 -0
  134. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-c6e4364d9de8f717.js → page-6c0b88068c143f15.js} +1 -1
  135. package/apps/pwa/out/_next/static/chunks/app/workflows/{page-0acc8e198f7b0dfb.js → page-7f8d7fcdf058a845.js} +1 -1
  136. package/apps/pwa/out/_next/static/chunks/app/workspace/{page-34882c84f84021c5.js → page-22163556cd38bc88.js} +1 -1
  137. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-141436fd26dd56fc.js +1 -0
  138. package/apps/pwa/out/_next/static/chunks/d70879db.c66c56a95a91efc6.js +66 -0
  139. package/apps/pwa/out/_next/static/chunks/f8626309.a4e5d362b1d55e59.js +1 -0
  140. package/apps/pwa/out/_next/static/chunks/webpack-f2b4016b9b981bc8.js +1 -0
  141. package/apps/pwa/out/_next/static/css/{d3427996da11e480.css → b9d8151e48998417.css} +1 -1
  142. package/apps/pwa/out/_next/static/css/e81df11490b4413b.css +1 -0
  143. package/apps/pwa/out/approvals/index.html +1 -1
  144. package/apps/pwa/out/approvals/index.txt +14 -14
  145. package/apps/pwa/out/autopilot/index.html +1 -1
  146. package/apps/pwa/out/autopilot/index.txt +14 -14
  147. package/apps/pwa/out/board/index.html +1 -1
  148. package/apps/pwa/out/board/index.txt +14 -14
  149. package/apps/pwa/out/botlab/index.html +1 -1
  150. package/apps/pwa/out/botlab/index.txt +14 -14
  151. package/apps/pwa/out/bots/index.html +1 -1
  152. package/apps/pwa/out/bots/index.txt +14 -14
  153. package/apps/pwa/out/ceo/index.html +1 -1
  154. package/apps/pwa/out/ceo/index.txt +14 -14
  155. package/apps/pwa/out/chat/index.html +1 -1
  156. package/apps/pwa/out/chat/index.txt +14 -14
  157. package/apps/pwa/out/consult/index.html +1 -1
  158. package/apps/pwa/out/consult/index.txt +14 -14
  159. package/apps/pwa/out/control/index.html +1 -1
  160. package/apps/pwa/out/control/index.txt +14 -14
  161. package/apps/pwa/out/decisions/index.html +1 -1
  162. package/apps/pwa/out/decisions/index.txt +14 -14
  163. package/apps/pwa/out/design-check/index.html +1 -1
  164. package/apps/pwa/out/design-check/index.txt +14 -14
  165. package/apps/pwa/out/editor/index.html +1 -1
  166. package/apps/pwa/out/editor/index.txt +15 -15
  167. package/apps/pwa/out/exec/index.html +1 -1
  168. package/apps/pwa/out/exec/index.txt +14 -14
  169. package/apps/pwa/out/field/index.html +1 -1
  170. package/apps/pwa/out/field/index.txt +14 -14
  171. package/apps/pwa/out/healthz +1 -1
  172. package/apps/pwa/out/index.html +1 -1
  173. package/apps/pwa/out/index.txt +14 -14
  174. package/apps/pwa/out/inside/index.html +1 -1
  175. package/apps/pwa/out/inside/index.txt +15 -15
  176. package/apps/pwa/out/intake/index.html +1 -1
  177. package/apps/pwa/out/intake/index.txt +14 -14
  178. package/apps/pwa/out/live/index.html +1 -1
  179. package/apps/pwa/out/live/index.txt +14 -14
  180. package/apps/pwa/out/live-run/index.html +1 -0
  181. package/apps/pwa/out/live-run/index.txt +35 -0
  182. package/apps/pwa/out/loops/index.html +1 -1
  183. package/apps/pwa/out/loops/index.txt +14 -14
  184. package/apps/pwa/out/market/index.html +1 -1
  185. package/apps/pwa/out/market/index.txt +14 -14
  186. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  187. package/apps/pwa/out/missions/placeholder/index.txt +14 -14
  188. package/apps/pwa/out/morning/index.html +1 -1
  189. package/apps/pwa/out/morning/index.txt +14 -14
  190. package/apps/pwa/out/observatory/index.html +1 -1
  191. package/apps/pwa/out/observatory/index.txt +14 -14
  192. package/apps/pwa/out/ops/checklist/index.html +1 -1
  193. package/apps/pwa/out/ops/checklist/index.txt +14 -14
  194. package/apps/pwa/out/ops/release/index.html +1 -1
  195. package/apps/pwa/out/ops/release/index.txt +14 -14
  196. package/apps/pwa/out/ops/seats/index.html +1 -1
  197. package/apps/pwa/out/ops/seats/index.txt +14 -14
  198. package/apps/pwa/out/outputs/index.html +1 -1
  199. package/apps/pwa/out/outputs/index.txt +14 -14
  200. package/apps/pwa/out/reflection/index.html +1 -1
  201. package/apps/pwa/out/reflection/index.txt +15 -15
  202. package/apps/pwa/out/scheduler/index.html +1 -1
  203. package/apps/pwa/out/scheduler/index.txt +14 -14
  204. package/apps/pwa/out/sessions/index.html +1 -1
  205. package/apps/pwa/out/sessions/index.txt +14 -14
  206. package/apps/pwa/out/settings/devices/index.html +1 -1
  207. package/apps/pwa/out/settings/devices/index.txt +14 -14
  208. package/apps/pwa/out/settings/index.html +1 -1
  209. package/apps/pwa/out/settings/index.txt +14 -14
  210. package/apps/pwa/out/setup/done/index.html +1 -1
  211. package/apps/pwa/out/setup/done/index.txt +14 -14
  212. package/apps/pwa/out/setup/index.html +1 -1
  213. package/apps/pwa/out/setup/index.txt +14 -14
  214. package/apps/pwa/out/share/index.html +1 -1
  215. package/apps/pwa/out/share/index.txt +14 -14
  216. package/apps/pwa/out/showroom/index.html +1 -1
  217. package/apps/pwa/out/showroom/index.txt +14 -14
  218. package/apps/pwa/out/tasks/index.html +1 -1
  219. package/apps/pwa/out/tasks/index.txt +14 -14
  220. package/apps/pwa/out/term/index.html +1 -1
  221. package/apps/pwa/out/term/index.txt +14 -14
  222. package/apps/pwa/out/today/index.html +1 -1
  223. package/apps/pwa/out/today/index.txt +14 -14
  224. package/apps/pwa/out/trace/index.html +1 -1
  225. package/apps/pwa/out/trace/index.txt +14 -14
  226. package/apps/pwa/out/vault/index.html +1 -1
  227. package/apps/pwa/out/vault/index.txt +14 -14
  228. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  229. package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
  230. package/apps/pwa/out/workflows/index.html +1 -1
  231. package/apps/pwa/out/workflows/index.txt +14 -14
  232. package/apps/pwa/out/workspace/index.html +1 -1
  233. package/apps/pwa/out/workspace/index.txt +14 -14
  234. package/apps/pwa/out/worktrees/index.html +1 -1
  235. package/apps/pwa/out/worktrees/index.txt +14 -14
  236. package/graphs/drafts/recipes.yaml +4 -4
  237. package/graphs/implement-loop.yaml +277 -5
  238. package/package.json +1 -1
  239. package/scripts/coord-channel-watch.sh +31 -4
  240. package/scripts/docs-lint.ts +4 -1
  241. package/scripts/marketing/generate-ax-brief.ts +18 -1
  242. package/scripts/measure-nl-routing-corpus.ts +11 -3
  243. package/scripts/release-loop/gate-node.ts +5 -5
  244. package/scripts/release-loop/shard-plan.ts +50 -5
  245. package/scripts/rhythm/daily.ts +2 -1
  246. package/src/browser-cdp/client.ts +10 -5
  247. package/src/cli/ops-cli.ts +11 -0
  248. package/src/cli/ops-overview-cli.ts +50 -0
  249. package/src/cli/pod-cli.ts +1 -0
  250. package/src/cli/tasks-cli.ts +106 -0
  251. package/src/dashboard/index.ts +40 -4
  252. package/src/esc-abort-gate.ts +3 -1
  253. package/src/graph-runner/graph-cli.ts +57 -0
  254. package/src/graph-runner/graph-run-api.ts +13 -0
  255. package/src/graph-wizard/archetypes.ts +88 -0
  256. package/src/graph-wizard/generate.ts +545 -0
  257. package/src/graph-wizard/saved-steps.ts +120 -0
  258. package/src/graph-wizard/steps.ts +193 -0
  259. package/src/harness/harness-cli-command.ts +68 -12
  260. package/src/index.ts +7 -4
  261. package/src/intake-plane/digest.ts +18 -11
  262. package/src/maturity/feature-maturity.ts +2 -1
  263. package/src/nexus/api/draft-metrics.ts +114 -0
  264. package/src/nexus/api/graph-wizard-api.ts +53 -0
  265. package/src/nexus/api/graphs-api.ts +50 -12
  266. package/src/nexus/api/harness-traversal.ts +205 -0
  267. package/src/nexus/api/http-server.ts +31 -0
  268. package/src/nexus/outbound/router.ts +28 -8
  269. package/src/onboarding/first-run.ts +3 -0
  270. package/src/ops/overview-collect.ts +345 -0
  271. package/src/ops/overview-seat-era.ts +90 -0
  272. package/src/ops/overview.ts +223 -0
  273. package/src/release-loop/gate-shards.ts +7 -2
  274. package/src/self-dev/ask-launch-flow.ts +4 -2
  275. package/src/self-dev/ask-launch-io.ts +3 -1
  276. package/src/self-dev/graph-journey-nodes.ts +157 -0
  277. package/src/self-dev/graph-journey-route.ts +105 -0
  278. package/src/self-dev/launch-preflight.ts +43 -3
  279. package/src/self-dev/release-path-guard.ts +7 -4
  280. package/src/self-implement/frozen-merges.ts +23 -0
  281. package/src/self-implement/goal-author.ts +14 -0
  282. package/src/self-implement/heal-intake.ts +3 -0
  283. package/src/self-implement/orchestrator.ts +16 -1
  284. package/src/self-implement/seams.ts +3 -0
  285. package/src/self-implement/self-implement-runtime.ts +12 -0
  286. package/src/session-runtime/index.ts +5 -3
  287. package/src/shell-env-bootstrap.ts +17 -3
  288. package/src/task-agent/card-evidence.ts +6 -0
  289. package/src/task-agent/parent-host.ts +315 -0
  290. package/src/task-agent/task-hand.ts +9 -2
  291. package/src/task-orchestrator/surfaces/pod-command-job.ts +41 -6
  292. package/src/task-orchestrator/surfaces/pod-gate-deps.ts +123 -0
  293. package/src/task-orchestrator/surfaces/self-implement-pod.ts +39 -6
  294. package/src/tool-runtime/browser-runtime.ts +87 -3
  295. package/src/tool-runtime/index.ts +1 -0
  296. package/src/user-config.ts +20 -4
  297. package/src/version/packed-revision.json +1 -1
  298. package/apps/pwa/out/_next/static/Bam1YziK2iH9bN4K0utGk/_buildManifest.js +0 -1
  299. package/apps/pwa/out/_next/static/chunks/1612-f60c0c93ec773f81.js +0 -1
  300. package/apps/pwa/out/_next/static/chunks/1844-9ec4b1d12f6e80eb.js +0 -1
  301. package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +0 -1
  302. package/apps/pwa/out/_next/static/chunks/1948.15f356b15a625597.js +0 -5
  303. package/apps/pwa/out/_next/static/chunks/2116-6429c188eb846ab8.js +0 -1
  304. package/apps/pwa/out/_next/static/chunks/224-e7be8f762fe74986.js +0 -1
  305. package/apps/pwa/out/_next/static/chunks/2549-89c518a4c8ed4d00.js +0 -1
  306. package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +0 -1
  307. package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +0 -1
  308. package/apps/pwa/out/_next/static/chunks/5755-ea7ed88eb956f47b.js +0 -1
  309. package/apps/pwa/out/_next/static/chunks/5977-94ed6f78e9814b2b.js +0 -1
  310. package/apps/pwa/out/_next/static/chunks/7748-6e51bfe927019f5c.js +0 -25
  311. package/apps/pwa/out/_next/static/chunks/9247-c1753a059e7bac08.js +0 -1
  312. package/apps/pwa/out/_next/static/chunks/a9f2b54d-33c0689b70ed8cf3.js +0 -1
  313. package/apps/pwa/out/_next/static/chunks/app/approvals/page-c855c24de7db74fe.js +0 -1
  314. package/apps/pwa/out/_next/static/chunks/app/ceo/page-c5a5c621be0e04ae.js +0 -1
  315. package/apps/pwa/out/_next/static/chunks/app/design-check/page-7c058069c5e7fa63.js +0 -1
  316. package/apps/pwa/out/_next/static/chunks/app/editor/page-dc5da0b8a2edb8a4.js +0 -1
  317. package/apps/pwa/out/_next/static/chunks/app/inside/page-ed4a6f799f005d09.js +0 -1
  318. package/apps/pwa/out/_next/static/chunks/app/intake/page-9cd565005b93aca6.js +0 -1
  319. package/apps/pwa/out/_next/static/chunks/app/layout-55f0f40c6db95f15.js +0 -1
  320. package/apps/pwa/out/_next/static/chunks/app/live/page-474cb5888ea0e8f3.js +0 -1
  321. package/apps/pwa/out/_next/static/chunks/app/loops/page-35bfec2e631d3eae.js +0 -1
  322. package/apps/pwa/out/_next/static/chunks/app/market/page-9ca21f9ad3208a61.js +0 -1
  323. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-f9ccb1b1adbea14d.js +0 -1
  324. package/apps/pwa/out/_next/static/chunks/app/not-found-a9c2cfa8dda922a5.js +0 -1
  325. package/apps/pwa/out/_next/static/chunks/app/observatory/page-6da35144878feb6d.js +0 -1
  326. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-25204694fa9984a5.js +0 -1
  327. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-a60e035f151d84d3.js +0 -1
  328. package/apps/pwa/out/_next/static/chunks/app/outputs/page-189c587f36874538.js +0 -1
  329. package/apps/pwa/out/_next/static/chunks/app/page-c56634bde705a65d.js +0 -1
  330. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-a8d2fe0c669e0de4.js +0 -1
  331. package/apps/pwa/out/_next/static/chunks/app/showroom/page-d81c07a7b4f618ae.js +0 -1
  332. package/apps/pwa/out/_next/static/chunks/app/term/page-cf0a4b4abead71ea.js +0 -1
  333. package/apps/pwa/out/_next/static/chunks/app/today/page-a4717e2c4bf7a28f.js +0 -1
  334. package/apps/pwa/out/_next/static/chunks/app/vault/page-8b6c1454a8e8f306.js +0 -1
  335. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-0506a1e0e4bdb2f5.js +0 -1
  336. package/apps/pwa/out/_next/static/chunks/webpack-c260571513bf43c2.js +0 -1
  337. package/apps/pwa/out/_next/static/css/c23d959d3ee15e57.css +0 -1
  338. /package/apps/pwa/out/_next/static/{Bam1YziK2iH9bN4K0utGk → MObU8ujkMciAKr6ljAV05}/_ssgManifest.js +0 -0
  339. /package/skills/project-onboarding/{skill.md → SKILL.md} +0 -0
@@ -0,0 +1,114 @@
1
+ import { join } from 'node:path';
2
+ import { debug } from '../../debug/log.js';
3
+ import type { DraftMetrics } from '../../self-dev/draft-sweep.js';
4
+
5
+ /**
6
+ * DRAFT-METRIC — CEO view source. Opening the page must never trigger a GitHub read:
7
+ * `read()` answers from memory and at most one background collection runs at a time,
8
+ * no more often than the TTL (or the failure back-off). The collection itself is
9
+ * `elanous harness drafts metrics --json` in a child process, which reads PR evidence
10
+ * through the batched GraphQL path (#24918) — one call per 40 drafts, no per-draft comments call.
11
+ */
12
+ export type DraftMetricsState = 'measuring' | 'ready' | 'unavailable';
13
+
14
+ export interface DraftMetricsSnapshot {
15
+ /** measuring = no value yet and a collection is running · unavailable = no value and the last collection failed. */
16
+ readonly state: DraftMetricsState;
17
+ readonly metrics: DraftMetrics | null;
18
+ /** When the served value was collected (ISO). null without a value. */
19
+ readonly measuredAt: string | null;
20
+ /** A background collection is in flight. */
21
+ readonly refreshing: boolean;
22
+ /** Last collection failure; with a value it means the value is older than intended. */
23
+ readonly reason: string | null;
24
+ }
25
+
26
+ export interface DraftMetricsSourceOptions {
27
+ readonly collect?: () => Promise<DraftMetrics>;
28
+ readonly ttlMs?: number;
29
+ readonly failureRetryMs?: number;
30
+ readonly now?: () => number;
31
+ }
32
+
33
+ export const DRAFT_METRICS_TTL_MS = 10 * 60_000;
34
+ export const DRAFT_METRICS_FAILURE_RETRY_MS = 2 * 60_000;
35
+ const COLLECT_TIMEOUT_MS = 5 * 60_000;
36
+
37
+ function isMetrics(value: unknown): value is DraftMetrics {
38
+ if (!value || typeof value !== 'object') return false;
39
+ const row = value as Record<string, unknown>;
40
+ const count = (key: string) => Number.isInteger(row[key]) && (row[key] as number) >= 0;
41
+ const nullableNumber = (key: string) => row[key] === null || (typeof row[key] === 'number' && Number.isFinite(row[key]));
42
+ return count('inventory') && count('needsOwner') && count('converted48h') && count('cohort48h')
43
+ && nullableNumber('oldestAgeHours') && nullableNumber('conversion48h');
44
+ }
45
+
46
+ /** Parses the CLI's single JSON line; anything else is a failed collection, never a zero. */
47
+ export function parseDraftMetricsOutput(stdout: string): DraftMetrics {
48
+ const line = stdout.trim().split('\n').reverse().find((text) => text.trim().startsWith('{'));
49
+ if (!line) throw new Error('draft metrics: no JSON output');
50
+ const parsed: unknown = JSON.parse(line);
51
+ if (!isMetrics(parsed)) throw new Error('draft metrics: malformed output');
52
+ const { inventory, oldestAgeHours, needsOwner, converted48h, cohort48h, conversion48h } = parsed;
53
+ return { inventory, oldestAgeHours, needsOwner, converted48h, cohort48h, conversion48h };
54
+ }
55
+
56
+ async function collectViaCli(): Promise<DraftMetrics> {
57
+ const root = join(import.meta.dir, '../../..');
58
+ const proc = Bun.spawn(['bun', join(root, 'bin/elanous.mjs'), 'harness', 'drafts', 'metrics', '--json'], {
59
+ cwd: root, stdout: 'pipe', stderr: 'pipe',
60
+ });
61
+ const timer = setTimeout(() => proc.kill(), COLLECT_TIMEOUT_MS);
62
+ try {
63
+ const [stdout, stderr, exitCode] = await Promise.all([new Response(proc.stdout).text(), new Response(proc.stderr).text(), proc.exited]);
64
+ if (exitCode !== 0) throw new Error(stderr.trim().split('\n').at(-1) || `draft metrics exited ${exitCode}`);
65
+ return parseDraftMetricsOutput(stdout);
66
+ } finally { clearTimeout(timer); }
67
+ }
68
+
69
+ export interface DraftMetricsSource {
70
+ read(): DraftMetricsSnapshot;
71
+ }
72
+
73
+ export function createDraftMetricsSource(options: DraftMetricsSourceOptions = {}): DraftMetricsSource {
74
+ const collect = options.collect ?? collectViaCli;
75
+ const ttl = options.ttlMs ?? DRAFT_METRICS_TTL_MS;
76
+ const retry = options.failureRetryMs ?? DRAFT_METRICS_FAILURE_RETRY_MS;
77
+ const now = options.now ?? Date.now;
78
+ let value: { metrics: DraftMetrics; at: number } | null = null;
79
+ let failure: { reason: string; at: number } | null = null;
80
+ let inFlight: Promise<void> | null = null;
81
+
82
+ const refresh = (): void => {
83
+ if (inFlight) return;
84
+ const started = now();
85
+ inFlight = (async () => {
86
+ try {
87
+ const metrics = await collect();
88
+ value = { metrics, at: now() };
89
+ failure = null;
90
+ try { debug.log('nexus.draft-metrics', 'collected', { ms: now() - started, inventory: metrics.inventory }); } catch { /* fail-soft */ }
91
+ } catch (error) {
92
+ failure = { reason: error instanceof Error ? error.message : String(error), at: now() };
93
+ try { debug.log('nexus.draft-metrics', 'failed', { ms: now() - started, reason: failure.reason }); } catch { /* fail-soft */ }
94
+ } finally { inFlight = null; }
95
+ })();
96
+ };
97
+
98
+ return {
99
+ read() {
100
+ const clock = now();
101
+ const due = failure && (!value || failure.at >= value.at)
102
+ ? clock - failure.at >= retry
103
+ : !value || clock - value.at >= ttl;
104
+ if (due) refresh();
105
+ return {
106
+ state: value ? 'ready' : failure && !inFlight ? 'unavailable' : 'measuring',
107
+ metrics: value?.metrics ?? null,
108
+ measuredAt: value ? new Date(value.at).toISOString() : null,
109
+ refreshing: inFlight !== null,
110
+ reason: failure?.reason ?? null,
111
+ };
112
+ },
113
+ };
114
+ }
@@ -0,0 +1,53 @@
1
+ import { debug } from '../../debug/log.js';
2
+ import { GraphWizardInputError, generateGraphFromPrompt, type GraphWizardDeps, type GraphWizardTurn } from '../../graph-wizard/generate.js';
3
+ import { jsonResponse } from './json-response.js';
4
+
5
+ const MAX_PROMPT = 4000;
6
+ const MAX_YAML = 200_000;
7
+
8
+ /**
9
+ * POST /v1/graphs/wizard {prompt, kind?, currentYaml?, history?}
10
+ * → 200 {ok:true, id, yaml, base, baseReason, issues:[], attempts, summary, labels, steps, recipes, dryRun} · 422 {ok:false, …} · 400 빈 prompt.
11
+ * (labels·steps·recipes·dryRun·baseReason 는 v2 추가 필드 — 기존 필드는 그대로.)
12
+ * currentYaml 이 빈 문자열이면 «빈 캔버스»로 보고 새 그래프를 짓는다(의도) · 내용이 있는데 id 가 없거나 형식 밖이면 400.
13
+ * 저장하지 않는다 — 저장은 POST /v1/graphs(새 그래프) 또는 PUT /v1/graphs/<id>/yaml(내 그래프).
14
+ */
15
+ export async function handleGraphWizardPost(req: Request, deps: GraphWizardDeps = {}): Promise<Response> {
16
+ let body: Record<string, unknown>;
17
+ try {
18
+ const parsed: unknown = await req.json();
19
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error('object');
20
+ body = parsed as Record<string, unknown>;
21
+ } catch { return jsonResponse({ error: 'bad_request', reason: 'json body required' }, 400); }
22
+ const prompt = typeof body.prompt === 'string' ? body.prompt.trim() : '';
23
+ if (!prompt) return jsonResponse({ error: 'bad_request', reason: 'prompt required' }, 400);
24
+ if (prompt.length > MAX_PROMPT) return jsonResponse({ error: 'bad_request', reason: `prompt longer than ${MAX_PROMPT}` }, 400);
25
+ if (body.kind !== undefined && body.kind !== 'harness' && body.kind !== 'workflow') {
26
+ return jsonResponse({ error: 'bad_request', reason: 'kind must be harness or workflow' }, 400);
27
+ }
28
+ if (body.currentYaml !== undefined && (typeof body.currentYaml !== 'string' || body.currentYaml.length > MAX_YAML)) {
29
+ return jsonResponse({ error: 'bad_request', reason: 'currentYaml must be a string' }, 400);
30
+ }
31
+ let history: GraphWizardTurn[] | undefined;
32
+ if (body.history !== undefined) {
33
+ if (!Array.isArray(body.history) || !body.history.every((t) => t && typeof t === 'object' &&
34
+ ((t as GraphWizardTurn).role === 'user' || (t as GraphWizardTurn).role === 'assistant') && typeof (t as GraphWizardTurn).text === 'string')) {
35
+ return jsonResponse({ error: 'bad_request', reason: 'history must be [{role, text}]' }, 400);
36
+ }
37
+ history = body.history as GraphWizardTurn[];
38
+ }
39
+ try {
40
+ const result = await generateGraphFromPrompt({
41
+ prompt,
42
+ ...(body.kind ? { kind: body.kind as 'harness' | 'workflow' } : {}),
43
+ ...(typeof body.currentYaml === 'string' && body.currentYaml.trim() ? { currentYaml: body.currentYaml } : {}),
44
+ ...(history ? { history } : {}),
45
+ }, deps);
46
+ return jsonResponse({ ...result, base: result.base ?? null }, result.ok ? 200 : 422);
47
+ } catch (error) {
48
+ const message = error instanceof Error ? error.message : String(error);
49
+ if (error instanceof GraphWizardInputError) return jsonResponse({ error: 'bad_request', reason: message }, 400);
50
+ debug.log('graph.wizard', 'error', { message });
51
+ return jsonResponse({ error: 'wizard-failed', reason: message }, 502);
52
+ }
53
+ }
@@ -7,6 +7,8 @@ import { debug } from '../../debug/log.js';
7
7
  import { defaultGraphsDir, loadGraphTemplatesFrom } from '../../self-implement/graph-templates.js';
8
8
  import { loadGraphTemplates } from '../../self-implement/graph-yaml.js';
9
9
  import { validateGraphYaml } from './graph-kinds.js';
10
+ import { parseWizardSteps, readWizardSteps, withWizardSteps } from '../../graph-wizard/saved-steps.js';
11
+ import type { WizardNodeStep } from '../../graph-wizard/steps.js';
10
12
  import { jsonResponse } from './json-response.js';
11
13
 
12
14
  /** Declared graph ids only. A user string is never joined onto a filesystem path. */
@@ -109,7 +111,12 @@ function detailOf(id: string, source: 'core' | 'mine', editable: boolean, dir: s
109
111
  editable,
110
112
  entry_node: template.entryNode,
111
113
  terminal_nodes: template.terminalNodes,
112
- nodes: template.nodes.map(({ nodeId, kind, recipe, maxVisits }) => ({ node_id: nodeId, kind, recipe, max_visits: maxVisits })),
114
+ // GRAPH-GROUPS — a node's `group:` tag (when the loader carries it) rides along so the PWA can box/fold units.
115
+ nodes: template.nodes.map((node) => {
116
+ const { nodeId, kind, recipe, maxVisits } = node;
117
+ const group = (node as { group?: unknown }).group;
118
+ return { node_id: nodeId, kind, recipe, max_visits: maxVisits, ...(typeof group === 'string' && group.trim() ? { group: group.trim() } : {}) };
119
+ }),
113
120
  edges: spec.edges.map(({ from, to, on, map }) => ({
114
121
  from,
115
122
  ...(to === undefined ? {} : { to }),
@@ -178,7 +185,8 @@ export function handleGraphsGet(pathname: string, dir = defaultGraphsDir(), deps
178
185
  const coreHit = core.ok ? indexByDeclaredId(coreDir).get(id) : undefined;
179
186
  const hit = mineHit ?? coreHit;
180
187
  if (!hit) return jsonResponse({ error: 'not-found', id }, 404);
181
- return jsonResponse({ id, source: mineHit ? 'mine' : 'core', editable: Boolean(mineHit), yaml: hit.text });
188
+ const steps = mineHit ? readWizardSteps(mineDir, id, hit.text) : null;
189
+ return jsonResponse({ id, source: mineHit ? 'mine' : 'core', editable: Boolean(mineHit), yaml: hit.text, ...(steps ? { steps } : {}) });
182
190
  }
183
191
 
184
192
  const match = /^\/v1\/graphs\/([^/]+)$/.exec(pathname);
@@ -209,17 +217,22 @@ export async function handleGraphsPut(pathname: string, req: Request, deps: Grap
209
217
  const mineOwns = indexByDeclaredId(mineDir).has(id);
210
218
  if (coreOwns && !mineOwns) return jsonResponse({ error: 'core-read-only', id }, 403);
211
219
  if (!mineOwns) return jsonResponse({ error: 'not-found', id }, 404);
212
- let body: { yaml?: unknown };
213
- try { body = await req.json() as { yaml?: unknown }; }
220
+ let body: { yaml?: unknown; steps?: unknown };
221
+ try { body = await req.json() as { yaml?: unknown; steps?: unknown }; }
214
222
  catch { return jsonResponse({ error: 'bad_request', reason: 'json body required' }, 400); }
215
223
  if (typeof body?.yaml !== 'string') return jsonResponse({ error: 'bad_request', reason: 'yaml string required' }, 400);
216
224
  const rejected = rejectGraphYaml(body.yaml, id);
217
225
  if (rejected) return rejected;
226
+ const steps = stepsOf(body.steps, body.yaml);
227
+ if (steps instanceof Response) return steps;
218
228
  const existing = indexByDeclaredId(mineDir).get(id)!;
219
229
  // Writes go only to <mine>/<id>.yaml; a hand-placed file under another name is not edited over HTTP.
220
230
  if (existing.file !== `${id}.yaml`) return jsonResponse({ error: 'file-name-mismatch', id, file: existing.file }, 409);
221
- const { version, previous } = writeWithVersion(mineDir, id, existing, body.yaml);
222
- debug.log('nexus.graphs', 'saved', { id, version, previous });
231
+ let written: { version: string; previous: string };
232
+ try { written = withWizardSteps(mineDir, id, steps, () => writeWithVersion(mineDir, id, existing, body.yaml as string)); }
233
+ catch (error) { return saveFailed(id, error); }
234
+ const { version, previous } = written;
235
+ debug.log('nexus.graphs', 'saved', { id, version, previous, steps: steps ? Object.keys(steps).length : 0 });
223
236
  return jsonResponse({ id, source: 'mine', editable: true, saved: true, version, previous });
224
237
  }
225
238
 
@@ -331,10 +344,27 @@ export async function handleGraphsRevert(pathname: string, req: Request, deps: G
331
344
  return jsonResponse({ id, source: 'mine', editable: true, reverted: true, version, previous, restoredFrom: target });
332
345
  }
333
346
 
347
+ /** GRAPH-WIZARD-SAVE-RECIPES — optional `steps` on save (the wizard's node → library step map). Absent = keep
348
+ * whatever sidecar the graph has; present = validated against the step library and stored as a sidecar. The
349
+ * run builds commands from it server-side (src/graph-wizard/saved-steps.ts) — no client command text is kept. */
350
+ function stepsOf(raw: unknown, yaml: string): Record<string, WizardNodeStep> | null | Response {
351
+ // Only an absent field means «keep what is there»; an explicit null or any other non-object is a bad request.
352
+ if (raw === undefined) return null;
353
+ const parsed = parseWizardSteps(raw, yaml);
354
+ if (!parsed.ok) return jsonResponse({ error: 'bad_request', reason: parsed.reason }, 400);
355
+ return parsed.steps;
356
+ }
357
+
358
+ function saveFailed(id: string, error: unknown): Response {
359
+ const reason = error instanceof Error ? error.message.slice(0, 200) : String(error);
360
+ debug.log('nexus.graphs', 'save-failed', { id, reason }, { level: 'warn' });
361
+ return jsonResponse({ error: 'save-failed', id, reason }, 500);
362
+ }
363
+
334
364
  /** POST /v1/graphs {id, yaml} — a brand-new «mine» graph (blank canvas → save). Records version v1. */
335
365
  export async function handleGraphsCreate(req: Request, deps: GraphsApiDeps = {}): Promise<Response> {
336
- let body: { id?: unknown; yaml?: unknown };
337
- try { body = await req.json() as { id?: unknown; yaml?: unknown }; }
366
+ let body: { id?: unknown; yaml?: unknown; steps?: unknown };
367
+ try { body = await req.json() as { id?: unknown; yaml?: unknown; steps?: unknown }; }
338
368
  catch { return jsonResponse({ error: 'bad_request', reason: 'json body required' }, 400); }
339
369
  const id = typeof body?.id === 'string' ? resolveId(body.id) : null;
340
370
  if (!id) return jsonResponse({ error: 'bad_request', reason: 'id must match [a-z0-9-]' }, 400);
@@ -347,10 +377,18 @@ export async function handleGraphsCreate(req: Request, deps: GraphsApiDeps = {})
347
377
  if (indexByDeclaredId(mineDir).has(id) || existsSync(join(mineDir, `${id}.yaml`))) return jsonResponse({ error: 'exists', id }, 409);
348
378
  const rejected = rejectGraphYaml(body.yaml, id);
349
379
  if (rejected) return rejected;
350
- mkdirSync(mineDir, { recursive: true });
351
- writeFileSync(join(mineDir, `${id}.yaml`), body.yaml, { flag: 'wx' });
352
- const version = recordVersion(mineDir, id, body.yaml);
353
- debug.log('nexus.graphs', 'saved', { id, version, created: true });
380
+ const steps = stepsOf(body.steps, body.yaml);
381
+ if (steps instanceof Response) return steps;
382
+ let version: string;
383
+ try {
384
+ version = withWizardSteps(mineDir, id, steps, () => {
385
+ mkdirSync(mineDir, { recursive: true });
386
+ writeFileSync(join(mineDir, `${id}.yaml`), body.yaml as string, { flag: 'wx' });
387
+ try { return recordVersion(mineDir, id, body.yaml as string); }
388
+ catch (error) { rmSync(join(mineDir, `${id}.yaml`), { force: true }); throw error; }
389
+ });
390
+ } catch (error) { return saveFailed(id, error); }
391
+ debug.log('nexus.graphs', 'saved', { id, version, created: true, steps: steps ? Object.keys(steps).length : 0 });
354
392
  return jsonResponse({ id, source: 'mine', editable: true, saved: true, version, previous: null }, 201);
355
393
  }
356
394
 
@@ -0,0 +1,205 @@
1
+ // HARNESS-RUN-LIVE-GRAPH (0.2.21) — one harness run's «which node, which outcome, when» for the PWA
2
+ // live/replay graph page (`/live-run/?run=<runId>`).
3
+ //
4
+ // Canonical source = the run's own ledger `~/.elanous/run-ledger/<runId>.jsonl` (federated: pod ledgers synced
5
+ // home are found through the same directories as the running-runs query):
6
+ // · `pipeline-node-entry` — one per node visit (node · round · graphId)
7
+ // · `graph-visit-budget` — the visit count the runtime itself counted
8
+ // · per-node outcome events — implemented · gated · reviewed(+review-reflect) · rework-budget · pre-pr-sync ·
9
+ // gate.postsync · pr-opened · merge-decision
10
+ // · `run-status` — terminal status of the whole run
11
+ // Durations are derived (gap to the next node entry, or to the terminal status); nothing writes a node «end».
12
+ //
13
+ // Node ids are never hardcoded for routing: any node the graph declares and the runtime enters (including the journey
14
+ // nodes HARNESS-FULL-GRAPH adds — entrance, queue, author, dispatch, landing, failure side) shows up as a step. A
15
+ // generic `pipeline-node-exit {node, outcome, detail?}` wins over the per-node heuristics below when present.
16
+ //
17
+ // ⛔ No goal body: the title is the first line of `start.feature`, cut to 80 chars. No artifact paths, no tokens.
18
+
19
+ import { loadFederatedRunLedger, loadRunLedger, runLedgerDir, type RunLedgerEntry } from '../../self-implement/run-ledger.js';
20
+ import { redactSecretText } from '../../debug/log.js';
21
+
22
+ export interface TraversalStepWire {
23
+ node: string;
24
+ round: number | null;
25
+ /** `pass` · `fail` · null (still running). */
26
+ outcome: string | null;
27
+ at: string;
28
+ durationMs: number | null;
29
+ visit: number;
30
+ maxVisits: number | null;
31
+ detail: string | null;
32
+ }
33
+
34
+ export interface RunTraversalWire {
35
+ runId: string;
36
+ graphId: string | null;
37
+ title: string | null;
38
+ /** `running` until the ledger carries a terminal `run-status`. */
39
+ status: string;
40
+ stage: string | null;
41
+ startedAt: string | null;
42
+ endedAt: string | null;
43
+ prNumber: number | null;
44
+ substrate: string | null;
45
+ steps: TraversalStepWire[];
46
+ ledgerEvents: number;
47
+ }
48
+
49
+ const str = (value: unknown): string | null => typeof value === 'string' && value.trim() ? value : null;
50
+ const num = (value: unknown): number | null => typeof value === 'number' && Number.isFinite(value) ? value : null;
51
+ const ms = (iso: string | undefined | null): number => iso ? Date.parse(iso) : Number.NaN;
52
+
53
+ const TERMINAL_FAIL = new Set(['failed', 'abandoned', 'cancelled', 'error', 'aborted', 'stopped']);
54
+
55
+ /** A display title from a goal's first line: path-like tokens shrink to their basename (`a/b/c.tsx` → `c.tsx`),
56
+ * then the line is redacted and cut to 80 chars. Shared with the PWA picker (`apps/pwa/src/lib/live-run-view.ts`). */
57
+ export function displayTitle(text: string | null | undefined, max = 80): string | null {
58
+ const line = text?.split('\n').map((part) => part.trim()).find(Boolean) ?? '';
59
+ const shrunk = line.replace(/\s*\[truncated[^\]]*\]\s*$/, '').replace(/(?:~|\.{0,2})?\/?(?:[\w.@-]+\/)+([\w.@-]+)/g, '$1');
60
+ const cut = shrunk.length > max ? `${shrunk.slice(0, max - 1)}…` : shrunk;
61
+ return cut ? redactSecretText(cut) : null;
62
+ }
63
+
64
+ function titleOf(entries: readonly RunLedgerEntry[]): string | null {
65
+ return displayTitle(str(entries.find((entry) => entry.event === 'start')?.data.feature));
66
+ }
67
+
68
+ /** Outcome + one-line detail of a node visit, read from the events between its entry and the next entry. */
69
+ function judgeVisit(node: string, window: readonly RunLedgerEntry[], lookback: readonly RunLedgerEntry[] = []): { outcome: 'pass' | 'fail' | null; detail: string | null } {
70
+ const last = (event: string) => [...window].reverse().find((entry) => entry.event === event)?.data;
71
+ // Some decisions are written just before the node they belong to is entered (merge-decision lands in the regate
72
+ // window, right before open-pr) — look back to the previous node entry for those.
73
+ const near = (event: string) => last(event) ?? [...lookback].reverse().find((entry) => entry.event === event)?.data;
74
+ const exit = [...window].reverse().find((entry) => entry.event === 'pipeline-node-exit' && entry.data.node === node)?.data;
75
+ const exitOutcome = str(exit?.outcome);
76
+ if (exitOutcome) {
77
+ const failed = TERMINAL_FAIL.has(exitOutcome) || exitOutcome === 'fail';
78
+ const exitDetail = str(exit?.detail)?.replace(/\s+/g, ' ').trim();
79
+ return { outcome: failed ? 'fail' : 'pass', detail: exitDetail ? redactSecretText(exitDetail.slice(0, 120)) : exitOutcome };
80
+ }
81
+ switch (node) {
82
+ case 'implement': {
83
+ const done = last('implemented');
84
+ if (!done) return { outcome: null, detail: null };
85
+ const evidence = last('off-diff-evidence');
86
+ const covered = num(evidence?.coveredEvidence); const required = num(evidence?.requiredEvidence);
87
+ const proof = covered !== null && required !== null ? ` · 증거 ${covered}/${required}` : '';
88
+ return done.ok === false ? { outcome: 'fail', detail: '구현 실패' } : { outcome: 'pass', detail: `구현 완료${proof}` };
89
+ }
90
+ case 'gate': {
91
+ const gated = last('gated');
92
+ if (!gated) return { outcome: null, detail: null };
93
+ return gated.passed === true ? { outcome: 'pass', detail: '게이트 통과' } : { outcome: 'fail', detail: '게이트 실패' };
94
+ }
95
+ case 'regate': {
96
+ const gated = last('gate.postsync');
97
+ if (!gated) return { outcome: null, detail: null };
98
+ return gated.passed === true ? { outcome: 'pass', detail: 'main 동기화 뒤 재게이트 통과' } : { outcome: 'fail', detail: '재게이트 실패' };
99
+ }
100
+ case 'review': {
101
+ const review = last('reviewed');
102
+ if (!review) return { outcome: null, detail: null };
103
+ const verdict = str(review.verdict) ?? '?';
104
+ const mustFix = num(review.mustFix);
105
+ const converged = window.some((entry) => entry.event === 'review-reflect' && entry.data.converged === true);
106
+ const base = `리뷰 ${verdict}${mustFix !== null ? ` · 꼭 고칠 것 ${mustFix}` : ''}`;
107
+ if (converged) return { outcome: 'pass', detail: `${base} · 반박 수렴` };
108
+ return { outcome: verdict === 'fail' ? 'fail' : 'pass', detail: base };
109
+ }
110
+ case 'rework': {
111
+ const budget = last('rework-budget');
112
+ const verdict = str(budget?.verdict);
113
+ return { outcome: 'pass', detail: verdict ? `재작업 예산 ${verdict}` : '재작업' };
114
+ }
115
+ case 'main-sync': {
116
+ const sync = last('pre-pr-sync');
117
+ if (!sync) return { outcome: null, detail: null };
118
+ const status = str(sync.status) ?? '?';
119
+ return { outcome: status === 'merged' || status === 'up-to-date' ? 'pass' : 'fail', detail: `main 동기화 ${status}` };
120
+ }
121
+ case 'open-pr': {
122
+ const opened = last('pr-opened');
123
+ const decision = str(near('merge-decision')?.decision);
124
+ if (!opened) return { outcome: null, detail: decision ? `병합 결정 ${decision}` : null };
125
+ const number = num(opened.number);
126
+ return { outcome: 'pass', detail: `PR${number !== null ? ` #${number}` : ''} 열림${decision ? ` · 병합 결정 ${decision}` : ''}` };
127
+ }
128
+ default:
129
+ return { outcome: null, detail: null };
130
+ }
131
+ }
132
+
133
+ export function buildRunTraversal(runId: string, entries: readonly RunLedgerEntry[]): RunTraversalWire {
134
+ const sorted = [...entries].map((entry, index) => ({ entry, index }))
135
+ .sort((a, b) => (ms(a.entry.timestamp) - ms(b.entry.timestamp)) || a.index - b.index).map(({ entry }) => entry);
136
+ const terminal = [...sorted].reverse().find((entry) => entry.event === 'run-status' && str(entry.data.runStatus));
137
+ const status = terminal ? str(terminal.data.runStatus)! : 'running';
138
+ const entryIndexes = sorted.map((entry, index) => entry.event === 'pipeline-node-entry' && str(entry.data.node) ? index : -1).filter((index) => index >= 0);
139
+ const visits = new Map<string, number>();
140
+ const steps: TraversalStepWire[] = entryIndexes.map((at, position) => {
141
+ const entry = sorted[at]!;
142
+ const node = str(entry.data.node)!;
143
+ const nextAt = entryIndexes[position + 1];
144
+ const window = sorted.slice(at + 1, nextAt ?? sorted.length);
145
+ const budget = [...sorted.slice(Math.max(0, at - 3), at)].reverse()
146
+ .find((candidate) => candidate.event === 'graph-visit-budget' && candidate.data.node === node);
147
+ const visit = num(budget?.data.visits) ?? (visits.get(node) ?? 0) + 1;
148
+ visits.set(node, visit);
149
+ const prevAt = position > 0 ? entryIndexes[position - 1]! : -1;
150
+ let { outcome, detail } = judgeVisit(node, window, sorted.slice(prevAt + 1, at));
151
+ const isLast = nextAt === undefined;
152
+ if (!isLast && outcome === null) outcome = 'pass';
153
+ if (isLast && terminal) outcome = TERMINAL_FAIL.has(status) ? 'fail' : (outcome ?? 'pass');
154
+ const endIso = nextAt !== undefined ? sorted[nextAt]!.timestamp : terminal?.timestamp;
155
+ const duration = ms(endIso) - ms(entry.timestamp);
156
+ return {
157
+ node,
158
+ round: num(entry.data.round),
159
+ outcome,
160
+ at: entry.timestamp ?? '',
161
+ durationMs: Number.isFinite(duration) && duration >= 0 ? duration : null,
162
+ visit,
163
+ maxVisits: num(budget?.data.maxVisits),
164
+ detail,
165
+ };
166
+ });
167
+
168
+ const contract = sorted.find((entry) => entry.event === 'graph-run-contract-resolved')?.data;
169
+ const pr = [...sorted].reverse().find((entry) => entry.event === 'pr-opened')?.data;
170
+ return {
171
+ runId,
172
+ graphId: str(contract?.graphId) ?? str(sorted.find((entry) => entry.event === 'pipeline-node-entry')?.data.graphId),
173
+ title: titleOf(sorted),
174
+ status,
175
+ stage: terminal ? str(terminal.data.stage) : null,
176
+ startedAt: sorted[0]?.timestamp ?? null,
177
+ endedAt: terminal?.timestamp ?? null,
178
+ prNumber: num(pr?.number),
179
+ substrate: str(contract?.actualSubstrate),
180
+ steps,
181
+ ledgerEvents: sorted.length,
182
+ };
183
+ }
184
+
185
+ export interface TraversalDeps {
186
+ loadLedger?: (runId: string) => RunLedgerEntry[] | null;
187
+ }
188
+
189
+ const USAGE = 'usage: GET /v1/harness/run-traversal?runId=<runId>';
190
+
191
+ /** GET /v1/harness/run-traversal?runId= — ordered node steps of one harness run. */
192
+ export function handleHarnessRunTraversalGet(req: Request, deps: TraversalDeps = {}): Response {
193
+ const runId = new URL(req.url).searchParams.get('runId')?.trim() ?? '';
194
+ if (!runId || /[\\/]|\.\./.test(runId)) return Response.json({ error: USAGE }, { status: 400 });
195
+ let entries: RunLedgerEntry[] | null;
196
+ try {
197
+ entries = deps.loadLedger
198
+ ? deps.loadLedger(runId)
199
+ : loadRunLedger(runId, runLedgerDir()) ?? loadFederatedRunLedger(runId);
200
+ } catch (error) {
201
+ return Response.json({ error: 'ledger-unreadable', reason: error instanceof Error ? error.message.slice(0, 200) : 'unknown' }, { status: 422 });
202
+ }
203
+ if (!entries) return Response.json({ error: 'run-not-found', runId }, { status: 404 });
204
+ return Response.json(buildRunTraversal(runId, entries));
205
+ }
@@ -23,6 +23,7 @@ import { isAbsolute } from 'node:path';
23
23
  import { createProject, listProjects } from '../../project/project-store.js';
24
24
  import { handleOutputsGet, type OutputsDeps } from './outputs-route.js';
25
25
  import { buildGridData, type GridDeps } from './grid.js';
26
+ import { createDraftMetricsSource, type DraftMetricsSource } from './draft-metrics.js';
26
27
  import { DECISIONS_PATH, handleDecisions, type DecisionsRouteDeps } from './decisions-route.js';
27
28
  import { OPS_BOARD_PATH, handleOpsBoard, type OpsBoardDeps } from './ops-board-route.js';
28
29
  import { CARD_FOLLOWUP_PATH, createCardFollowupJobs, type CardFollowupJobs } from './card-followup-route.js';
@@ -31,6 +32,7 @@ import { compareTokenConstTime } from '../../acp/transport/auth.js';
31
32
  import { ensureAuthToken } from '../../auth/acp-token.js';
32
33
  import type { NexusState } from '../state/state.js';
33
34
  import { DEFAULT_NEXUS_HTTP_PORT } from '../default-port.js';
35
+
34
36
  export { DEFAULT_NEXUS_HTTP_PORT } from '../default-port.js';
35
37
  import type { TabRegistry } from '../state/tab-registry.js';
36
38
  import type { FeedbackEnvelope } from '../../feedback/envelope.js';
@@ -164,6 +166,7 @@ import {
164
166
  } from './vault-api.js';
165
167
  import { handleBuildsGet, parseBuildsPath } from './builds-api.js';
166
168
  import { handleHarnessAskPost, handleHarnessAskStatusGet, handleHarnessRunEventsGet, handleHarnessRunScreenGet, handleHarnessRunsGet, handleHarnessStopPost } from './harness-api.js';
169
+ import { handleHarnessRunTraversalGet } from './harness-traversal.js';
167
170
  import { handleLoopEdgesGet } from './loop-edges.js';
168
171
  import { handleFabricPlans, type FabricPlansRouteOpts } from './fabric-plans.js';
169
172
  import { dispatchPersonaRoute } from './personas.js';
@@ -367,6 +370,7 @@ import { handleTriggersSnapshot } from './triggers.js';
367
370
  import { handleWorkflowTemplatesList } from './workflow-templates.js';
368
371
  import { handleGraphVersionsGet, handleGraphsGet, handleGraphsMutation } from './graphs-api.js';
369
372
  import { handleGraphKindsGet, handleGraphsValidatePost } from './graph-kinds.js';
373
+ import { handleGraphWizardPost } from './graph-wizard-api.js';
370
374
  import { syncInstalledPluginNodes } from '../../graph-kinds/installed-plugin-nodes.js';
371
375
  import { elanousStateRoot } from '../../autopilot/state-paths.js';
372
376
  import { handlePluginsIndexGet, handlePluginsGet, handlePluginsMarketRefresh, handlePluginsInstall, handlePluginsRemove } from './plugins-market.js';
@@ -426,6 +430,9 @@ import {
426
430
  parseShowroomLayoutPath,
427
431
  } from './showroom-layouts.js';
428
432
 
433
+ /** Process-wide DRAFT-METRIC cache (one background collection per TTL, shared by every page load). */
434
+ let sharedDraftMetrics: DraftMetricsSource | undefined;
435
+
429
436
  export const NEXUS_HTTP_PORT_RANGE = 16; // try 31415..31430
430
437
  /** Short timeout for the pre-bind localhost occupancy probe. */
431
438
  export const NEXUS_PORT_PROBE_TIMEOUT_MS = 400;
@@ -480,11 +487,15 @@ export type NexusWsBridgeInit = Omit<WsBridgeOpts, 'hostname' | 'port'>;
480
487
  export interface NexusHttpServerOpts {
481
488
  /** Optional fabric-plan ledger and decomposition seam; production uses the instance root and existing fabric decomposer. */
482
489
  fabricPlans?: FabricPlansRouteOpts;
490
+ /** GRAPH-WIZARD 시험 주입(LLM·템플릿 뿌리). 운영은 비워 둔다. */
491
+ graphWizard?: import('../../graph-wizard/generate.js').GraphWizardDeps;
483
492
  /** Optional installer state root for isolated API consumers and tests. */
484
493
  pluginStateRoot?: string;
485
494
  outputs?: OutputsDeps;
486
495
  /** Read-only grid measurement seam; omitted in production for live HQ and Pod pool reads. */
487
496
  grid?: GridDeps;
497
+ /** DRAFT-METRIC cached source seam; omitted in production for the process-wide TTL cache. */
498
+ draftMetrics?: DraftMetricsSource;
488
499
  decisions?: Pick<DecisionsRouteDeps, 'ledger'>;
489
500
  opsBoard?: Pick<OpsBoardDeps, 'openStore'>;
490
501
  cardFollowup?: CardFollowupJobs;
@@ -1360,6 +1371,13 @@ export async function routeRequest(
1360
1371
  return handleHarnessRunEventsGet(req, opts.metaApi);
1361
1372
  }
1362
1373
 
1374
+ // HARNESS-RUN-LIVE-GRAPH — one run's ordered node steps ⊕ journey band (PWA /live-run).
1375
+ if (pathname === '/v1/harness/run-traversal' && method === 'GET') {
1376
+ if (!opts.metaApi) return jsonResponse({ error: 'meta-api-runtime-not-wired' }, 503);
1377
+ if (!checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
1378
+ return handleHarnessRunTraversalGet(req);
1379
+ }
1380
+
1363
1381
  // Design direction is a write: check owner auth before the handler reads the body.
1364
1382
  // Keep this POST ahead of the mutation fallback, and never make it a public route.
1365
1383
  if (method === 'POST' && pathname === DESIGN_DIRECTION_PATH) {
@@ -1389,6 +1407,13 @@ export async function routeRequest(
1389
1407
  return handleGraphsValidatePost(req, opts.metaApi);
1390
1408
  }
1391
1409
 
1410
+ // GRAPH-WIZARD — 말 → 그래프 YAML(저장 안 함). 다른 /v1/graphs 쓰기와 같은 인증.
1411
+ if (method === 'POST' && pathname === '/v1/graphs/wizard') {
1412
+ if (!opts.metaApi) return jsonResponse({ error: 'meta-api-not-wired' }, 503);
1413
+ if (!checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
1414
+ return handleGraphWizardPost(req, opts.graphWizard);
1415
+ }
1416
+
1392
1417
  if (pathname === '/v1/fabric/decompose' || pathname.startsWith('/v1/fabric/plans/')) {
1393
1418
  if (!opts.metaApi || !checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
1394
1419
  return handleFabricPlans(req, opts.fabricPlans);
@@ -1437,6 +1462,12 @@ export async function routeRequest(
1437
1462
  if (method !== 'GET') return jsonResponse({ error: 'method-not-allowed', method }, 405);
1438
1463
  return jsonResponse(buildGridData(opts.grid));
1439
1464
  }
1465
+ // DRAFT-METRIC: answers from memory; a stale/cold cache starts one background collection (never per request).
1466
+ if (pathname === '/v1/drafts/metrics') {
1467
+ if (!opts.metaApi || !checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
1468
+ if (method !== 'GET') return jsonResponse({ error: 'method-not-allowed', method }, 405);
1469
+ return jsonResponse((opts.draftMetrics ?? (sharedDraftMetrics ??= createDraftMetricsSource())).read());
1470
+ }
1440
1471
  if (method === 'GET' && pathname === '/v1/outputs') {
1441
1472
  if (!opts.metaApi || !checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
1442
1473
  return handleOutputsGet(req, opts.outputs);