elanous 0.2.20 → 0.2.22

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 (456) 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/aBi6EyMtLsrl7J8eH4kF7/_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-08b716476f1d50af.js +1 -0
  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/{2489-ff55a35b202c0f94.js → 2489-d238d3e2eb5b2bf5.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/2549-a8515c86f9e8cb96.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/2655.72cc6a97276c5acc.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/2743-ef6e8c3ced74cce5.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/2840-5458ad50c49cac6a.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/2856.f75d5baef7f49b74.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/3344.0de4b0ec5a454b7a.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/335.a13f339e97893437.js +94 -0
  34. package/apps/pwa/out/_next/static/chunks/3394.646348f34b2500be.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/3443.6276909b53d72d53.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/3495.abd4254d808bf2dc.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/3522-1de1319d028ac89c.js +25 -0
  38. package/apps/pwa/out/_next/static/chunks/3721.ba5b82db0d9a5572.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/3857-5985236d9307f676.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/3919.9ff7dc25afefee33.js +74 -0
  41. package/apps/pwa/out/_next/static/chunks/3998.bd9a69d9b50e9bd2.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/4003.ab6db1dc266576c9.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/4216.e8ac94b9fe6b96ab.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/4327.c44dc2b55cb43ee5.js +34 -0
  45. package/apps/pwa/out/_next/static/chunks/4401.94257d497b6d26bc.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/4559.17ca2cdbaa8d414f.js +115 -0
  47. package/apps/pwa/out/_next/static/chunks/4588.ad3364cb7fb97ada.js +86 -0
  48. package/apps/pwa/out/_next/static/chunks/4706.97e734bd4c9816ca.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/4982.6e44352f751b9a1b.js +77 -0
  50. package/apps/pwa/out/_next/static/chunks/5342.49c5c5a530e62b50.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/5360-08a80abdf3a4da99.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/5431.66c58ab1327ee0b7.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/5633-d6852385000738ef.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/5717.bcfcb96e701b82e0.js +90 -0
  55. package/apps/pwa/out/_next/static/chunks/5755-30d102586789bf7f.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/5957.3f1a4012388dd962.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/5977-5b9e8a45a5b0c823.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/602.9bf02ac8e9502598.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/6020.3a1b347e2d2dfacc.js +8 -0
  60. package/apps/pwa/out/_next/static/chunks/{6158.af2e93b7adff709b.js → 6158.566c890adc06a459.js} +1 -1
  61. package/apps/pwa/out/_next/static/chunks/6375.fce1d174ba6d0324.js +64 -0
  62. package/apps/pwa/out/_next/static/chunks/6546.92a828aef441271f.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/658.957fdb0a8c6c6c8e.js +287 -0
  64. package/apps/pwa/out/_next/static/chunks/6706.323590aabb8b1293.js +226 -0
  65. package/apps/pwa/out/_next/static/chunks/6747.b67947f293a4c02d.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/6767.8e6c5f6e744fcd9f.js +38 -0
  67. package/apps/pwa/out/_next/static/chunks/6868.000b24a3045c5574.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/6946.bba74e9c96050f30.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/7033.a558f1b2f6912a53.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/7065.46029c26c53eac50.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/7087.6fae577d09249bc1.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/7175-350f4ce2873bc038.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/722.de8d9801e0f6d414.js +41 -0
  74. package/apps/pwa/out/_next/static/chunks/7497.57981e9d7a75a52a.js +24 -0
  75. package/apps/pwa/out/_next/static/chunks/7630.2b9a68659053ff4e.js +193 -0
  76. package/apps/pwa/out/_next/static/chunks/7672.4af122ad892117c7.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/771.6ab889a233e7dae7.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/7899.597af4a9996c907f.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/79b78388.663188133449d707.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/8096.f7da0776cfde6300.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/8194.484f51e273d9abd2.js +217 -0
  82. package/apps/pwa/out/_next/static/chunks/8769.815ca69879f608b4.js +104 -0
  83. package/apps/pwa/out/_next/static/chunks/8857-aa1cc4ac90af4d40.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/88729267.9c6c000157c83f2f.js +62 -0
  85. package/apps/pwa/out/_next/static/chunks/8f835b4f.048eeb02299e3e12.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/9068.5f2764fccf86c1b2.js +93 -0
  87. package/apps/pwa/out/_next/static/chunks/9247-55bcfbb0e8057f59.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/9451.ebfb0dcc9c02250f.js +23 -0
  89. package/apps/pwa/out/_next/static/chunks/9559.e648369b1d57cf9a.js +62 -0
  90. package/apps/pwa/out/_next/static/chunks/9832.2ab2e818013574c7.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/a9f2b54d-40c16bd0efdb04b8.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/app/approvals/{page-c855c24de7db74fe.js → page-88459e0903da2803.js} +1 -1
  93. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-85b316bb4235980b.js → page-d85c43f46f58716b.js} +1 -1
  94. package/apps/pwa/out/_next/static/chunks/app/board/{page-9b2b26a33cb2cd42.js → page-4e645600579ae952.js} +1 -1
  95. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-c9955fde67b1bcb5.js → page-87edbfd19117823b.js} +1 -1
  96. package/apps/pwa/out/_next/static/chunks/app/bots/page-7f0c2b7801ac6a54.js +1 -0
  97. package/apps/pwa/out/_next/static/chunks/app/ceo/page-bf1f030a9d1adffd.js +1 -0
  98. package/apps/pwa/out/_next/static/chunks/app/chat/{page-4ba4abe096b6c2a8.js → page-cbaf66b1e1804adf.js} +1 -1
  99. package/apps/pwa/out/_next/static/chunks/app/consult/{page-575d4a8b4f7becb2.js → page-80ed511e02c81412.js} +1 -1
  100. package/apps/pwa/out/_next/static/chunks/app/control/{page-f5d56ff5f45ad164.js → page-9f5610caaa06178c.js} +1 -1
  101. package/apps/pwa/out/_next/static/chunks/app/decisions/{page-2260e9eef0357421.js → page-ec8f3575e339de74.js} +1 -1
  102. package/apps/pwa/out/_next/static/chunks/app/design-check/page-86d753e460f1a989.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/app/editor/page-4166a7d51cf0a57d.js +1 -0
  104. package/apps/pwa/out/_next/static/chunks/app/exec/{page-466bf0ee16ad1432.js → page-2a19c5cd3b562580.js} +1 -1
  105. package/apps/pwa/out/_next/static/chunks/app/field/{page-a23bc71713bcc08f.js → page-b6a09cde2e07521a.js} +1 -1
  106. package/apps/pwa/out/_next/static/chunks/app/inside/page-f7eae1518ca0c353.js +1 -0
  107. package/apps/pwa/out/_next/static/chunks/app/intake/page-456a8573ad811381.js +1 -0
  108. package/apps/pwa/out/_next/static/chunks/app/layout-8462d6a543cbb2d9.js +1 -0
  109. package/apps/pwa/out/_next/static/chunks/app/live/page-2974cdfb87c0f9c9.js +1 -0
  110. package/apps/pwa/out/_next/static/chunks/app/live-run/page-f927ffa25f3c86d1.js +1 -0
  111. package/apps/pwa/out/_next/static/chunks/app/loops/{page-35bfec2e631d3eae.js → page-abd86cd832aaa011.js} +1 -1
  112. package/apps/pwa/out/_next/static/chunks/app/market/page-79daf68f6256adaa.js +1 -0
  113. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-f9ccb1b1adbea14d.js → page-8c3f168fc3f91939.js} +1 -1
  114. package/apps/pwa/out/_next/static/chunks/app/morning/{page-ec102c9b8b691e37.js → page-786fb4504d0f2fa9.js} +1 -1
  115. package/apps/pwa/out/_next/static/chunks/app/not-found-75209f14597dcb8f.js +1 -0
  116. package/apps/pwa/out/_next/static/chunks/app/observatory/page-bf30affe3a85af7e.js +1 -0
  117. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1187a44b7515434b.js +1 -0
  118. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-3d0910185a66ed54.js +1 -0
  119. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-f401ee1dd5a26e25.js +1 -0
  120. package/apps/pwa/out/_next/static/chunks/app/outputs/page-5dfe7604fd842784.js +1 -0
  121. package/apps/pwa/out/_next/static/chunks/app/page-76cf838050a8d681.js +1 -0
  122. package/apps/pwa/out/_next/static/chunks/app/reflection/{page-db03ae3589a81c9e.js → page-7ec8e289b4c14df6.js} +1 -1
  123. package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-4a6463b97f0ad94f.js → page-8c8ac17d52be2a79.js} +1 -1
  124. package/apps/pwa/out/_next/static/chunks/app/sessions/{page-75bffecc9aa1004e.js → page-d359ab048d63a9d9.js} +1 -1
  125. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-b8b345c880bf78d1.js +1 -0
  126. package/apps/pwa/out/_next/static/chunks/app/settings/{page-31d5b875760bb62d.js → page-f151cc10ad026940.js} +1 -1
  127. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-9875f103057da433.js +1 -0
  128. package/apps/pwa/out/_next/static/chunks/app/setup/page-9789328af9a9aa62.js +1 -0
  129. package/apps/pwa/out/_next/static/chunks/app/share/{page-46d5201d2207a511.js → page-bf495a1e284f4e00.js} +1 -1
  130. package/apps/pwa/out/_next/static/chunks/app/showroom/page-a263d94bca4be22a.js +1 -0
  131. package/apps/pwa/out/_next/static/chunks/app/tasks/page-3962eab8ec58d16e.js +1 -0
  132. package/apps/pwa/out/_next/static/chunks/app/term/{page-cf0a4b4abead71ea.js → page-9abcfac07e02a542.js} +1 -1
  133. package/apps/pwa/out/_next/static/chunks/app/today/page-bee49f927c333abb.js +1 -0
  134. package/apps/pwa/out/_next/static/chunks/app/trace/page-bac2d56dcb02d955.js +1 -0
  135. package/apps/pwa/out/_next/static/chunks/app/vault/page-937cbcc13176295c.js +1 -0
  136. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d09495594f7f480d.js +1 -0
  137. package/apps/pwa/out/_next/static/chunks/app/workflows/{page-0acc8e198f7b0dfb.js → page-69cb93d1a2e57e0e.js} +1 -1
  138. package/apps/pwa/out/_next/static/chunks/app/workspace/{page-34882c84f84021c5.js → page-72ec543928d67c3e.js} +1 -1
  139. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-82906eeda0186a7b.js +1 -0
  140. package/apps/pwa/out/_next/static/chunks/d70879db.c66c56a95a91efc6.js +66 -0
  141. package/apps/pwa/out/_next/static/chunks/f8626309.a4e5d362b1d55e59.js +1 -0
  142. package/apps/pwa/out/_next/static/chunks/webpack-f2b4016b9b981bc8.js +1 -0
  143. package/apps/pwa/out/_next/static/css/{d3427996da11e480.css → b9d8151e48998417.css} +1 -1
  144. package/apps/pwa/out/_next/static/css/f2bd5257e6af88d8.css +1 -0
  145. package/apps/pwa/out/approvals/index.html +1 -1
  146. package/apps/pwa/out/approvals/index.txt +14 -14
  147. package/apps/pwa/out/autopilot/index.html +1 -1
  148. package/apps/pwa/out/autopilot/index.txt +14 -14
  149. package/apps/pwa/out/board/index.html +1 -1
  150. package/apps/pwa/out/board/index.txt +14 -14
  151. package/apps/pwa/out/botlab/index.html +1 -1
  152. package/apps/pwa/out/botlab/index.txt +14 -14
  153. package/apps/pwa/out/bots/index.html +1 -1
  154. package/apps/pwa/out/bots/index.txt +14 -14
  155. package/apps/pwa/out/ceo/index.html +1 -1
  156. package/apps/pwa/out/ceo/index.txt +14 -14
  157. package/apps/pwa/out/chat/index.html +1 -1
  158. package/apps/pwa/out/chat/index.txt +14 -14
  159. package/apps/pwa/out/consult/index.html +1 -1
  160. package/apps/pwa/out/consult/index.txt +14 -14
  161. package/apps/pwa/out/control/index.html +1 -1
  162. package/apps/pwa/out/control/index.txt +14 -14
  163. package/apps/pwa/out/decisions/index.html +1 -1
  164. package/apps/pwa/out/decisions/index.txt +14 -14
  165. package/apps/pwa/out/design-check/index.html +1 -1
  166. package/apps/pwa/out/design-check/index.txt +14 -14
  167. package/apps/pwa/out/editor/index.html +1 -1
  168. package/apps/pwa/out/editor/index.txt +15 -15
  169. package/apps/pwa/out/exec/index.html +1 -1
  170. package/apps/pwa/out/exec/index.txt +14 -14
  171. package/apps/pwa/out/field/index.html +1 -1
  172. package/apps/pwa/out/field/index.txt +14 -14
  173. package/apps/pwa/out/healthz +1 -1
  174. package/apps/pwa/out/index.html +1 -1
  175. package/apps/pwa/out/index.txt +14 -14
  176. package/apps/pwa/out/inside/index.html +1 -1
  177. package/apps/pwa/out/inside/index.txt +15 -15
  178. package/apps/pwa/out/intake/index.html +1 -1
  179. package/apps/pwa/out/intake/index.txt +14 -14
  180. package/apps/pwa/out/live/index.html +1 -1
  181. package/apps/pwa/out/live/index.txt +14 -14
  182. package/apps/pwa/out/live-run/index.html +1 -0
  183. package/apps/pwa/out/live-run/index.txt +35 -0
  184. package/apps/pwa/out/loops/index.html +1 -1
  185. package/apps/pwa/out/loops/index.txt +14 -14
  186. package/apps/pwa/out/market/index.html +1 -1
  187. package/apps/pwa/out/market/index.txt +14 -14
  188. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  189. package/apps/pwa/out/missions/placeholder/index.txt +14 -14
  190. package/apps/pwa/out/morning/index.html +1 -1
  191. package/apps/pwa/out/morning/index.txt +14 -14
  192. package/apps/pwa/out/observatory/index.html +1 -1
  193. package/apps/pwa/out/observatory/index.txt +14 -14
  194. package/apps/pwa/out/ops/checklist/index.html +1 -1
  195. package/apps/pwa/out/ops/checklist/index.txt +14 -14
  196. package/apps/pwa/out/ops/release/index.html +1 -1
  197. package/apps/pwa/out/ops/release/index.txt +14 -14
  198. package/apps/pwa/out/ops/seats/index.html +1 -1
  199. package/apps/pwa/out/ops/seats/index.txt +14 -14
  200. package/apps/pwa/out/outputs/index.html +1 -1
  201. package/apps/pwa/out/outputs/index.txt +14 -14
  202. package/apps/pwa/out/reflection/index.html +1 -1
  203. package/apps/pwa/out/reflection/index.txt +15 -15
  204. package/apps/pwa/out/scheduler/index.html +1 -1
  205. package/apps/pwa/out/scheduler/index.txt +14 -14
  206. package/apps/pwa/out/sessions/index.html +1 -1
  207. package/apps/pwa/out/sessions/index.txt +14 -14
  208. package/apps/pwa/out/settings/devices/index.html +1 -1
  209. package/apps/pwa/out/settings/devices/index.txt +14 -14
  210. package/apps/pwa/out/settings/index.html +1 -1
  211. package/apps/pwa/out/settings/index.txt +14 -14
  212. package/apps/pwa/out/setup/done/index.html +1 -1
  213. package/apps/pwa/out/setup/done/index.txt +14 -14
  214. package/apps/pwa/out/setup/index.html +1 -1
  215. package/apps/pwa/out/setup/index.txt +14 -14
  216. package/apps/pwa/out/share/index.html +1 -1
  217. package/apps/pwa/out/share/index.txt +14 -14
  218. package/apps/pwa/out/showroom/index.html +1 -1
  219. package/apps/pwa/out/showroom/index.txt +14 -14
  220. package/apps/pwa/out/tasks/index.html +1 -1
  221. package/apps/pwa/out/tasks/index.txt +14 -14
  222. package/apps/pwa/out/term/index.html +1 -1
  223. package/apps/pwa/out/term/index.txt +14 -14
  224. package/apps/pwa/out/today/index.html +1 -1
  225. package/apps/pwa/out/today/index.txt +14 -14
  226. package/apps/pwa/out/trace/index.html +1 -1
  227. package/apps/pwa/out/trace/index.txt +14 -14
  228. package/apps/pwa/out/vault/index.html +1 -1
  229. package/apps/pwa/out/vault/index.txt +14 -14
  230. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  231. package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
  232. package/apps/pwa/out/workflows/index.html +1 -1
  233. package/apps/pwa/out/workflows/index.txt +14 -14
  234. package/apps/pwa/out/workspace/index.html +1 -1
  235. package/apps/pwa/out/workspace/index.txt +14 -14
  236. package/apps/pwa/out/worktrees/index.html +1 -1
  237. package/apps/pwa/out/worktrees/index.txt +14 -14
  238. package/graphs/catalog/catalog.yaml +1 -1
  239. package/graphs/drafts/recipes.yaml +6 -6
  240. package/graphs/implement-loop.yaml +277 -5
  241. package/graphs/test-diet/recipes.yaml +3 -0
  242. package/graphs/test-diet/test-diet.yaml +3 -1
  243. package/package.json +1 -1
  244. package/scripts/coord-channel-watch.sh +31 -4
  245. package/scripts/docs-lint.ts +4 -1
  246. package/scripts/f-session-monitor.ts +69 -0
  247. package/scripts/hq/standby-snapshot.ts +7 -3
  248. package/scripts/knowledge-compress.ts +11 -0
  249. package/scripts/marketing/generate-ax-brief.ts +18 -1
  250. package/scripts/measure-nl-routing-corpus.ts +11 -3
  251. package/scripts/nightly-docops.ts +90 -3
  252. package/scripts/release-loop/gate-node.ts +5 -5
  253. package/scripts/release-loop/shard-plan.ts +50 -5
  254. package/scripts/research-coo-admin-dates.ts +39 -0
  255. package/scripts/rhythm/daily.ts +2 -1
  256. package/scripts/skills/public-check.ts +7 -2
  257. package/scripts/smart-merge/resync-shadow.ts +5 -3
  258. package/scripts/test-diet/lib.ts +81 -1
  259. package/scripts/test-diet/node.ts +48 -2
  260. package/scripts/verify-coo-admin-evidence.ts +83 -0
  261. package/src/acp/client.ts +4 -2
  262. package/src/agent/run-context.ts +4 -0
  263. package/src/agent-env/profile.ts +25 -5
  264. package/src/agent-mission/driver.ts +26 -12
  265. package/src/agent-mission/supervisor-metrics.ts +17 -2
  266. package/src/agent-substrate/acp-reviewer.ts +12 -5
  267. package/src/agent-substrate/pr-reviewer.ts +11 -4
  268. package/src/agent-substrate/review-intent.ts +102 -3
  269. package/src/autopilot/build/llm-conflict-merge.ts +22 -8
  270. package/src/autopilot/pr-manager.ts +18 -8
  271. package/src/away/release-watch.ts +47 -8
  272. package/src/briefing/brief-items.ts +104 -11
  273. package/src/browser-cdp/client.ts +10 -5
  274. package/src/chat/index.ts +1 -0
  275. package/src/cli/brief-cli.ts +20 -2
  276. package/src/cli/config-classify.ts +83 -0
  277. package/src/cli/control-cli.ts +14 -0
  278. package/src/cli/decisions-cli.ts +26 -16
  279. package/src/cli/logs-model-input.ts +1 -1
  280. package/src/cli/ops-cli.ts +11 -0
  281. package/src/cli/ops-overview-cli.ts +50 -0
  282. package/src/cli/pod-cli.ts +1 -0
  283. package/src/cli/pr-cli.ts +65 -8
  284. package/src/cli/release-cli.ts +21 -4
  285. package/src/cli/tasks-cli.ts +214 -1
  286. package/src/connectors/types.ts +6 -6
  287. package/src/context-bus/context-now-surfaces.ts +18 -0
  288. package/src/context-bus/coord-events.ts +32 -4
  289. package/src/context-bus/long-term-memory.ts +23 -6
  290. package/src/context-bus/nightly-condense.ts +169 -21
  291. package/src/control/control-status.ts +87 -0
  292. package/src/control-plane/light-member.ts +5 -3
  293. package/src/control-plane/member.ts +172 -7
  294. package/src/core-turn/run-goal-loop.ts +22 -1
  295. package/src/dashboard/index.ts +40 -4
  296. package/src/dashboard/slash-runtime/dashboard-handlers.ts +9 -0
  297. package/src/decisions/decision-cards.ts +20 -1
  298. package/src/decisions/decision-ledger.ts +11 -8
  299. package/src/decisions/web-push-decision-cards.ts +19 -3
  300. package/src/discord-self-message.ts +2 -2
  301. package/src/domains/coo-admin-dates.evidence.json +876 -0
  302. package/src/domains/coo-admin-tool.ts +48 -14
  303. package/src/domains/ops-status.ts +53 -0
  304. package/src/domains/outbound-alert.ts +24 -1
  305. package/src/esc-abort-gate.ts +3 -1
  306. package/src/exec-requests/planner.ts +6 -0
  307. package/src/graph-kinds/registry.ts +6 -6
  308. package/src/graph-runner/graph-cli.ts +91 -2
  309. package/src/graph-runner/graph-run-api.ts +20 -0
  310. package/src/graph-runner/graph-tick.ts +73 -12
  311. package/src/graph-runner/runner.ts +43 -11
  312. package/src/graph-unify/wf-to-graph.ts +79 -0
  313. package/src/graph-wizard/archetypes.ts +88 -0
  314. package/src/graph-wizard/generate.ts +577 -0
  315. package/src/graph-wizard/saved-steps.ts +120 -0
  316. package/src/graph-wizard/steps.ts +206 -0
  317. package/src/harness/harness-cli-command.ts +186 -32
  318. package/src/harness/harness-queue.ts +58 -14
  319. package/src/harness/launch-advice.ts +23 -0
  320. package/src/index.ts +43 -11
  321. package/src/intake-plane/digest.ts +18 -11
  322. package/src/knowledge/conclusion-compress.ts +93 -0
  323. package/src/knowledge/know-find.ts +9 -2
  324. package/src/knowledge/query.ts +20 -0
  325. package/src/llm.ts +26 -16
  326. package/src/loops/neighbor-runtime.ts +88 -0
  327. package/src/loops/orchestrator/author-depth.ts +29 -11
  328. package/src/loops/orchestrator/overlap.ts +7 -1
  329. package/src/loops/orchestrator/resync-shadow.ts +1 -1
  330. package/src/loops/orchestrator/tick.ts +35 -2
  331. package/src/loops/orchestrator/traffic.ts +2 -0
  332. package/src/loops/registry.ts +5 -0
  333. package/src/market/publish.ts +17 -1
  334. package/src/maturity/feature-maturity.ts +4 -2
  335. package/src/mss/logging/redaction.ts +17 -0
  336. package/src/nexus/api/draft-metrics.ts +128 -0
  337. package/src/nexus/api/graph-peer-edit.ts +77 -0
  338. package/src/nexus/api/graph-wizard-api.ts +61 -0
  339. package/src/nexus/api/graphs-api.ts +193 -17
  340. package/src/nexus/api/harness-traversal.ts +205 -0
  341. package/src/nexus/api/http-server.ts +122 -6
  342. package/src/nexus/api/plugins-market.ts +24 -0
  343. package/src/nexus/index.ts +3 -2
  344. package/src/nexus/outbound/router.ts +28 -8
  345. package/src/oauth/claude-pty-maturity.ts +24 -0
  346. package/src/oauth/fallback-chain.ts +27 -14
  347. package/src/onboarding/first-run.ts +3 -0
  348. package/src/ops/overview-collect.ts +345 -0
  349. package/src/ops/overview-seat-era.ts +90 -0
  350. package/src/ops/overview.ts +223 -0
  351. package/src/plugins/maker/market-bundle.ts +9 -0
  352. package/src/plugins/maker/plugin-maker.ts +9 -3
  353. package/src/plugins/maker/wizard-generate.ts +73 -9
  354. package/src/policy/llm-policy.ts +11 -6
  355. package/src/release-loop/checklist-board.ts +27 -0
  356. package/src/release-loop/checklist.ts +38 -0
  357. package/src/release-loop/gate-shards.ts +7 -2
  358. package/src/seat-address/seat-address.ts +1 -0
  359. package/src/seat-loop/seat-loop.ts +174 -20
  360. package/src/seat-loop/seat-report.ts +29 -1
  361. package/src/self-dev/ask-launch-flow.ts +4 -2
  362. package/src/self-dev/ask-launch-io.ts +3 -1
  363. package/src/self-dev/dev-cli.ts +90 -8
  364. package/src/self-dev/dev-pipeline.ts +20 -1
  365. package/src/self-dev/draft-sweep.ts +45 -8
  366. package/src/self-dev/graph-journey-nodes.ts +157 -0
  367. package/src/self-dev/graph-journey-route.ts +105 -0
  368. package/src/self-dev/launch-preflight.ts +43 -3
  369. package/src/self-dev/orchestrate.ts +3 -0
  370. package/src/self-dev/release-path-guard.ts +7 -4
  371. package/src/self-dev/review-provider-fallback.ts +83 -0
  372. package/src/self-dev/run-supervisor.ts +5 -1
  373. package/src/self-implement/frozen-merges.ts +23 -0
  374. package/src/self-implement/goal-author.ts +14 -0
  375. package/src/self-implement/harness-policy-gates.ts +7 -5
  376. package/src/self-implement/heal-intake.ts +3 -0
  377. package/src/self-implement/host-regate.ts +28 -14
  378. package/src/self-implement/l8-merge-queue.ts +80 -8
  379. package/src/self-implement/orchestrator.ts +84 -28
  380. package/src/self-implement/seams.ts +8 -3
  381. package/src/self-implement/self-implement-cli.ts +4 -0
  382. package/src/self-implement/self-implement-runtime.ts +12 -0
  383. package/src/session-runtime/index.ts +5 -3
  384. package/src/shell-env-bootstrap.ts +35 -6
  385. package/src/steward/triage.ts +25 -8
  386. package/src/storage/file-lock.ts +33 -4
  387. package/src/task-agent/card-evidence.ts +6 -0
  388. package/src/task-agent/completion-evidence.ts +31 -3
  389. package/src/task-agent/cover.ts +61 -0
  390. package/src/task-agent/live-moves.ts +126 -3
  391. package/src/task-agent/parent-host.ts +315 -0
  392. package/src/task-agent/shadow.ts +19 -4
  393. package/src/task-agent/task-hand.ts +11 -2
  394. package/src/task-orchestrator/surfaces/pod-command-job.ts +41 -6
  395. package/src/task-orchestrator/surfaces/pod-gate-deps.ts +123 -0
  396. package/src/task-orchestrator/surfaces/pod-pool.ts +138 -15
  397. package/src/task-orchestrator/surfaces/self-implement-pod.ts +39 -6
  398. package/src/task-orchestrator/surfaces/self-implement.ts +7 -0
  399. package/src/telegram-commands.ts +8 -0
  400. package/src/telegram-project-command.ts +6 -0
  401. package/src/telegram.ts +2 -2
  402. package/src/tool-runtime/browser-runtime.ts +87 -3
  403. package/src/tool-runtime/index.ts +1 -0
  404. package/src/typecheck-ratchet.ts +2 -1
  405. package/src/user-config.ts +55 -8
  406. package/src/version/packed-revision.json +1 -1
  407. package/src/workflow-runtime/executor.ts +24 -2
  408. package/src/workflow-runtime/triggers/registry.ts +27 -0
  409. package/src/workflow-runtime/triggers/schedule-source.ts +30 -3
  410. package/apps/pwa/out/_next/static/Bam1YziK2iH9bN4K0utGk/_buildManifest.js +0 -1
  411. package/apps/pwa/out/_next/static/chunks/1307-77fad80cf54477c6.js +0 -1
  412. package/apps/pwa/out/_next/static/chunks/1612-f60c0c93ec773f81.js +0 -1
  413. package/apps/pwa/out/_next/static/chunks/1844-9ec4b1d12f6e80eb.js +0 -1
  414. package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +0 -1
  415. package/apps/pwa/out/_next/static/chunks/1948.15f356b15a625597.js +0 -5
  416. package/apps/pwa/out/_next/static/chunks/2116-6429c188eb846ab8.js +0 -1
  417. package/apps/pwa/out/_next/static/chunks/224-e7be8f762fe74986.js +0 -1
  418. package/apps/pwa/out/_next/static/chunks/2549-89c518a4c8ed4d00.js +0 -1
  419. package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +0 -1
  420. package/apps/pwa/out/_next/static/chunks/3857-995ea9f5ef33d71e.js +0 -1
  421. package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +0 -1
  422. package/apps/pwa/out/_next/static/chunks/5755-ea7ed88eb956f47b.js +0 -1
  423. package/apps/pwa/out/_next/static/chunks/5977-94ed6f78e9814b2b.js +0 -1
  424. package/apps/pwa/out/_next/static/chunks/7748-6e51bfe927019f5c.js +0 -25
  425. package/apps/pwa/out/_next/static/chunks/9247-c1753a059e7bac08.js +0 -1
  426. package/apps/pwa/out/_next/static/chunks/a9f2b54d-33c0689b70ed8cf3.js +0 -1
  427. package/apps/pwa/out/_next/static/chunks/app/bots/page-7a852956c74015a3.js +0 -1
  428. package/apps/pwa/out/_next/static/chunks/app/ceo/page-c5a5c621be0e04ae.js +0 -1
  429. package/apps/pwa/out/_next/static/chunks/app/design-check/page-7c058069c5e7fa63.js +0 -1
  430. package/apps/pwa/out/_next/static/chunks/app/editor/page-dc5da0b8a2edb8a4.js +0 -1
  431. package/apps/pwa/out/_next/static/chunks/app/inside/page-ed4a6f799f005d09.js +0 -1
  432. package/apps/pwa/out/_next/static/chunks/app/intake/page-9cd565005b93aca6.js +0 -1
  433. package/apps/pwa/out/_next/static/chunks/app/layout-55f0f40c6db95f15.js +0 -1
  434. package/apps/pwa/out/_next/static/chunks/app/live/page-474cb5888ea0e8f3.js +0 -1
  435. package/apps/pwa/out/_next/static/chunks/app/market/page-9ca21f9ad3208a61.js +0 -1
  436. package/apps/pwa/out/_next/static/chunks/app/not-found-a9c2cfa8dda922a5.js +0 -1
  437. package/apps/pwa/out/_next/static/chunks/app/observatory/page-6da35144878feb6d.js +0 -1
  438. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-f6055495e9ed7ea8.js +0 -1
  439. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-25204694fa9984a5.js +0 -1
  440. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-a60e035f151d84d3.js +0 -1
  441. package/apps/pwa/out/_next/static/chunks/app/outputs/page-189c587f36874538.js +0 -1
  442. package/apps/pwa/out/_next/static/chunks/app/page-c56634bde705a65d.js +0 -1
  443. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-a8d2fe0c669e0de4.js +0 -1
  444. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-c26fa10aac6c21c4.js +0 -1
  445. package/apps/pwa/out/_next/static/chunks/app/setup/page-68793c6e10331d66.js +0 -1
  446. package/apps/pwa/out/_next/static/chunks/app/showroom/page-d81c07a7b4f618ae.js +0 -1
  447. package/apps/pwa/out/_next/static/chunks/app/tasks/page-be1c4e8697f22360.js +0 -1
  448. package/apps/pwa/out/_next/static/chunks/app/today/page-a4717e2c4bf7a28f.js +0 -1
  449. package/apps/pwa/out/_next/static/chunks/app/trace/page-0fd895fab9b62c1b.js +0 -1
  450. package/apps/pwa/out/_next/static/chunks/app/vault/page-8b6c1454a8e8f306.js +0 -1
  451. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-c6e4364d9de8f717.js +0 -1
  452. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-0506a1e0e4bdb2f5.js +0 -1
  453. package/apps/pwa/out/_next/static/chunks/webpack-c260571513bf43c2.js +0 -1
  454. package/apps/pwa/out/_next/static/css/c23d959d3ee15e57.css +0 -1
  455. /package/apps/pwa/out/_next/static/{Bam1YziK2iH9bN4K0utGk → aBi6EyMtLsrl7J8eH4kF7}/_ssgManifest.js +0 -0
  456. /package/skills/project-onboarding/{skill.md → SKILL.md} +0 -0
