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,545 @@
1
+ /**
2
+ * GRAPH-WIZARD — 말 한 줄(또는 대화 한 턴)에서 그래프 YAML 을 짓는다.
3
+ *
4
+ * 목표: «플러그인 마법사처럼 그래프도 말로 말하면 컨셉으로 생겨나게 · 채팅으로 쭉 치면 노드와 간선이 쭉 만들어진다».
5
+ *
6
+ * 흐름: ① 가장 가까운 기존 템플릿(graphs/**)을 고른다(키워드 점수 — LLM 이 목록에서 다시 고를 수 있다)
7
+ * ② LLM 이 «알려진 노드 종류·스키마»만으로 YAML 을 쓴다(편집이면 currentYaml 을 고쳐 «전체»를 돌려준다)
8
+ * ③ API 와 «같은» 검증기(validateGraphYaml)로 재고, 실패면 문제를 되먹여 최대 3회.
9
+ * ⛔ 저장하지 않는다 — 저장은 편집기의 기존 POST /v1/graphs 몫이다.
10
+ */
11
+ import { createHash } from 'node:crypto';
12
+ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
13
+ import { join } from 'node:path';
14
+ import { parse as parseYaml } from 'yaml';
15
+ import { debug } from '../debug/log.js';
16
+ import { listNodeKinds, type GraphKind } from '../graph-kinds/registry.js';
17
+ import { validateGraphYaml } from '../nexus/api/graph-kinds.js';
18
+ import { defaultGraphsDir } from '../self-implement/graph-templates.js';
19
+ import { WORKFLOW_SYNTH_SYSTEM_PROMPT } from '../workflow-synth/system-prompt.js';
20
+ import { WIZARD_ARCHETYPES } from './archetypes.js';
21
+ import { WIZARD_MAX_RETRIES, WIZARD_STEPS, WIZARD_STEP_IDS, recipesYamlFor, type WizardNodeStep } from './steps.js';
22
+
23
+ export interface GraphWizardTurn { role: 'user' | 'assistant'; text: string }
24
+
25
+ export interface GraphWizardRequest {
26
+ prompt: string;
27
+ kind?: GraphKind;
28
+ /** 있으면 prompt 는 «이 그래프를 고치라»는 지시다 — 결과는 고친 «전체» YAML. */
29
+ currentYaml?: string;
30
+ history?: GraphWizardTurn[];
31
+ }
32
+
33
+ export interface GraphWizardResult {
34
+ ok: boolean;
35
+ id: string;
36
+ yaml: string;
37
+ base?: string;
38
+ /** 왜 그 기반을 골랐나 — 키워드 · LLM 재선택. */
39
+ baseReason?: string;
40
+ issues: string[];
41
+ attempts: number;
42
+ summary: string;
43
+ /** 노드 id → 한국어 이름 (YAML 노드 줄 주석에서 읽는다). */
44
+ labels?: Record<string, string>;
45
+ /** 노드 id → 서재 단계(·인자). 하니스만. */
46
+ steps?: Record<string, WizardNodeStep>;
47
+ /** 이 그래프 옆에 둘 recipes.yaml — `elanous graph run` 이 cmd/approval 을 여기서 찾는다. 하니스만. */
48
+ recipes?: string;
49
+ /** 실행 가능성 증명: 그 recipes 로 `graph run --dry-run` 을 걸어 본 결과. 하니스만. */
50
+ dryRun?: { status: string; path: string[] };
51
+ }
52
+
53
+ export interface GraphWizardDeps {
54
+ /** LLM 한 번 부르기. 기본 = 역할 `graph-grow` 해석 ⊕ 설정 폴백 체인(streamLLM). */
55
+ callLLM?: (prompt: string) => Promise<string>;
56
+ /** 템플릿 뿌리(기본 graphs/). */
57
+ graphsDir?: string;
58
+ /** 이미 쓰인 graph_id(핵심 ⊕ 내 그래프). 기본 = graphs/** ⊕ <state>/graphs. */
59
+ existingIds?: () => ReadonlySet<string>;
60
+ maxAttempts?: number;
61
+ }
62
+
63
+ export const GRAPH_WIZARD_MAX_ATTEMPTS = 3;
64
+
65
+ /** 요청 자체가 틀렸다(400) — LLM 실패(502)와 가른다. */
66
+ export class GraphWizardInputError extends Error { override name = 'GraphWizardInputError'; }
67
+ const GRAPH_ID = /^[a-z0-9-]+$/;
68
+
69
+ interface TemplateEntry { id: string; description: string; text: string; runnable: boolean; keywords?: string[] }
70
+
71
+ function walkYaml(dir: string, depth = 0): string[] {
72
+ if (depth > 3 || !existsSync(dir)) return [];
73
+ let names: string[];
74
+ try { names = readdirSync(dir); } catch { return []; }
75
+ return names.flatMap((name) => {
76
+ if (name.startsWith('.') || name === 'overlays' || name === 'catalog') return [];
77
+ const path = join(dir, name);
78
+ let dirLike = false;
79
+ try { dirLike = statSync(path).isDirectory(); } catch { return []; }
80
+ if (dirLike) return walkYaml(path, depth + 1);
81
+ return /\.ya?ml$/.test(name) && !/^recipes\.ya?ml$/.test(name) ? [path] : [];
82
+ });
83
+ }
84
+
85
+ function graphIdOf(text: string): string | undefined {
86
+ try {
87
+ const raw = parseYaml(text) as { graph_id?: unknown; name?: unknown } | null;
88
+ if (raw && typeof raw === 'object') {
89
+ if (typeof raw.graph_id === 'string') return raw.graph_id;
90
+ if (typeof raw.name === 'string') return raw.name;
91
+ }
92
+ } catch { /* not a graph */ }
93
+ return undefined;
94
+ }
95
+
96
+ /** 검증을 통과하는 하니스 템플릿만 «기반» 후보가 된다 — 깨진 표본을 보여주면 깨진 것을 배운다. */
97
+ export function listWizardTemplates(dir = defaultGraphsDir()): TemplateEntry[] {
98
+ // 원형이 맨 앞 — 사람이 말로 시키는 일의 뼈대(운영 템플릿은 개발·출시 루프라 말과 낱말이 안 겹친다).
99
+ const out: TemplateEntry[] = WIZARD_ARCHETYPES.map((a) => ({ id: a.id, description: a.description, text: a.text, runnable: true, keywords: a.keywords }));
100
+ const seen = new Set<string>(out.map((t) => t.id));
101
+ for (const file of walkYaml(dir)) {
102
+ let text: string;
103
+ try { text = readFileSync(file, 'utf8'); } catch { continue; }
104
+ let raw: Record<string, unknown> | null;
105
+ try { raw = parseYaml(text) as Record<string, unknown> | null; } catch { continue; }
106
+ if (!raw || typeof raw.graph_id !== 'string' || seen.has(raw.graph_id)) continue;
107
+ if (!validateGraphYaml('harness', text).ok) continue;
108
+ const loop = (raw.loop ?? {}) as { title?: unknown; description?: unknown };
109
+ const description = [loop.title, loop.description].filter((v): v is string => typeof v === 'string').join(' — ') || raw.graph_id;
110
+ const terminals = Array.isArray(raw.terminal_nodes) ? raw.terminal_nodes : [];
111
+ seen.add(raw.graph_id);
112
+ out.push({ id: raw.graph_id, description: description.slice(0, 160), text, runnable: terminals.every((t) => t === 'done' || t === 'failed') });
113
+ }
114
+ return out;
115
+ }
116
+
117
+ function grams(text: string): Set<string> {
118
+ const lower = text.toLowerCase();
119
+ const out = new Set<string>();
120
+ for (const word of lower.split(/[^\p{L}\p{N}]+/u).filter(Boolean)) {
121
+ if (/^[a-z0-9]+$/.test(word)) { if (word.length > 2) out.add(word); continue; }
122
+ for (let i = 0; i + 1 < word.length; i += 1) out.add(word.slice(i, i + 2));
123
+ }
124
+ return out;
125
+ }
126
+
127
+ export interface BasePick { template: TemplateEntry; reason: string }
128
+
129
+ /**
130
+ * 원형 키워드(낱말이 말 안에 있으면 3점) ⊕ 설명 한글 2-gram 겹침(1점)으로 가장 가까운 «실행 가능한» 템플릿.
131
+ * 동점이면 원형이 앞선다(목록 순서). 0점이면 «모아서 요약해 보내기» 원형. 이유를 함께 낸다.
132
+ */
133
+ export function pickBaseTemplate(prompt: string, templates: readonly TemplateEntry[]): BasePick | undefined {
134
+ const lower = prompt.toLowerCase();
135
+ const wanted = grams(prompt);
136
+ let best: BasePick | undefined;
137
+ let bestScore = 0;
138
+ for (const template of templates) {
139
+ if (!template.runnable) continue;
140
+ const hits = (template.keywords ?? []).filter((k) => lower.includes(k));
141
+ const have = grams(`${template.id} ${template.description}`);
142
+ let overlap = 0;
143
+ for (const g of wanted) if (have.has(g)) overlap += 1;
144
+ const score = hits.length * 3 + overlap;
145
+ if (score > bestScore) {
146
+ bestScore = score;
147
+ best = { template, reason: hits.length ? `키워드 ${hits.slice(0, 5).join('·')}` : `설명 낱말 겹침 ${overlap}` };
148
+ }
149
+ }
150
+ if (best) return best;
151
+ const fallback = templates.find((t) => t.id === WIZARD_ARCHETYPES[0]!.id) ?? templates.find((t) => t.runnable);
152
+ return fallback ? { template: fallback, reason: '겹치는 낱말이 없어 기본 원형' } : undefined;
153
+ }
154
+
155
+ export function existingGraphIds(dirs: readonly string[]): Set<string> {
156
+ const ids = new Set<string>();
157
+ for (const dir of dirs) {
158
+ for (const file of walkYaml(dir)) {
159
+ try { const id = graphIdOf(readFileSync(file, 'utf8')); if (id) ids.add(id); } catch { /* unreadable */ }
160
+ }
161
+ }
162
+ return ids;
163
+ }
164
+
165
+ /** 슬러그 ⊕ 짧은 해시. 기존 id 와 겹치면 접미로 비켜 간다 — 덮어쓰지 않는다. */
166
+ export function wizardGraphId(prompt: string, hint: string | undefined, taken: ReadonlySet<string>): string {
167
+ const slugify = (s: string) => s.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').split('-').filter(Boolean).slice(0, 5).join('-').slice(0, 40).replace(/-+$/, '');
168
+ const stem = slugify(hint ?? '') || slugify(prompt) || 'graph';
169
+ const hash = createHash('sha256').update(prompt).digest('hex').slice(0, 6);
170
+ let id = `${stem}-${hash}`;
171
+ for (let n = 2; taken.has(id); n += 1) id = `${stem}-${hash}-${n}`;
172
+ return id;
173
+ }
174
+
175
+ export const HARNESS_EXAMPLE = `graph_id: example-digest
176
+ loop:
177
+ title: 예시 — 뉴스를 모아 요약하고 승인 뒤 보낸다
178
+ description: 뉴스를 검색해 요약하고 품질을 본 뒤 사람 승인을 받아 텔레그램으로 보낸다
179
+ trigger:
180
+ cron: "0 8 * * *"
181
+ version: 1
182
+ entry_node: collect
183
+ terminal_nodes: [done, failed]
184
+ nodes:
185
+ - { node_id: collect, kind: agent, recipe: 'cmd:collect', max_visits: 1 } # 뉴스 수집 | web-search | 오늘 AI 뉴스
186
+ - { node_id: summarize, kind: agent, recipe: 'cmd:summarize', max_visits: 2 } # 요약 | summarize | 핵심 5줄 한국어 요약
187
+ - { node_id: check, kind: judge, recipe: 'cmd:check', max_visits: 2 } # 요약 품질 판정 | check | 사실에 맞고 5줄 이내
188
+ - { node_id: approve, kind: hitl, recipe: 'approval:approve', max_visits: 1 } # 발송 승인 | approval
189
+ - { node_id: send, kind: agent, recipe: 'cmd:send', max_visits: 1 } # 텔레그램 발송 | telegram-send
190
+ - { node_id: done, kind: gate, recipe: none, max_visits: 1 } # 완료
191
+ - { node_id: failed, kind: gate, recipe: none, max_visits: 1 } # 실패
192
+ edges:
193
+ - { from: collect, on: outcome, map: { ok: summarize, fail: failed } }
194
+ - { from: summarize, on: outcome, map: { ok: check, fail: failed } }
195
+ - { from: check, on: outcome, map: { ok: approve, rework: summarize, fail: failed } }
196
+ - { from: approve, on: outcome, map: { ok: send, fail: failed } }
197
+ - { from: send, on: outcome, map: { ok: done, fail: failed } }
198
+ `;
199
+
200
+ export const WORKFLOW_EXAMPLE = `name: example-digest
201
+ description: 자료를 모아 요약하고 승인 뒤 보낸다
202
+ nodes:
203
+ - id: collect
204
+ bash: "echo collect"
205
+ - id: summarize
206
+ prompt: "다음을 다섯 줄로 요약하라: $collect.output"
207
+ depends_on: [collect]
208
+ - id: approve
209
+ approval:
210
+ message: "요약을 보낼까요?"
211
+ depends_on: [summarize]
212
+ - id: send
213
+ bash: "echo send"
214
+ depends_on: [approve]
215
+ `;
216
+
217
+ function harnessRules(kinds: string): string {
218
+ return [
219
+ '스키마(하니스 실행 그래프 · YAML):',
220
+ '- 최상위: graph_id, loop{title, description, trigger{cron|events}}, version: 1, entry_node, terminal_nodes: [done, failed], nodes, edges.',
221
+ `- nodes[]: { node_id(kebab-case 영문), kind, recipe, max_visits(1 이상 정수) }. kind 는 이것만: ${kinds}.`,
222
+ "- recipe 는 셋 중 하나뿐: 'none'(done·failed 같은 끝 노드), 'cmd:<kebab-id>'(실행 단계), 'approval:<kebab-id>'(사람 승인 — kind: hitl).",
223
+ '- 끝 노드는 정확히 done 과 failed 두 개(kind: gate, recipe: none). 다른 끝 노드를 만들지 마라.',
224
+ '- edges[]: { from, on: outcome, map: { ok: <다음>, fail: failed, <다른 결과>: <노드> } } — 모든 노드가 entry 에서 닿아야 하고, done·failed 외 노드는 나가는 간선이 있어야 한다.',
225
+ '- 되돌아가는 고리(재작업)는 map 의 다른 결과로 앞 노드를 가리키고, 그 노드의 max_visits 를 2~3 으로.',
226
+ '- 다른 키(contract, progress 등)는 쓰지 마라.',
227
+ '',
228
+ '노드 줄 형식(필수): 노드는 한 줄 흐름식 `- { node_id: …, kind: …, recipe: …, max_visits: … }` 으로 쓰고, 줄 끝 주석에 `# <한국어 이름> | <단계> | <인자>` 를 단다.',
229
+ '- 한국어 이름은 사용자가 화면에서 보는 노드 이름이다(예: 뉴스 수집). 끝 노드는 `# 완료` · `# 실패`. 승인 노드는 `# <이름> | approval`.',
230
+ "- cmd: 노드의 <단계> 는 아래 서재 중 하나다. 맞는 것이 없을 때만 custom(외부 연동 · 실행 시 «미구현»으로 실패). 인자에 `|`·`#` 을 쓰지 마라.",
231
+ ...WIZARD_STEPS.map((step) => ` · ${step.id} — ${step.use} (결과: ${step.outcomes.join('/')})`),
232
+ '- 간선 map 키는 그 단계가 내는 결과를 쓴다(예: check → ok/rework, retry-gate → retry/give-up, gh-pr-review → ok/must-fix).',
233
+ '- «실패하면 다시 시도하고 N번 넘으면 알려줘» 패턴(간선을 늘리지 않는다): 재시도는 «단계 안»에서 한다 — 각 cmd 노드 주석 끝에 `| retry=N` 을 붙인다(인자가 없으면 `# 이름 | 단계 | | retry=N`). 재시도 노드·되돌이 간선을 만들지 마라. 알림 노드(notify-me) «하나»만 더하고, 실행 노드들의 `fail: failed` 를 `fail: <알림 노드>` 로 바꾸고, 알림 노드는 `{ ok: failed, fail: failed }`.',
234
+ ].join('\n');
235
+ }
236
+
237
+ /** 워크플로 어휘는 `elanous wf synth` 의 시스템 프롬프트를 «그대로» 재사용한다(노드 변형·본문 모양·예시). 출력 형식만 이 마법사 것을 쓴다. */
238
+ function workflowRules(kinds: string): string {
239
+ return [
240
+ '--- wf synth 어휘(재사용 · 그 안의 «# Output» 절은 무시하고 아래 출력 형식을 따른다) ---',
241
+ WORKFLOW_SYNTH_SYSTEM_PROMPT.trim(),
242
+ '--- 끝 ---',
243
+ '스키마(워크플로 · YAML):',
244
+ '- 최상위: name(kebab-case), description, nodes.',
245
+ `- nodes[]: { id, <종류 키 하나>, depends_on?: [id…] }. 종류: ${kinds}. 흔한 것 = prompt: "<지시>", bash: "<명령>", approval: { message }.`,
246
+ '- 순환 금지 · depends_on 은 선언된 id 만.',
247
+ ].join('\n');
248
+ }
249
+
250
+ function extractYaml(text: string): string {
251
+ const fence = /```(?:ya?ml)?\s*\n([\s\S]*?)```/i.exec(text);
252
+ const body = fence ? fence[1]! : text.split('\n').filter((line) => !/^\s*(BASE|SLUG|SUMMARY)\s*:/.test(line)).join('\n');
253
+ return body.trim() + '\n';
254
+ }
255
+
256
+ function lineValue(text: string, key: string): string | undefined {
257
+ const match = new RegExp(`^\\s*${key}\\s*:\\s*(.+)$`, 'mi').exec(text.split('```')[0] ?? '');
258
+ return match?.[1]?.trim().replace(/^['"`]|['"`]$/g, '') || undefined;
259
+ }
260
+
261
+ /** 선언된 id 줄만 바꾼다 — 나머지 바이트(주석 포함)는 그대로. */
262
+ function forceId(yaml: string, kind: GraphKind, id: string): string {
263
+ const key = kind === 'harness' ? 'graph_id' : 'name';
264
+ const re = new RegExp(`^([ \\t]*${key}:[ \\t]*)([^\\n#]*?)([ \\t]*(?:#.*)?)$`, 'm');
265
+ return re.test(yaml) ? yaml.replace(re, `$1${id}$3`) : `${key}: ${id}\n${yaml}`;
266
+ }
267
+
268
+ function fmtIssue(issue: unknown): string {
269
+ if (typeof issue === 'string') return issue;
270
+ if (issue && typeof issue === 'object') {
271
+ const { path, message } = issue as { path?: unknown; message?: unknown };
272
+ if (typeof message === 'string') return typeof path === 'string' && path ? `${path}: ${message}` : message;
273
+ }
274
+ return JSON.stringify(issue);
275
+ }
276
+
277
+ /** API 검증기 ⊕ (하니스) `elanous graph run` 이 실제로 받는 모양 — 통과해도 못 도는 그래프를 «ok» 라 하지 않는다. */
278
+ export function validateWizardYaml(kind: GraphKind, yaml: string): string[] {
279
+ const checked = validateGraphYaml(kind, yaml);
280
+ const issues = checked.errors.map(fmtIssue);
281
+ if (kind !== 'harness' || issues.length > 0) return issues;
282
+ const raw = parseYaml(yaml) as { terminal_nodes?: unknown; nodes?: Array<{ node_id?: unknown; kind?: unknown; recipe?: unknown }> };
283
+ const terminals = Array.isArray(raw.terminal_nodes) ? raw.terminal_nodes : [];
284
+ for (const t of terminals) if (t !== 'done' && t !== 'failed') issues.push(`terminal_nodes: '${String(t)}' — 끝 노드는 done·failed 만 쓴다`);
285
+ for (const node of raw.nodes ?? []) {
286
+ const recipe = typeof node.recipe === 'string' ? node.recipe : '';
287
+ if (recipe !== 'none' && !/^(cmd|approval):[a-z0-9][a-z0-9-]*$/.test(recipe)) {
288
+ issues.push(`nodes/${String(node.node_id)}/recipe: '${recipe}' — none · cmd:<id> · approval:<id> 중 하나여야 한다`);
289
+ }
290
+ }
291
+ return issues;
292
+ }
293
+
294
+ /** 노드 줄 끝 주석 `# 이름 | 단계 | 인자` 를 읽는다. 흐름식 노드 줄에서만. */
295
+ export function parseNodeAnnotations(yaml: string): Record<string, WizardNodeStep> {
296
+ const out: Record<string, WizardNodeStep> = {};
297
+ for (const line of yaml.split('\n')) {
298
+ const match = /node_id:\s*['"]?([a-z0-9][a-z0-9-]*)['"]?[^#]*\}\s*#\s*(.+)$/.exec(line);
299
+ if (!match) continue;
300
+ const parts = match[2]!.split('|').map((part) => part.trim());
301
+ let retries: number | undefined;
302
+ const retryAt = parts.findIndex((part, index) => index >= 2 && /^retry\s*=\s*\d+$/i.test(part));
303
+ if (retryAt >= 0) retries = Number.parseInt(parts.splice(retryAt, 1)[0]!.split('=')[1]!, 10);
304
+ const [label, step, ...rest] = parts;
305
+ const arg = rest.join('|').trim();
306
+ out[match[1]!] = { ...(label ? { label } : {}), ...(step ? { step } : {}), ...(arg ? { arg } : {}), ...(retries ? { retries } : {}) };
307
+ }
308
+ return out;
309
+ }
310
+
311
+ /** cmd 노드마다 서재 단계가 있는지 — 없으면 «이름만 있는» 노드라 실제로 못 돈다. */
312
+ export function stepIssues(yaml: string, steps: Record<string, WizardNodeStep>): string[] {
313
+ const raw = parseYaml(yaml) as {
314
+ nodes?: Array<{ node_id?: unknown; kind?: unknown; recipe?: unknown }>;
315
+ edges?: Array<{ from?: unknown; to?: unknown; map?: Record<string, unknown> }>;
316
+ };
317
+ const issues: string[] = [];
318
+ const recipeOwner = new Map<string, string>();
319
+ const kindOf = new Map((raw.nodes ?? []).map((n) => [String(n.node_id), String(n.kind)]));
320
+ for (const node of raw.nodes ?? []) {
321
+ const id = String(node.node_id);
322
+ const recipe = typeof node.recipe === 'string' ? node.recipe : '';
323
+ const meta = steps[id];
324
+ // recipes.yaml 은 recipe 이름으로 한 줄 — 두 노드가 같은 이름을 쓰면 둘째 노드의 단계·인자가 사라진다.
325
+ if (recipe !== 'none' && recipeOwner.has(recipe)) issues.push(`nodes/${id}: recipe '${recipe}' 를 ${recipeOwner.get(recipe)} 도 쓴다 — 노드마다 고유한 recipe 이름(보통 cmd:<node_id>)`);
326
+ else recipeOwner.set(recipe, id);
327
+ if ((meta?.retries ?? 0) > WIZARD_MAX_RETRIES) issues.push(`nodes/${id}: retry=${meta!.retries} — 최대 ${WIZARD_MAX_RETRIES}`);
328
+ // 머지는 사람 승인 «뒤»에만 — 들어오는 간선이 전부 hitl 노드에서 와야 한다.
329
+ if (meta?.step === 'gh-pr-merge') {
330
+ const sources = (raw.edges ?? []).filter((e) => e.to === id || Object.values(e.map ?? {}).includes(id)).map((e) => String(e.from));
331
+ if (sources.length === 0 || sources.some((from) => kindOf.get(from) !== 'hitl')) issues.push(`nodes/${id}: PR 머지 앞에는 사람 승인(kind: hitl) 노드만 올 수 있다`);
332
+ }
333
+ if (!meta?.label) issues.push(`nodes/${id}: 줄 끝 주석 \`# <한국어 이름> | …\` 이 없다`);
334
+ if (recipe.startsWith('cmd:') && !(meta?.step && WIZARD_STEP_IDS.has(meta.step))) {
335
+ issues.push(`nodes/${id}: cmd 노드의 단계가 서재(${[...WIZARD_STEP_IDS].join(', ')})에 없다 — 주석 \`# 이름 | <단계> | <인자>\``);
336
+ }
337
+ }
338
+ return issues;
339
+ }
340
+
341
+ /** 실제 recipes 로 `graph run --dry-run` 을 걸어 «도는가»를 잰다(명령 실행 0). */
342
+ export async function dryRunWizardGraph(yaml: string, recipes: string): Promise<{ status: string; path: string[]; error?: string }> {
343
+ const { mkdtempSync, writeFileSync, rmSync } = await import('node:fs');
344
+ const { tmpdir } = await import('node:os');
345
+ const { runGraph } = await import('../graph-runner/runner.js');
346
+ const dir = mkdtempSync(join(tmpdir(), 'elanous-graph-wizard-'));
347
+ try {
348
+ writeFileSync(join(dir, 'graph.yaml'), yaml);
349
+ writeFileSync(join(dir, 'recipes.yaml'), recipes);
350
+ const state = await runGraph(join(dir, 'graph.yaml'), { dryRun: true, deps: { root: join(dir, 'state'), log: () => {} } });
351
+ return { status: state.status, path: state.path };
352
+ } catch (error) {
353
+ return { status: 'error', path: [], error: error instanceof Error ? error.message : String(error) };
354
+ } finally { rmSync(dir, { recursive: true, force: true }); }
355
+ }
356
+
357
+ interface Shape { nodes: string[]; labels: Record<string, string>; retries: Record<string, number>; edges: Map<string, string> }
358
+ function shapeOf(kind: GraphKind, yaml: string | undefined): Shape {
359
+ const empty: Shape = { nodes: [], labels: {}, retries: {}, edges: new Map() };
360
+ if (!yaml) return empty;
361
+ try {
362
+ const raw = parseYaml(yaml) as Record<string, unknown>;
363
+ const nodes = Array.isArray(raw.nodes) ? raw.nodes as Array<Record<string, unknown>> : [];
364
+ if (kind === 'workflow') {
365
+ const edges = new Map<string, string>();
366
+ for (const n of nodes) for (const d of Array.isArray(n.depends_on) ? n.depends_on : []) edges.set(`${String(d)}>${String(n.id)}`, String(n.id));
367
+ return { ...empty, nodes: nodes.map((n) => String(n.id)), edges };
368
+ }
369
+ // 간선 = (from, 결과) → to. 같은 칸의 목적지만 바뀌면 «재연결»이지 새 간선이 아니다.
370
+ const edges = new Map<string, string>();
371
+ for (const e of Array.isArray(raw.edges) ? raw.edges as Array<Record<string, unknown>> : []) {
372
+ if (typeof e.to === 'string') { edges.set(`${String(e.from)}:*`, e.to); continue; }
373
+ const map = e.map && typeof e.map === 'object' ? e.map as Record<string, unknown> : {};
374
+ for (const [outcome, to] of Object.entries(map)) edges.set(`${String(e.from)}:${outcome}`, String(to));
375
+ }
376
+ const notes = parseNodeAnnotations(yaml);
377
+ return {
378
+ nodes: nodes.map((n) => String(n.node_id)), edges,
379
+ labels: Object.fromEntries(Object.entries(notes).filter(([, v]) => v.label).map(([k, v]) => [k, v.label!])),
380
+ retries: Object.fromEntries(Object.entries(notes).filter(([, v]) => v.retries).map(([k, v]) => [k, v.retries!])),
381
+ };
382
+ } catch { return empty; }
383
+ }
384
+
385
+ /** 결정적 변경 요약 — LLM 의 한 줄 요약이 없거나 틀려도 «무엇이 바뀌었나»는 센 값으로 남는다. 노드 이름은 한국어 라벨. */
386
+ export function summarizeChange(kind: GraphKind, before: string | undefined, after: string): string {
387
+ const a = shapeOf(kind, before);
388
+ const b = shapeOf(kind, after);
389
+ const name = (id: string) => b.labels[id] ?? a.labels[id] ?? id;
390
+ if (!before) return `새 그래프: 노드 ${b.nodes.length}개 (${b.nodes.map(name).join(' → ')}) · 간선 ${b.edges.size}개`;
391
+ const added = b.nodes.filter((n) => !a.nodes.includes(n));
392
+ const removed = a.nodes.filter((n) => !b.nodes.includes(n));
393
+ let edgesAdded = 0;
394
+ let rewired = 0;
395
+ for (const [key, to] of b.edges) {
396
+ if (!a.edges.has(key)) edgesAdded += 1;
397
+ else if (a.edges.get(key) !== to) rewired += 1;
398
+ }
399
+ const edgesRemoved = [...a.edges.keys()].filter((key) => !b.edges.has(key)).length;
400
+ const retryChanged = b.nodes.filter((n) => (b.retries[n] ?? 0) !== (a.retries[n] ?? 0));
401
+ const parts: string[] = [];
402
+ if (added.length) parts.push(`노드 ${added.length}개 추가: ${added.map(name).join(' → ')}`);
403
+ if (removed.length) parts.push(`노드 ${removed.length}개 삭제: ${removed.map(name).join(', ')}`);
404
+ if (edgesAdded) parts.push(`간선 ${edgesAdded}개 추가`);
405
+ if (edgesRemoved) parts.push(`간선 ${edgesRemoved}개 삭제`);
406
+ if (rewired) parts.push(`간선 ${rewired}개 재연결`);
407
+ if (retryChanged.length) parts.push(`재시도 설정 ${retryChanged.length}개 노드(${retryChanged.map((n) => `${name(n)} ${b.retries[n] ?? 0}회`).join(', ')})`);
408
+ return parts.length ? parts.join(' · ') : '구조 변경 없음';
409
+ }
410
+
411
+ async function defaultCallLLM(prompt: string): Promise<string> {
412
+ const [{ PROVIDERS, streamLLM }, { getUserConfig }, { graphGrowthLlmOptions }] = await Promise.all([
413
+ import('../llm.js'), import('../user-config.js'), import('../graph-runner/graph-grow-llm.js'),
414
+ ]);
415
+ const selected = graphGrowthLlmOptions(getUserConfig());
416
+ return streamLLM([{ role: 'user', content: prompt }], () => {}, {
417
+ ...(selected.model ? { model: selected.model, provider: PROVIDERS[selected.provider!] } : {}),
418
+ reasoningEffort: 'low',
419
+ });
420
+ }
421
+
422
+ function buildPrompt(input: {
423
+ req: GraphWizardRequest; kind: GraphKind; id: string; base?: TemplateEntry; templates: readonly TemplateEntry[];
424
+ previous?: { yaml: string; issues: string[] };
425
+ }): string {
426
+ const { req, kind, id, base, templates, previous } = input;
427
+ const kinds = listNodeKinds(kind).map((k) => k.kind).join(', ');
428
+ const editing = typeof req.currentYaml === 'string' && req.currentYaml.trim() !== '';
429
+ const lines: string[] = [
430
+ '너는 엘라누스 «그래프 마법사»다. 사용자의 말을 실행 그래프 YAML 로 짓는다.',
431
+ kind === 'harness' ? harnessRules(kinds) : workflowRules(kinds),
432
+ `- ${kind === 'harness' ? 'graph_id' : 'name'} 는 정확히 "${id}" 로 쓴다.`,
433
+ '- 노드 id 는 사용자의 업무 단계를 드러내는 짧은 영문 kebab-case(예: collect-news, summarize, send-telegram). 단계 수는 3~10개.',
434
+ '- 사용자가 말한 것만 짓는다. 승인(hitl)·품질 판정(check)·재시도·알림은 사용자가 말했을 때만 넣는다 — 예외는 PR 머지·결제·공개 게시(외부 공개) 앞의 승인뿐이다. 텔레그램 발송·나에게 알림 앞에는 말하지 않은 승인을 넣지 마라.',
435
+ '',
436
+ '예시(사용자가 «품질을 보고, 내가 승인하면 보내»라고 «말했을 때»의 모양 — 말하지 않은 단계는 넣지 않는다):',
437
+ '```yaml', kind === 'harness' ? HARNESS_EXAMPLE.trim() : WORKFLOW_EXAMPLE.trim(), '```',
438
+ ];
439
+ if (kind === 'harness' && !editing) {
440
+ lines.push('', '기존 템플릿 목록(id — 설명):', ...templates.filter((t) => t.runnable).slice(0, 30).map((t) => `- ${t.id} — ${t.description}`));
441
+ if (base) lines.push('', `가장 가까운 템플릿(${base.id}) — 구조를 참고하라:`, '```yaml', base.text.trim(), '```');
442
+ }
443
+ if (req.history?.length) {
444
+ lines.push('', '지금까지의 대화:', ...req.history.slice(-10).map((t) => `${t.role === 'user' ? '사용자' : '마법사'}: ${t.text.slice(0, 500)}`));
445
+ }
446
+ if (editing) {
447
+ lines.push('', '지금 그래프(이것을 고친다 — 바뀌지 않는 노드의 id 는 그대로 둔다):', '```yaml', req.currentYaml!.trim(), '```',
448
+ '', `사용자의 수정 지시: ${req.prompt}`,
449
+ '최소 변경 규칙: 지시가 요구하는 노드·간선만 더하거나 바꾼다. 나머지 노드 줄(id·kind·recipe·주석)과 간선은 글자 그대로 둔다. id 를 바꾸지 마라. loop.title·description 은 바뀐 내용만 반영한다.',
450
+ '고친 «전체» YAML 을 돌려준다.');
451
+ } else {
452
+ lines.push('', `사용자의 말: ${req.prompt}`);
453
+ }
454
+ if (previous) {
455
+ lines.push('', '직전 시도가 검증에 실패했다. 아래 문제를 «전부» 고쳐 전체 YAML 을 다시 써라:', ...previous.issues.map((i) => `- ${i}`),
456
+ '직전 YAML:', '```yaml', previous.yaml.trim(), '```');
457
+ }
458
+ lines.push('', '출력 형식(이 순서 · 다른 설명 금지):',
459
+ ...(editing || kind !== 'harness' ? [] : ['BASE: <참고한 템플릿 id 또는 none>']),
460
+ 'SLUG: <이 그래프를 나타내는 영문 kebab-case 2~4 낱말>',
461
+ 'SUMMARY: <무엇을 만들었/바꿨는지 한국어 한 줄 — 예: 노드 2개 추가: 요약 → 텔레그램 발송 · 간선 2개>',
462
+ '```yaml', '<전체 YAML>', '```');
463
+ return lines.join('\n');
464
+ }
465
+
466
+ export async function generateGraphFromPrompt(req: GraphWizardRequest, deps: GraphWizardDeps = {}): Promise<GraphWizardResult> {
467
+ const kind: GraphKind = req.kind === 'workflow' ? 'workflow' : 'harness';
468
+ const prompt = typeof req.prompt === 'string' ? req.prompt.trim() : '';
469
+ if (!prompt) throw new GraphWizardInputError('prompt is required');
470
+ const editing = typeof req.currentYaml === 'string' && req.currentYaml.trim() !== '';
471
+ const graphsDir = deps.graphsDir ?? defaultGraphsDir();
472
+ const templates = kind === 'harness' ? listWizardTemplates(graphsDir) : [];
473
+ const pick = kind === 'harness' && !editing ? pickBaseTemplate(prompt, templates) : undefined;
474
+ const keywordBase = pick?.template;
475
+ let baseReason = pick?.reason;
476
+ const taken = deps.existingIds?.() ?? existingGraphIds([graphsDir, join((await import('../autopilot/state-paths.js')).elanousStateRoot(), 'graphs')]);
477
+ // 편집이면 지금 그래프의 id 를 지킨다 — 캔버스와 저장 경로가 그 id 를 쥐고 있다.
478
+ const currentId = editing ? graphIdOf(req.currentYaml!) : undefined;
479
+ // 편집인데 id 를 지킬 수 없으면(없음·형식 밖) 새 id 로 바꿔 «다른 그래프»를 돌려주지 않고 거절한다.
480
+ if (editing && !(currentId && GRAPH_ID.test(currentId))) {
481
+ debug.log('graph.wizard', 'rejected', { reason: 'current-id-invalid', kind });
482
+ throw new GraphWizardInputError(`currentYaml has no valid ${kind === 'harness' ? 'graph_id' : 'name'} ([a-z0-9-])`);
483
+ }
484
+ let id = editing ? currentId! : wizardGraphId(prompt, undefined, taken);
485
+ const callLLM = deps.callLLM ?? defaultCallLLM;
486
+ const maxAttempts = Math.max(1, deps.maxAttempts ?? GRAPH_WIZARD_MAX_ATTEMPTS);
487
+ let base = keywordBase?.id;
488
+ let yaml = '';
489
+ let issues: string[] = [];
490
+ let llmSummary: string | undefined;
491
+ let attempts = 0;
492
+ let steps: Record<string, WizardNodeStep> = {};
493
+ let recipes: string | undefined;
494
+ let dryRun: { status: string; path: string[] } | undefined;
495
+ const startedAt = Date.now();
496
+ debug.log('graph.wizard', 'start', { kind, editing, base, id, prompt: prompt.slice(0, 300), historyTurns: req.history?.length ?? 0 });
497
+ for (attempts = 1; attempts <= maxAttempts; attempts += 1) {
498
+ let text: string;
499
+ try {
500
+ text = await callLLM(buildPrompt({ req: { ...req, prompt }, kind, id, base: keywordBase, templates,
501
+ ...(attempts > 1 ? { previous: { yaml, issues } } : {}) }));
502
+ } catch (error) {
503
+ debug.log('graph.wizard', 'error', { id, kind, attempt: attempts, base, message: error instanceof Error ? error.message : String(error) });
504
+ throw error;
505
+ }
506
+ if (attempts === 1 && !editing) {
507
+ const picked = lineValue(text, 'BASE');
508
+ if (picked && picked !== base && templates.some((t) => t.id === picked)) { base = picked; baseReason = `LLM 재선택 (키워드 후보 ${keywordBase?.id ?? '-'})`; }
509
+ const slug = lineValue(text, 'SLUG');
510
+ if (slug && !currentId) id = wizardGraphId(prompt, slug, taken);
511
+ }
512
+ llmSummary = lineValue(text, 'SUMMARY') ?? llmSummary;
513
+ yaml = forceId(extractYaml(text), kind, id);
514
+ try { issues = validateWizardYaml(kind, yaml); }
515
+ catch (error) { issues = [`검증 실패: ${error instanceof Error ? error.message : String(error)}`]; }
516
+ steps = kind === 'harness' ? parseNodeAnnotations(yaml) : {};
517
+ recipes = undefined;
518
+ dryRun = undefined;
519
+ if (kind === 'harness' && issues.length === 0) {
520
+ issues = stepIssues(yaml, steps);
521
+ const nodes = ((parseYaml(yaml) as { nodes?: Array<{ node_id?: unknown; recipe?: unknown }> }).nodes ?? [])
522
+ .map((n) => ({ nodeId: String(n.node_id), recipe: typeof n.recipe === 'string' ? n.recipe : '' }));
523
+ recipes = recipesYamlFor(nodes, steps);
524
+ const walked = await dryRunWizardGraph(yaml, recipes);
525
+ dryRun = { status: walked.status, path: walked.path };
526
+ if (walked.status !== 'done') issues.push(`graph run --dry-run: ${walked.status}${walked.error ? ` — ${walked.error}` : ''} (경로 ${walked.path.join(' → ')})`);
527
+ }
528
+ debug.log('graph.wizard', 'attempt', { id, attempt: attempts, base, ok: issues.length === 0, issues: issues.slice(0, 10) });
529
+ if (issues.length === 0) break;
530
+ }
531
+ attempts = Math.min(attempts, maxAttempts);
532
+ const ok = issues.length === 0;
533
+ const counted = summarizeChange(kind, editing ? req.currentYaml : undefined, yaml);
534
+ const summary = llmSummary ? `${llmSummary} (${counted})` : counted;
535
+ debug.log('graph.wizard', ok ? 'validated' : 'failed', { id, kind, base, attempts, editing, issues: issues.slice(0, 10), summary, dryRun: dryRun?.status, ms: Date.now() - startedAt });
536
+ const labels = Object.fromEntries(Object.entries(steps).filter(([, v]) => v.label).map(([k, v]) => [k, v.label!]));
537
+ return { ok, id, yaml, ...(base ? { base } : {}), ...(base && baseReason ? { baseReason } : {}), issues, attempts, summary, labels, steps, ...(recipes ? { recipes } : {}), ...(dryRun ? { dryRun } : {}) };
538
+ }
539
+
540
+ /** v1 이름 유지 — 생성 YAML 의 노드 주석(단계·인자)으로 실제 recipes.yaml 을 낸다(v1 의 «미구현» 자리표시를 대체). */
541
+ export function stubRecipesFor(yaml: string): string {
542
+ const raw = parseYaml(yaml) as { nodes?: Array<{ node_id?: unknown; recipe?: unknown }> };
543
+ const nodes = (raw.nodes ?? []).map((n) => ({ nodeId: String(n.node_id), recipe: typeof n.recipe === 'string' ? n.recipe : '' }));
544
+ return recipesYamlFor(nodes, parseNodeAnnotations(yaml));
545
+ }