@@ -0,0 +1,577 @@
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 { listInstalledPacks, queryInstalledPack } from '../knowledge/query.js';
22
+ import { parsePackIdString } from '../knowledge/kgs/pack.js';
23
+ import { WIZARD_MAX_RETRIES, WIZARD_STEPS, WIZARD_STEP_IDS, recipesYamlFor, type WizardNodeStep } from './steps.js';
24
+
25
+ export interface GraphWizardTurn { role: 'user' | 'assistant'; text: string }
26
+
27
+ export interface GraphWizardRequest {
28
+ prompt: string;
29
+ kind?: GraphKind;
30
+ /** 있으면 prompt 는 «이 그래프를 고치라»는 지시다 — 결과는 고친 «전체» YAML. */
31
+ currentYaml?: string;
32
+ history?: GraphWizardTurn[];
33
+ /** Installed pack selected for a knowledge (RAG) node. */
34
+ packId?: string;
35
+ }
36
+
37
+ export interface GraphWizardResult {
38
+ ok: boolean;
39
+ id: string;
40
+ yaml: string;
41
+ base?: string;
42
+ /** 왜 그 기반을 골랐나 — 키워드 · LLM 재선택. */
43
+ baseReason?: string;
44
+ issues: string[];
45
+ attempts: number;
46
+ summary: string;
47
+ /** 노드 id → 한국어 이름 (YAML 노드 줄 주석에서 읽는다). */
48
+ labels?: Record<string, string>;
49
+ /** 노드 id → 서재 단계(·인자). 하니스만. */
50
+ steps?: Record<string, WizardNodeStep>;
51
+ /** 이 그래프 옆에 둘 recipes.yaml — `elanous graph run` 이 cmd/approval 을 여기서 찾는다. 하니스만. */
52
+ recipes?: string;
53
+ /** 실행 가능성 증명: 그 recipes 로 `graph run --dry-run` 을 걸어 본 결과. 하니스만. */
54
+ dryRun?: { status: string; path: string[] };
55
+ }
56
+
57
+ export interface GraphWizardDeps {
58
+ /** LLM 한 번 부르기. 기본 = 역할 `graph-grow` 해석 ⊕ 설정 폴백 체인(streamLLM). */
59
+ callLLM?: (prompt: string) => Promise<string>;
60
+ /** 템플릿 뿌리(기본 graphs/). */
61
+ graphsDir?: string;
62
+ /** 이미 쓰인 graph_id(핵심 ⊕ 내 그래프). 기본 = graphs/** ⊕ <state>/graphs. */
63
+ existingIds?: () => ReadonlySet<string>;
64
+ maxAttempts?: number;
65
+ /** Installed pack inventory and retrieval seams for isolated generation. */
66
+ packs?: typeof listInstalledPacks;
67
+ searchPack?: typeof queryInstalledPack;
68
+ }
69
+
70
+ export const GRAPH_WIZARD_MAX_ATTEMPTS = 3;
71
+
72
+ /** Inventory used by the editor's pack picker and the wizard's validation. */
73
+ export function wizardInstalledPacks(deps: GraphWizardDeps = {}): Array<{ id: string; title: string }> {
74
+ return (deps.packs ?? listInstalledPacks)();
75
+ }
76
+
77
+ /** 요청 자체가 틀렸다(400) — LLM 실패(502)와 가른다. */
78
+ export class GraphWizardInputError extends Error { override name = 'GraphWizardInputError'; }
79
+ const GRAPH_ID = /^[a-z0-9-]+$/;
80
+
81
+ interface TemplateEntry { id: string; description: string; text: string; runnable: boolean; keywords?: string[] }
82
+
83
+ function walkYaml(dir: string, depth = 0): string[] {
84
+ if (depth > 3 || !existsSync(dir)) return [];
85
+ let names: string[];
86
+ try { names = readdirSync(dir); } catch { return []; }
87
+ return names.flatMap((name) => {
88
+ if (name.startsWith('.') || name === 'overlays' || name === 'catalog') return [];
89
+ const path = join(dir, name);
90
+ let dirLike = false;
91
+ try { dirLike = statSync(path).isDirectory(); } catch { return []; }
92
+ if (dirLike) return walkYaml(path, depth + 1);
93
+ return /\.ya?ml$/.test(name) && !/^recipes\.ya?ml$/.test(name) ? [path] : [];
94
+ });
95
+ }
96
+
97
+ function graphIdOf(text: string): string | undefined {
98
+ try {
99
+ const raw = parseYaml(text) as { graph_id?: unknown; name?: unknown } | null;
100
+ if (raw && typeof raw === 'object') {
101
+ if (typeof raw.graph_id === 'string') return raw.graph_id;
102
+ if (typeof raw.name === 'string') return raw.name;
103
+ }
104
+ } catch { /* not a graph */ }
105
+ return undefined;
106
+ }
107
+
108
+ /** 검증을 통과하는 하니스 템플릿만 «기반» 후보가 된다 — 깨진 표본을 보여주면 깨진 것을 배운다. */
109
+ export function listWizardTemplates(dir = defaultGraphsDir()): TemplateEntry[] {
110
+ // 원형이 맨 앞 — 사람이 말로 시키는 일의 뼈대(운영 템플릿은 개발·출시 루프라 말과 낱말이 안 겹친다).
111
+ const out: TemplateEntry[] = WIZARD_ARCHETYPES.map((a) => ({ id: a.id, description: a.description, text: a.text, runnable: true, keywords: a.keywords }));
112
+ const seen = new Set<string>(out.map((t) => t.id));
113
+ for (const file of walkYaml(dir)) {
114
+ let text: string;
115
+ try { text = readFileSync(file, 'utf8'); } catch { continue; }
116
+ let raw: Record<string, unknown> | null;
117
+ try { raw = parseYaml(text) as Record<string, unknown> | null; } catch { continue; }
118
+ if (!raw || typeof raw.graph_id !== 'string' || seen.has(raw.graph_id)) continue;
119
+ if (!validateGraphYaml('harness', text).ok) continue;
120
+ const loop = (raw.loop ?? {}) as { title?: unknown; description?: unknown };
121
+ const description = [loop.title, loop.description].filter((v): v is string => typeof v === 'string').join(' — ') || raw.graph_id;
122
+ const terminals = Array.isArray(raw.terminal_nodes) ? raw.terminal_nodes : [];
123
+ seen.add(raw.graph_id);
124
+ out.push({ id: raw.graph_id, description: description.slice(0, 160), text, runnable: terminals.every((t) => t === 'done' || t === 'failed') });
125
+ }
126
+ return out;
127
+ }
128
+
129
+ function grams(text: string): Set<string> {
130
+ const lower = text.toLowerCase();
131
+ const out = new Set<string>();
132
+ for (const word of lower.split(/[^\p{L}\p{N}]+/u).filter(Boolean)) {
133
+ if (/^[a-z0-9]+$/.test(word)) { if (word.length > 2) out.add(word); continue; }
134
+ for (let i = 0; i + 1 < word.length; i += 1) out.add(word.slice(i, i + 2));
135
+ }
136
+ return out;
137
+ }
138
+
139
+ export interface BasePick { template: TemplateEntry; reason: string }
140
+
141
+ /**
142
+ * 원형 키워드(낱말이 말 안에 있으면 3점) ⊕ 설명 한글 2-gram 겹침(1점)으로 가장 가까운 «실행 가능한» 템플릿.
143
+ * 동점이면 원형이 앞선다(목록 순서). 0점이면 «모아서 요약해 보내기» 원형. 이유를 함께 낸다.
144
+ */
145
+ export function pickBaseTemplate(prompt: string, templates: readonly TemplateEntry[]): BasePick | undefined {
146
+ const lower = prompt.toLowerCase();
147
+ const wanted = grams(prompt);
148
+ let best: BasePick | undefined;
149
+ let bestScore = 0;
150
+ for (const template of templates) {
151
+ if (!template.runnable) continue;
152
+ const hits = (template.keywords ?? []).filter((k) => lower.includes(k));
153
+ const have = grams(`${template.id} ${template.description}`);
154
+ let overlap = 0;
155
+ for (const g of wanted) if (have.has(g)) overlap += 1;
156
+ const score = hits.length * 3 + overlap;
157
+ if (score > bestScore) {
158
+ bestScore = score;
159
+ best = { template, reason: hits.length ? `키워드 ${hits.slice(0, 5).join('·')}` : `설명 낱말 겹침 ${overlap}` };
160
+ }
161
+ }
162
+ if (best) return best;
163
+ const fallback = templates.find((t) => t.id === WIZARD_ARCHETYPES[0]!.id) ?? templates.find((t) => t.runnable);
164
+ return fallback ? { template: fallback, reason: '겹치는 낱말이 없어 기본 원형' } : undefined;
165
+ }
166
+
167
+ export function existingGraphIds(dirs: readonly string[]): Set<string> {
168
+ const ids = new Set<string>();
169
+ for (const dir of dirs) {
170
+ for (const file of walkYaml(dir)) {
171
+ try { const id = graphIdOf(readFileSync(file, 'utf8')); if (id) ids.add(id); } catch { /* unreadable */ }
172
+ }
173
+ }
174
+ return ids;
175
+ }
176
+
177
+ /** 슬러그 ⊕ 짧은 해시. 기존 id 와 겹치면 접미로 비켜 간다 — 덮어쓰지 않는다. */
178
+ export function wizardGraphId(prompt: string, hint: string | undefined, taken: ReadonlySet<string>): string {
179
+ const slugify = (s: string) => s.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').split('-').filter(Boolean).slice(0, 5).join('-').slice(0, 40).replace(/-+$/, '');
180
+ const stem = slugify(hint ?? '') || slugify(prompt) || 'graph';
181
+ const hash = createHash('sha256').update(prompt).digest('hex').slice(0, 6);
182
+ let id = `${stem}-${hash}`;
183
+ for (let n = 2; taken.has(id); n += 1) id = `${stem}-${hash}-${n}`;
184
+ return id;
185
+ }
186
+
187
+ export const HARNESS_EXAMPLE = `graph_id: example-digest
188
+ loop:
189
+ title: 예시 — 뉴스를 모아 요약하고 승인 뒤 보낸다
190
+ description: 뉴스를 검색해 요약하고 품질을 본 뒤 사람 승인을 받아 텔레그램으로 보낸다
191
+ trigger:
192
+ cron: "0 8 * * *"
193
+ version: 1
194
+ entry_node: collect
195
+ terminal_nodes: [done, failed]
196
+ nodes:
197
+ - { node_id: collect, kind: agent, recipe: 'cmd:collect', max_visits: 1 } # 뉴스 수집 | web-search | 오늘 AI 뉴스
198
+ - { node_id: summarize, kind: agent, recipe: 'cmd:summarize', max_visits: 2 } # 요약 | summarize | 핵심 5줄 한국어 요약
199
+ - { node_id: check, kind: judge, recipe: 'cmd:check', max_visits: 2 } # 요약 품질 판정 | check | 사실에 맞고 5줄 이내
200
+ - { node_id: approve, kind: hitl, recipe: 'approval:approve', max_visits: 1 } # 발송 승인 | approval
201
+ - { node_id: send, kind: agent, recipe: 'cmd:send', max_visits: 1 } # 텔레그램 발송 | telegram-send
202
+ - { node_id: done, kind: gate, recipe: none, max_visits: 1 } # 완료
203
+ - { node_id: failed, kind: gate, recipe: none, max_visits: 1 } # 실패
204
+ edges:
205
+ - { from: collect, on: outcome, map: { ok: summarize, fail: failed } }
206
+ - { from: summarize, on: outcome, map: { ok: check, fail: failed } }
207
+ - { from: check, on: outcome, map: { ok: approve, rework: summarize, fail: failed } }
208
+ - { from: approve, on: outcome, map: { ok: send, fail: failed } }
209
+ - { from: send, on: outcome, map: { ok: done, fail: failed } }
210
+ `;
211
+
212
+ export const WORKFLOW_EXAMPLE = `name: example-digest
213
+ description: 자료를 모아 요약하고 승인 뒤 보낸다
214
+ nodes:
215
+ - id: collect
216
+ bash: "echo collect"
217
+ - id: summarize
218
+ prompt: "다음을 다섯 줄로 요약하라: $collect.output"
219
+ depends_on: [collect]
220
+ - id: approve
221
+ approval:
222
+ message: "요약을 보낼까요?"
223
+ depends_on: [summarize]
224
+ - id: send
225
+ bash: "echo send"
226
+ depends_on: [approve]
227
+ `;
228
+
229
+ function harnessRules(kinds: string): string {
230
+ return [
231
+ '스키마(하니스 실행 그래프 · YAML):',
232
+ '- 최상위: graph_id, loop{title, description, trigger{cron|events}}, version: 1, entry_node, terminal_nodes: [done, failed], nodes, edges.',
233
+ `- nodes[]: { node_id(kebab-case 영문), kind, recipe, max_visits(1 이상 정수) }. kind 는 이것만: ${kinds}.`,
234
+ "- recipe 는 셋 중 하나뿐: 'none'(done·failed 같은 끝 노드), 'cmd:<kebab-id>'(실행 단계), 'approval:<kebab-id>'(사람 승인 — kind: hitl).",
235
+ '- 끝 노드는 정확히 done 과 failed 두 개(kind: gate, recipe: none). 다른 끝 노드를 만들지 마라.',
236
+ '- edges[]: { from, on: outcome, map: { ok: <다음>, fail: failed, <다른 결과>: <노드> } } — 모든 노드가 entry 에서 닿아야 하고, done·failed 외 노드는 나가는 간선이 있어야 한다.',
237
+ '- 되돌아가는 고리(재작업)는 map 의 다른 결과로 앞 노드를 가리키고, 그 노드의 max_visits 를 2~3 으로.',
238
+ '- 다른 키(contract, progress 등)는 쓰지 마라.',
239
+ '',
240
+ '노드 줄 형식(필수): 노드는 한 줄 흐름식 `- { node_id: …, kind: …, recipe: …, max_visits: … }` 으로 쓰고, 줄 끝 주석에 `# <한국어 이름> | <단계> | <인자>` 를 단다.',
241
+ '- 한국어 이름은 사용자가 화면에서 보는 노드 이름이다(예: 뉴스 수집). 끝 노드는 `# 완료` · `# 실패`. 승인 노드는 `# <이름> | approval`.',
242
+ "- cmd: 노드의 <단계> 는 아래 서재 중 하나다. 맞는 것이 없을 때만 custom(외부 연동 · 실행 시 «미구현»으로 실패). 인자에 `|`·`#` 을 쓰지 마라.",
243
+ ...WIZARD_STEPS.map((step) => ` · ${step.id} — ${step.use} (결과: ${step.outcomes.join('/')})`),
244
+ '- 간선 map 키는 그 단계가 내는 결과를 쓴다(예: check → ok/rework, retry-gate → retry/give-up, gh-pr-review → ok/must-fix).',
245
+ '- «실패하면 다시 시도하고 N번 넘으면 알려줘» 패턴(간선을 늘리지 않는다): 재시도는 «단계 안»에서 한다 — 각 cmd 노드 주석 끝에 `| retry=N` 을 붙인다(인자가 없으면 `# 이름 | 단계 | | retry=N`). 재시도 노드·되돌이 간선을 만들지 마라. 알림 노드(notify-me) «하나»만 더하고, 실행 노드들의 `fail: failed` 를 `fail: <알림 노드>` 로 바꾸고, 알림 노드는 `{ ok: failed, fail: failed }`.',
246
+ ].join('\n');
247
+ }
248
+
249
+ /** 워크플로 어휘는 `elanous wf synth` 의 시스템 프롬프트를 «그대로» 재사용한다(노드 변형·본문 모양·예시). 출력 형식만 이 마법사 것을 쓴다. */
250
+ function workflowRules(kinds: string): string {
251
+ return [
252
+ '--- wf synth 어휘(재사용 · 그 안의 «# Output» 절은 무시하고 아래 출력 형식을 따른다) ---',
253
+ WORKFLOW_SYNTH_SYSTEM_PROMPT.trim(),
254
+ '--- 끝 ---',
255
+ '스키마(워크플로 · YAML):',
256
+ '- 최상위: name(kebab-case), description, nodes.',
257
+ `- nodes[]: { id, <종류 키 하나>, depends_on?: [id…] }. 종류: ${kinds}. 흔한 것 = prompt: "<지시>", bash: "<명령>", approval: { message }.`,
258
+ '- 순환 금지 · depends_on 은 선언된 id 만.',
259
+ ].join('\n');
260
+ }
261
+
262
+ function extractYaml(text: string): string {
263
+ const fence = /```(?:ya?ml)?\s*\n([\s\S]*?)```/i.exec(text);
264
+ const body = fence ? fence[1]! : text.split('\n').filter((line) => !/^\s*(BASE|SLUG|SUMMARY)\s*:/.test(line)).join('\n');
265
+ return body.trim() + '\n';
266
+ }
267
+
268
+ function lineValue(text: string, key: string): string | undefined {
269
+ const match = new RegExp(`^\\s*${key}\\s*:\\s*(.+)$`, 'mi').exec(text.split('```')[0] ?? '');
270
+ return match?.[1]?.trim().replace(/^['"`]|['"`]$/g, '') || undefined;
271
+ }
272
+
273
+ /** 선언된 id 줄만 바꾼다 — 나머지 바이트(주석 포함)는 그대로. */
274
+ function forceId(yaml: string, kind: GraphKind, id: string): string {
275
+ const key = kind === 'harness' ? 'graph_id' : 'name';
276
+ const re = new RegExp(`^([ \\t]*${key}:[ \\t]*)([^\\n#]*?)([ \\t]*(?:#.*)?)$`, 'm');
277
+ return re.test(yaml) ? yaml.replace(re, `$1${id}$3`) : `${key}: ${id}\n${yaml}`;
278
+ }
279
+
280
+ function fmtIssue(issue: unknown): string {
281
+ if (typeof issue === 'string') return issue;
282
+ if (issue && typeof issue === 'object') {
283
+ const { path, message } = issue as { path?: unknown; message?: unknown };
284
+ if (typeof message === 'string') return typeof path === 'string' && path ? `${path}: ${message}` : message;
285
+ }
286
+ return JSON.stringify(issue);
287
+ }
288
+
289
+ /** API 검증기 ⊕ (하니스) `elanous graph run` 이 실제로 받는 모양 — 통과해도 못 도는 그래프를 «ok» 라 하지 않는다. */
290
+ export function validateWizardYaml(kind: GraphKind, yaml: string): string[] {
291
+ const checked = validateGraphYaml(kind, yaml);
292
+ const issues = checked.errors.map(fmtIssue);
293
+ if (kind !== 'harness' || issues.length > 0) return issues;
294
+ const raw = parseYaml(yaml) as { terminal_nodes?: unknown; nodes?: Array<{ node_id?: unknown; kind?: unknown; recipe?: unknown }> };
295
+ const terminals = Array.isArray(raw.terminal_nodes) ? raw.terminal_nodes : [];
296
+ for (const t of terminals) if (t !== 'done' && t !== 'failed') issues.push(`terminal_nodes: '${String(t)}' — 끝 노드는 done·failed 만 쓴다`);
297
+ for (const node of raw.nodes ?? []) {
298
+ const recipe = typeof node.recipe === 'string' ? node.recipe : '';
299
+ if (recipe !== 'none' && !/^(cmd|approval):[a-z0-9][a-z0-9-]*$/.test(recipe)) {
300
+ issues.push(`nodes/${String(node.node_id)}/recipe: '${recipe}' — none · cmd:<id> · approval:<id> 중 하나여야 한다`);
301
+ }
302
+ }
303
+ return issues;
304
+ }
305
+
306
+ /** 노드 줄 끝 주석 `# 이름 | 단계 | 인자` 를 읽는다. 흐름식 노드 줄에서만. */
307
+ export function parseNodeAnnotations(yaml: string): Record<string, WizardNodeStep> {
308
+ const out: Record<string, WizardNodeStep> = {};
309
+ for (const line of yaml.split('\n')) {
310
+ const match = /node_id:\s*['"]?([a-z0-9][a-z0-9-]*)['"]?[^#]*\}\s*#\s*(.+)$/.exec(line);
311
+ if (!match) continue;
312
+ const parts = match[2]!.split('|').map((part) => part.trim());
313
+ let retries: number | undefined;
314
+ const retryAt = parts.findIndex((part, index) => index >= 2 && /^retry\s*=\s*\d+$/i.test(part));
315
+ if (retryAt >= 0) retries = Number.parseInt(parts.splice(retryAt, 1)[0]!.split('=')[1]!, 10);
316
+ const [label, step, ...rest] = parts;
317
+ const arg = rest.join('|').trim();
318
+ out[match[1]!] = { ...(label ? { label } : {}), ...(step ? { step } : {}), ...(arg ? { arg } : {}), ...(retries ? { retries } : {}) };
319
+ }
320
+ return out;
321
+ }
322
+
323
+ /** cmd 노드마다 서재 단계가 있는지 — 없으면 «이름만 있는» 노드라 실제로 못 돈다. */
324
+ export function stepIssues(yaml: string, steps: Record<string, WizardNodeStep>): string[] {
325
+ const raw = parseYaml(yaml) as {
326
+ nodes?: Array<{ node_id?: unknown; kind?: unknown; recipe?: unknown }>;
327
+ edges?: Array<{ from?: unknown; to?: unknown; map?: Record<string, unknown> }>;
328
+ };
329
+ const issues: string[] = [];
330
+ const recipeOwner = new Map<string, string>();
331
+ const kindOf = new Map((raw.nodes ?? []).map((n) => [String(n.node_id), String(n.kind)]));
332
+ for (const node of raw.nodes ?? []) {
333
+ const id = String(node.node_id);
334
+ const recipe = typeof node.recipe === 'string' ? node.recipe : '';
335
+ const meta = steps[id];
336
+ // recipes.yaml 은 recipe 이름으로 한 줄 — 두 노드가 같은 이름을 쓰면 둘째 노드의 단계·인자가 사라진다.
337
+ if (recipe !== 'none' && recipeOwner.has(recipe)) issues.push(`nodes/${id}: recipe '${recipe}' 를 ${recipeOwner.get(recipe)} 도 쓴다 — 노드마다 고유한 recipe 이름(보통 cmd:<node_id>)`);
338
+ else recipeOwner.set(recipe, id);
339
+ if ((meta?.retries ?? 0) > WIZARD_MAX_RETRIES) issues.push(`nodes/${id}: retry=${meta!.retries} — 최대 ${WIZARD_MAX_RETRIES}`);
340
+ // 머지는 사람 승인 «뒤»에만 — 들어오는 간선이 전부 hitl 노드에서 와야 한다.
341
+ if (meta?.step === 'gh-pr-merge') {
342
+ const sources = (raw.edges ?? []).filter((e) => e.to === id || Object.values(e.map ?? {}).includes(id)).map((e) => String(e.from));
343
+ if (sources.length === 0 || sources.some((from) => kindOf.get(from) !== 'hitl')) issues.push(`nodes/${id}: PR 머지 앞에는 사람 승인(kind: hitl) 노드만 올 수 있다`);
344
+ }
345
+ if (!meta?.label) issues.push(`nodes/${id}: 줄 끝 주석 \`# <한국어 이름> | …\` 이 없다`);
346
+ if (recipe.startsWith('cmd:') && !(meta?.step && WIZARD_STEP_IDS.has(meta.step))) {
347
+ issues.push(`nodes/${id}: cmd 노드의 단계가 서재(${[...WIZARD_STEP_IDS].join(', ')})에 없다 — 주석 \`# 이름 | <단계> | <인자>\``);
348
+ }
349
+ }
350
+ return issues;
351
+ }
352
+
353
+ /** 실제 recipes 로 `graph run --dry-run` 을 걸어 «도는가»를 잰다(명령 실행 0). */
354
+ export async function dryRunWizardGraph(yaml: string, recipes: string): Promise<{ status: string; path: string[]; error?: string }> {
355
+ const { mkdtempSync, writeFileSync, rmSync } = await import('node:fs');
356
+ const { tmpdir } = await import('node:os');
357
+ const { runGraph } = await import('../graph-runner/runner.js');
358
+ const dir = mkdtempSync(join(tmpdir(), 'elanous-graph-wizard-'));
359
+ try {
360
+ writeFileSync(join(dir, 'graph.yaml'), yaml);
361
+ writeFileSync(join(dir, 'recipes.yaml'), recipes);
362
+ const state = await runGraph(join(dir, 'graph.yaml'), { dryRun: true, deps: { root: join(dir, 'state'), log: () => {} } });
363
+ return { status: state.status, path: state.path };
364
+ } catch (error) {
365
+ return { status: 'error', path: [], error: error instanceof Error ? error.message : String(error) };
366
+ } finally { rmSync(dir, { recursive: true, force: true }); }
367
+ }
368
+
369
+ interface Shape { nodes: string[]; labels: Record<string, string>; retries: Record<string, number>; edges: Map<string, string> }
370
+ function shapeOf(kind: GraphKind, yaml: string | undefined): Shape {
371
+ const empty: Shape = { nodes: [], labels: {}, retries: {}, edges: new Map() };
372
+ if (!yaml) return empty;
373
+ try {
374
+ const raw = parseYaml(yaml) as Record<string, unknown>;
375
+ const nodes = Array.isArray(raw.nodes) ? raw.nodes as Array<Record<string, unknown>> : [];
376
+ if (kind === 'workflow') {
377
+ const edges = new Map<string, string>();
378
+ 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));
379
+ return { ...empty, nodes: nodes.map((n) => String(n.id)), edges };
380
+ }
381
+ // 간선 = (from, 결과) → to. 같은 칸의 목적지만 바뀌면 «재연결»이지 새 간선이 아니다.
382
+ const edges = new Map<string, string>();
383
+ for (const e of Array.isArray(raw.edges) ? raw.edges as Array<Record<string, unknown>> : []) {
384
+ if (typeof e.to === 'string') { edges.set(`${String(e.from)}:*`, e.to); continue; }
385
+ const map = e.map && typeof e.map === 'object' ? e.map as Record<string, unknown> : {};
386
+ for (const [outcome, to] of Object.entries(map)) edges.set(`${String(e.from)}:${outcome}`, String(to));
387
+ }
388
+ const notes = parseNodeAnnotations(yaml);
389
+ return {
390
+ nodes: nodes.map((n) => String(n.node_id)), edges,
391
+ labels: Object.fromEntries(Object.entries(notes).filter(([, v]) => v.label).map(([k, v]) => [k, v.label!])),
392
+ retries: Object.fromEntries(Object.entries(notes).filter(([, v]) => v.retries).map(([k, v]) => [k, v.retries!])),
393
+ };
394
+ } catch { return empty; }
395
+ }
396
+
397
+ /** 결정적 변경 요약 — LLM 의 한 줄 요약이 없거나 틀려도 «무엇이 바뀌었나»는 센 값으로 남는다. 노드 이름은 한국어 라벨. */
398
+ export function summarizeChange(kind: GraphKind, before: string | undefined, after: string): string {
399
+ const a = shapeOf(kind, before);
400
+ const b = shapeOf(kind, after);
401
+ const name = (id: string) => b.labels[id] ?? a.labels[id] ?? id;
402
+ if (!before) return `새 그래프: 노드 ${b.nodes.length}개 (${b.nodes.map(name).join(' → ')}) · 간선 ${b.edges.size}개`;
403
+ const added = b.nodes.filter((n) => !a.nodes.includes(n));
404
+ const removed = a.nodes.filter((n) => !b.nodes.includes(n));
405
+ let edgesAdded = 0;
406
+ let rewired = 0;
407
+ for (const [key, to] of b.edges) {
408
+ if (!a.edges.has(key)) edgesAdded += 1;
409
+ else if (a.edges.get(key) !== to) rewired += 1;
410
+ }
411
+ const edgesRemoved = [...a.edges.keys()].filter((key) => !b.edges.has(key)).length;
412
+ const retryChanged = b.nodes.filter((n) => (b.retries[n] ?? 0) !== (a.retries[n] ?? 0));
413
+ const parts: string[] = [];
414
+ if (added.length) parts.push(`노드 ${added.length}개 추가: ${added.map(name).join(' → ')}`);
415
+ if (removed.length) parts.push(`노드 ${removed.length}개 삭제: ${removed.map(name).join(', ')}`);
416
+ if (edgesAdded) parts.push(`간선 ${edgesAdded}개 추가`);
417
+ if (edgesRemoved) parts.push(`간선 ${edgesRemoved}개 삭제`);
418
+ if (rewired) parts.push(`간선 ${rewired}개 재연결`);
419
+ if (retryChanged.length) parts.push(`재시도 설정 ${retryChanged.length}개 노드(${retryChanged.map((n) => `${name(n)} ${b.retries[n] ?? 0}회`).join(', ')})`);
420
+ return parts.length ? parts.join(' · ') : '구조 변경 없음';
421
+ }
422
+
423
+ async function defaultCallLLM(prompt: string): Promise<string> {
424
+ const [{ PROVIDERS, streamLLM }, { getUserConfig }, { graphGrowthLlmOptions }] = await Promise.all([
425
+ import('../llm.js'), import('../user-config.js'), import('../graph-runner/graph-grow-llm.js'),
426
+ ]);
427
+ const selected = graphGrowthLlmOptions(getUserConfig());
428
+ return streamLLM([{ role: 'user', content: prompt }], () => {}, {
429
+ ...(selected.model ? { model: selected.model, provider: PROVIDERS[selected.provider!] } : {}),
430
+ reasoningEffort: 'low',
431
+ });
432
+ }
433
+
434
+ function buildPrompt(input: {
435
+ req: GraphWizardRequest; kind: GraphKind; id: string; base?: TemplateEntry; templates: readonly TemplateEntry[];
436
+ previous?: { yaml: string; issues: string[] };
437
+ evidence?: string;
438
+ }): string {
439
+ const { req, kind, id, base, templates, previous, evidence } = input;
440
+ const kinds = listNodeKinds(kind).map((k) => k.kind).join(', ');
441
+ const editing = typeof req.currentYaml === 'string' && req.currentYaml.trim() !== '';
442
+ const lines: string[] = [
443
+ '너는 엘라누스 «그래프 마법사»다. 사용자의 말을 실행 그래프 YAML 로 짓는다.',
444
+ kind === 'harness' ? harnessRules(kinds) : workflowRules(kinds),
445
+ `- ${kind === 'harness' ? 'graph_id' : 'name'} 는 정확히 "${id}" 로 쓴다.`,
446
+ '- 노드 id 는 사용자의 업무 단계를 드러내는 짧은 영문 kebab-case(예: collect-news, summarize, send-telegram). 단계 수는 3~10개.',
447
+ '- 사용자가 말한 것만 짓는다. 승인(hitl)·품질 판정(check)·재시도·알림은 사용자가 말했을 때만 넣는다 — 예외는 PR 머지·결제·공개 게시(외부 공개) 앞의 승인뿐이다. 텔레그램 발송·나에게 알림 앞에는 말하지 않은 승인을 넣지 마라.',
448
+ '',
449
+ '예시(사용자가 «품질을 보고, 내가 승인하면 보내»라고 «말했을 때»의 모양 — 말하지 않은 단계는 넣지 않는다):',
450
+ '```yaml', kind === 'harness' ? HARNESS_EXAMPLE.trim() : WORKFLOW_EXAMPLE.trim(), '```',
451
+ ];
452
+ if (kind === 'harness' && !editing) {
453
+ lines.push('', '기존 템플릿 목록(id — 설명):', ...templates.filter((t) => t.runnable).slice(0, 30).map((t) => `- ${t.id} — ${t.description}`));
454
+ if (base) lines.push('', `가장 가까운 템플릿(${base.id}) — 구조를 참고하라:`, '```yaml', base.text.trim(), '```');
455
+ }
456
+ if (req.history?.length) {
457
+ lines.push('', '지금까지의 대화:', ...req.history.slice(-10).map((t) => `${t.role === 'user' ? '사용자' : '마법사'}: ${t.text.slice(0, 500)}`));
458
+ }
459
+ if (editing) {
460
+ lines.push('', '지금 그래프(이것을 고친다 — 바뀌지 않는 노드의 id 는 그대로 둔다):', '```yaml', req.currentYaml!.trim(), '```',
461
+ '', `사용자의 수정 지시: ${req.prompt}`,
462
+ '최소 변경 규칙: 지시가 요구하는 노드·간선만 더하거나 바꾼다. 나머지 노드 줄(id·kind·recipe·주석)과 간선은 글자 그대로 둔다. id 를 바꾸지 마라. loop.title·description 은 바뀐 내용만 반영한다.',
463
+ '고친 «전체» YAML 을 돌려준다.');
464
+ } else {
465
+ lines.push('', `사용자의 말: ${req.prompt}`);
466
+ }
467
+ if (evidence) lines.push('', '선택한 설치 팩의 검색 근거:', evidence,
468
+ `지식(RAG) 노드를 cmd 단계로 넣고 # 지식 검색 | knowledge-rag | ${req.packId} 주석을 붙인다. 다음 산출 단계는 이 노드의 근거를 사용한다. 실행 입력 input.query 가 있으면 질문으로 검색하고 없으면 설치 팩 전체 색인에서 가져온다. 근거에 없는 사실을 만들지 마라.`);
469
+ if (previous) {
470
+ lines.push('', '직전 시도가 검증에 실패했다. 아래 문제를 «전부» 고쳐 전체 YAML 을 다시 써라:', ...previous.issues.map((i) => `- ${i}`),
471
+ '직전 YAML:', '```yaml', previous.yaml.trim(), '```');
472
+ }
473
+ lines.push('', '출력 형식(이 순서 · 다른 설명 금지):',
474
+ ...(editing || kind !== 'harness' ? [] : ['BASE: <참고한 템플릿 id 또는 none>']),
475
+ 'SLUG: <이 그래프를 나타내는 영문 kebab-case 2~4 낱말>',
476
+ 'SUMMARY: <무엇을 만들었/바꿨는지 한국어 한 줄 — 예: 노드 2개 추가: 요약 → 텔레그램 발송 · 간선 2개>',
477
+ '```yaml', '<전체 YAML>', '```');
478
+ return lines.join('\n');
479
+ }
480
+
481
+ export async function generateGraphFromPrompt(req: GraphWizardRequest, deps: GraphWizardDeps = {}): Promise<GraphWizardResult> {
482
+ const kind: GraphKind = req.kind === 'workflow' ? 'workflow' : 'harness';
483
+ const prompt = typeof req.prompt === 'string' ? req.prompt.trim() : '';
484
+ if (!prompt) throw new GraphWizardInputError('prompt is required');
485
+ if (req.packId !== undefined && kind !== 'harness') throw new GraphWizardInputError('pack selection requires a harness graph');
486
+ let evidence: string | undefined;
487
+ if (req.packId !== undefined) {
488
+ if (!parsePackIdString(req.packId) || !wizardInstalledPacks(deps).some(pack => pack.id === req.packId))
489
+ throw new GraphWizardInputError(`pack not installed: ${req.packId}`);
490
+ const cards = (deps.searchPack ?? queryInstalledPack)(req.packId, prompt);
491
+ if (!cards.length) throw new GraphWizardInputError(`no knowledge found in ${req.packId} for: ${prompt}`);
492
+ evidence = cards.slice(0, 5).map(card => `[${card.ref}] ${card.title}: ${card.body.slice(0, 1200)}`).join('\n');
493
+ }
494
+ const editing = typeof req.currentYaml === 'string' && req.currentYaml.trim() !== '';
495
+ const graphsDir = deps.graphsDir ?? defaultGraphsDir();
496
+ const templates = kind === 'harness' ? listWizardTemplates(graphsDir) : [];
497
+ const pick = kind === 'harness' && !editing ? pickBaseTemplate(prompt, templates) : undefined;
498
+ const keywordBase = pick?.template;
499
+ let baseReason = pick?.reason;
500
+ const taken = deps.existingIds?.() ?? existingGraphIds([graphsDir, join((await import('../autopilot/state-paths.js')).elanousStateRoot(), 'graphs')]);
501
+ // 편집이면 지금 그래프의 id 를 지킨다 — 캔버스와 저장 경로가 그 id 를 쥐고 있다.
502
+ const currentId = editing ? graphIdOf(req.currentYaml!) : undefined;
503
+ // 편집인데 id 를 지킬 수 없으면(없음·형식 밖) 새 id 로 바꿔 «다른 그래프»를 돌려주지 않고 거절한다.
504
+ if (editing && !(currentId && GRAPH_ID.test(currentId))) {
505
+ debug.log('graph.wizard', 'rejected', { reason: 'current-id-invalid', kind });
506
+ throw new GraphWizardInputError(`currentYaml has no valid ${kind === 'harness' ? 'graph_id' : 'name'} ([a-z0-9-])`);
507
+ }
508
+ let id = editing ? currentId! : wizardGraphId(prompt, undefined, taken);
509
+ const callLLM = deps.callLLM ?? defaultCallLLM;
510
+ const maxAttempts = Math.max(1, deps.maxAttempts ?? GRAPH_WIZARD_MAX_ATTEMPTS);
511
+ let base = keywordBase?.id;
512
+ let yaml = '';
513
+ let issues: string[] = [];
514
+ let llmSummary: string | undefined;
515
+ let attempts = 0;
516
+ let steps: Record<string, WizardNodeStep> = {};
517
+ let recipes: string | undefined;
518
+ let dryRun: { status: string; path: string[] } | undefined;
519
+ const startedAt = Date.now();
520
+ debug.log('graph.wizard', 'start', { kind, editing, base, id, prompt: prompt.slice(0, 300), historyTurns: req.history?.length ?? 0 });
521
+ for (attempts = 1; attempts <= maxAttempts; attempts += 1) {
522
+ let text: string;
523
+ try {
524
+ text = await callLLM(buildPrompt({ req: { ...req, prompt }, kind, id, base: keywordBase, templates, evidence,
525
+ ...(attempts > 1 ? { previous: { yaml, issues } } : {}) }));
526
+ } catch (error) {
527
+ debug.log('graph.wizard', 'error', { id, kind, attempt: attempts, base, message: error instanceof Error ? error.message : String(error) });
528
+ throw error;
529
+ }
530
+ if (attempts === 1 && !editing) {
531
+ const picked = lineValue(text, 'BASE');
532
+ if (picked && picked !== base && templates.some((t) => t.id === picked)) { base = picked; baseReason = `LLM 재선택 (키워드 후보 ${keywordBase?.id ?? '-'})`; }
533
+ const slug = lineValue(text, 'SLUG');
534
+ if (slug && !currentId) id = wizardGraphId(prompt, slug, taken);
535
+ }
536
+ llmSummary = lineValue(text, 'SUMMARY') ?? llmSummary;
537
+ yaml = forceId(extractYaml(text), kind, id);
538
+ try { issues = validateWizardYaml(kind, yaml); }
539
+ catch (error) { issues = [`검증 실패: ${error instanceof Error ? error.message : String(error)}`]; }
540
+ steps = kind === 'harness' ? parseNodeAnnotations(yaml) : {};
541
+ if (req.packId && issues.length === 0) {
542
+ const raw = parseYaml(yaml) as { nodes?: Array<{ node_id: string; recipe: string }>; edges?: Array<{ from: string; map?: Record<string, string> }> };
543
+ const rag = (raw.nodes ?? []).find(node => node.recipe.startsWith('cmd:') && steps[node.node_id]?.step === 'knowledge-rag' && steps[node.node_id]?.arg === req.packId);
544
+ if (!rag || !(raw.edges ?? []).some(edge => edge.from === rag.node_id &&
545
+ typeof edge.map?.ok === 'string' && !['done', 'failed'].includes(edge.map.ok) &&
546
+ (raw.nodes ?? []).some(node => node.node_id === edge.map!.ok && node.recipe.startsWith('cmd:'))))
547
+ issues.push(`선택한 팩 ${req.packId} 의 knowledge-rag 노드를 산출 단계에 연결하라`);
548
+ }
549
+ recipes = undefined;
550
+ dryRun = undefined;
551
+ if (kind === 'harness' && issues.length === 0) {
552
+ issues = stepIssues(yaml, steps);
553
+ const nodes = ((parseYaml(yaml) as { nodes?: Array<{ node_id?: unknown; recipe?: unknown }> }).nodes ?? [])
554
+ .map((n) => ({ nodeId: String(n.node_id), recipe: typeof n.recipe === 'string' ? n.recipe : '' }));
555
+ recipes = recipesYamlFor(nodes, steps);
556
+ const walked = await dryRunWizardGraph(yaml, recipes);
557
+ dryRun = { status: walked.status, path: walked.path };
558
+ if (walked.status !== 'done') issues.push(`graph run --dry-run: ${walked.status}${walked.error ? ` — ${walked.error}` : ''} (경로 ${walked.path.join(' → ')})`);
559
+ }
560
+ debug.log('graph.wizard', 'attempt', { id, attempt: attempts, base, ok: issues.length === 0, issues: issues.slice(0, 10) });
561
+ if (issues.length === 0) break;
562
+ }
563
+ attempts = Math.min(attempts, maxAttempts);
564
+ const ok = issues.length === 0;
565
+ const counted = summarizeChange(kind, editing ? req.currentYaml : undefined, yaml);
566
+ const summary = llmSummary ? `${llmSummary} (${counted})` : counted;
567
+ 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 });
568
+ const labels = Object.fromEntries(Object.entries(steps).filter(([, v]) => v.label).map(([k, v]) => [k, v.label!]));
569
+ return { ok, id, yaml, ...(base ? { base } : {}), ...(base && baseReason ? { baseReason } : {}), issues, attempts, summary, labels, steps, ...(recipes ? { recipes } : {}), ...(dryRun ? { dryRun } : {}) };
570
+ }
571
+
572
+ /** v1 이름 유지 — 생성 YAML 의 노드 주석(단계·인자)으로 실제 recipes.yaml 을 낸다(v1 의 «미구현» 자리표시를 대체). */
573
+ export function stubRecipesFor(yaml: string): string {
574
+ const raw = parseYaml(yaml) as { nodes?: Array<{ node_id?: unknown; recipe?: unknown }> };
575
+ const nodes = (raw.nodes ?? []).map((n) => ({ nodeId: String(n.node_id), recipe: typeof n.recipe === 'string' ? n.recipe : '' }));
576
+ return recipesYamlFor(nodes, parseNodeAnnotations(yaml));
577
+ }