elanous 0.2.19 → 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 (509) 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/{4323.c5dfb6f503304775.js → 1268.0840ff91e083fcbd.js} +1 -1
  10. package/apps/pwa/out/_next/static/chunks/1307-c97eba9fb72a9a5e.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/1358d292.5c87f12cad6eaa91.js +122 -0
  12. package/apps/pwa/out/_next/static/chunks/1438.27a1a7b8f2308004.js +149 -0
  13. package/apps/pwa/out/_next/static/chunks/1442.02d85cd88f4c1eac.js +79 -0
  14. package/apps/pwa/out/_next/static/chunks/1501-1aad662d82ba43f7.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/1584.9e2dd53da6e6d4b1.js +24 -0
  16. package/apps/pwa/out/_next/static/chunks/1586.b989b0614afb77e6.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/1609.2ad355062870151f.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/1612-f6df0fc0eed7157b.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/1923.37f1d5fe7ebe3860.js +27 -0
  21. package/apps/pwa/out/_next/static/chunks/1948.5f35b39eef222a94.js +5 -0
  22. package/apps/pwa/out/_next/static/chunks/195-fb1d7690bf30bab4.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/1986-33e13d648a9df765.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/1993.3fd074a2a246234e.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/{2977-e38442e86289f1d3.js → 211-9c9090c59e2c5907.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/2140.d2daf9ee96e295d1.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/2212.0359851457473efc.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/224.583e562365a52f74.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/2328.0f37465646bafa7d.js +36 -0
  31. package/apps/pwa/out/_next/static/chunks/2372.bf71372184991937.js +286 -0
  32. package/apps/pwa/out/_next/static/chunks/2489-ff55a35b202c0f94.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/2537-6c289376caa88ca1.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/2549-a8515c86f9e8cb96.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/2655.72cc6a97276c5acc.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/2699-325cc923ba7b8631.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/2743-ef6e8c3ced74cce5.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/2840-5458ad50c49cac6a.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/2856.f75d5baef7f49b74.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/3344.0de4b0ec5a454b7a.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/335.a13f339e97893437.js +94 -0
  45. package/apps/pwa/out/_next/static/chunks/3394.646348f34b2500be.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/3443.6276909b53d72d53.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/3495.abd4254d808bf2dc.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/3522-1de1319d028ac89c.js +25 -0
  49. package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/3721.ba5b82db0d9a5572.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/3857-995ea9f5ef33d71e.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/3919.9ff7dc25afefee33.js +74 -0
  54. package/apps/pwa/out/_next/static/chunks/3998.bd9a69d9b50e9bd2.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/4003.ab6db1dc266576c9.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/4144-fad095b48a1862ee.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/4216.e8ac94b9fe6b96ab.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/4327.c44dc2b55cb43ee5.js +34 -0
  59. package/apps/pwa/out/_next/static/chunks/4401.94257d497b6d26bc.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/4559.17ca2cdbaa8d414f.js +115 -0
  61. package/apps/pwa/out/_next/static/chunks/4565-3d7e3fdeb43686a8.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/4588.ad3364cb7fb97ada.js +86 -0
  63. package/apps/pwa/out/_next/static/chunks/4706.97e734bd4c9816ca.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/4795-f372490b178583fc.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/4982.6e44352f751b9a1b.js +77 -0
  66. package/apps/pwa/out/_next/static/chunks/5342.49c5c5a530e62b50.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/5360-08a80abdf3a4da99.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/5431.66c58ab1327ee0b7.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/5717.bcfcb96e701b82e0.js +90 -0
  70. package/apps/pwa/out/_next/static/chunks/5755-30d102586789bf7f.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/5957.3f1a4012388dd962.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/5977-59fedea50d13c762.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/602.9bf02ac8e9502598.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/6020.3a1b347e2d2dfacc.js +8 -0
  75. package/apps/pwa/out/_next/static/chunks/{4847.56f5bc454fbcc827.js → 6110.1297d4093fdfbb0d.js} +2 -2
  76. package/apps/pwa/out/_next/static/chunks/6158.566c890adc06a459.js +2 -0
  77. package/apps/pwa/out/_next/static/chunks/6375.fce1d174ba6d0324.js +64 -0
  78. package/apps/pwa/out/_next/static/chunks/6546.92a828aef441271f.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/658.957fdb0a8c6c6c8e.js +287 -0
  80. package/apps/pwa/out/_next/static/chunks/6706.323590aabb8b1293.js +226 -0
  81. package/apps/pwa/out/_next/static/chunks/6747.b67947f293a4c02d.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/6767.8e6c5f6e744fcd9f.js +38 -0
  83. package/apps/pwa/out/_next/static/chunks/6868.000b24a3045c5574.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/6877-74399f69c7da1433.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/6946.bba74e9c96050f30.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/7033.a558f1b2f6912a53.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/7065.46029c26c53eac50.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/7087.6fae577d09249bc1.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/7175-350f4ce2873bc038.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/722.de8d9801e0f6d414.js +41 -0
  91. package/apps/pwa/out/_next/static/chunks/{1470.98a6ab61ccb4131c.js → 726.83532d69452ee24b.js} +1 -1
  92. package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/{6062-73e68343a9814f6a.js → 7327-2d69fb321ba73389.js} +1 -1
  94. package/apps/pwa/out/_next/static/chunks/7497.57981e9d7a75a52a.js +24 -0
  95. package/apps/pwa/out/_next/static/chunks/7630.2b9a68659053ff4e.js +193 -0
  96. package/apps/pwa/out/_next/static/chunks/7672.4af122ad892117c7.js +1 -0
  97. package/apps/pwa/out/_next/static/chunks/771.6ab889a233e7dae7.js +1 -0
  98. package/apps/pwa/out/_next/static/chunks/7797-df5aa0381fdc078f.js +1 -0
  99. package/apps/pwa/out/_next/static/chunks/7899.597af4a9996c907f.js +1 -0
  100. package/apps/pwa/out/_next/static/chunks/79b78388.663188133449d707.js +1 -0
  101. package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +1 -0
  102. package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/8096.f7da0776cfde6300.js +1 -0
  104. package/apps/pwa/out/_next/static/chunks/8194.484f51e273d9abd2.js +217 -0
  105. package/apps/pwa/out/_next/static/chunks/8401-35e94e9091f5eb8d.js +1 -0
  106. package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +1 -0
  107. package/apps/pwa/out/_next/static/chunks/8695-846034ce44852c70.js +1 -0
  108. package/apps/pwa/out/_next/static/chunks/8769.815ca69879f608b4.js +104 -0
  109. package/apps/pwa/out/_next/static/chunks/8857-f1762140ef9a96b1.js +1 -0
  110. package/apps/pwa/out/_next/static/chunks/88729267.9c6c000157c83f2f.js +62 -0
  111. package/apps/pwa/out/_next/static/chunks/8f835b4f.048eeb02299e3e12.js +1 -0
  112. package/apps/pwa/out/_next/static/chunks/9068.5f2764fccf86c1b2.js +93 -0
  113. package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +1 -0
  114. package/apps/pwa/out/_next/static/chunks/9103-60fbef33e66f0a46.js +1 -0
  115. package/apps/pwa/out/_next/static/chunks/9247-7581825d511a64c0.js +1 -0
  116. package/apps/pwa/out/_next/static/chunks/{7647.2fcd30d894d5ee30.js → 935.4ddaa99fc1d1126c.js} +1 -1
  117. package/apps/pwa/out/_next/static/chunks/9451.ebfb0dcc9c02250f.js +23 -0
  118. package/apps/pwa/out/_next/static/chunks/9497-b0e758d1cd4ae8d7.js +1 -0
  119. package/apps/pwa/out/_next/static/chunks/9559.e648369b1d57cf9a.js +62 -0
  120. package/apps/pwa/out/_next/static/chunks/9832.2ab2e818013574c7.js +1 -0
  121. package/apps/pwa/out/_next/static/chunks/a9f2b54d-40c16bd0efdb04b8.js +1 -0
  122. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +1 -0
  123. package/apps/pwa/out/_next/static/chunks/app/approvals/page-937c92292a4ab4f0.js +1 -0
  124. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-d781be3e3a9cfd74.js +1 -0
  125. package/apps/pwa/out/_next/static/chunks/app/board/page-b0a80997903852d8.js +1 -0
  126. package/apps/pwa/out/_next/static/chunks/app/botlab/page-2b1263165630d9c8.js +1 -0
  127. package/apps/pwa/out/_next/static/chunks/app/bots/page-ea998f7f5d156bea.js +1 -0
  128. package/apps/pwa/out/_next/static/chunks/app/ceo/page-33c328b64ca508f8.js +1 -0
  129. package/apps/pwa/out/_next/static/chunks/app/chat/page-e2d6bed48ac20120.js +1 -0
  130. package/apps/pwa/out/_next/static/chunks/app/consult/page-111cabe4d1fd69ae.js +1 -0
  131. package/apps/pwa/out/_next/static/chunks/app/control/page-544dd4bd4847eeb7.js +1 -0
  132. package/apps/pwa/out/_next/static/chunks/app/decisions/page-3ee191319bf97d87.js +1 -0
  133. package/apps/pwa/out/_next/static/chunks/app/design-check/page-e4bcb2c503dab04b.js +1 -0
  134. package/apps/pwa/out/_next/static/chunks/app/editor/page-300897dcbbd5ed76.js +1 -0
  135. package/apps/pwa/out/_next/static/chunks/app/exec/page-a449f1ed472b81c2.js +1 -0
  136. package/apps/pwa/out/_next/static/chunks/app/field/page-9045094d6d3b5cd0.js +1 -0
  137. package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +1 -0
  138. package/apps/pwa/out/_next/static/chunks/app/inside/page-c06673596849ef45.js +1 -0
  139. package/apps/pwa/out/_next/static/chunks/app/intake/page-103fa3191c1b2e08.js +1 -0
  140. package/apps/pwa/out/_next/static/chunks/app/layout-9f81233f29dcd409.js +1 -0
  141. package/apps/pwa/out/_next/static/chunks/app/live/{page-b70cd005d9169e25.js → page-be8b4615c919e36c.js} +1 -1
  142. package/apps/pwa/out/_next/static/chunks/app/live-run/page-c685b104e0dfa609.js +1 -0
  143. package/apps/pwa/out/_next/static/chunks/app/loops/page-8d693331cfbb6add.js +1 -0
  144. package/apps/pwa/out/_next/static/chunks/app/market/page-cef4f30a4c22fae1.js +1 -0
  145. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66ea1b54fc83fa15.js +1 -0
  146. package/apps/pwa/out/_next/static/chunks/app/morning/page-7a12f61be4a579d1.js +1 -0
  147. package/apps/pwa/out/_next/static/chunks/app/not-found-c85b0589591a99c9.js +1 -0
  148. package/apps/pwa/out/_next/static/chunks/app/observatory/page-725a4da02dd129eb.js +1 -0
  149. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-8cc707bd09a497d9.js +1 -0
  150. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-90b55be1f15ce0e8.js +1 -0
  151. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cb16606e08be65db.js +1 -0
  152. package/apps/pwa/out/_next/static/chunks/app/outputs/page-16dc64b74ed6a40a.js +1 -0
  153. package/apps/pwa/out/_next/static/chunks/app/page-21bec4afeb2f0917.js +1 -0
  154. package/apps/pwa/out/_next/static/chunks/app/reflection/page-dc39ee7a36d26385.js +1 -0
  155. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-84ae759f76053672.js +1 -0
  156. package/apps/pwa/out/_next/static/chunks/app/sessions/page-3db58ddb61fe2be9.js +1 -0
  157. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-c5315e30f3528dc4.js +1 -0
  158. package/apps/pwa/out/_next/static/chunks/app/settings/page-2fe368efb6de3b6a.js +1 -0
  159. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-8d04132dd0ac5e10.js +1 -0
  160. package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +1 -0
  161. package/apps/pwa/out/_next/static/chunks/app/setup/page-f23484de467c9ea3.js +1 -0
  162. package/apps/pwa/out/_next/static/chunks/app/share/page-c69762987b751174.js +1 -0
  163. package/apps/pwa/out/_next/static/chunks/app/showroom/page-5456692bda39aafb.js +1 -0
  164. package/apps/pwa/out/_next/static/chunks/app/tasks/page-d75f7cd65845ce98.js +1 -0
  165. package/apps/pwa/out/_next/static/chunks/app/term/page-5603f5e568b25ce3.js +1 -0
  166. package/apps/pwa/out/_next/static/chunks/app/today/page-d2cc0f4e89f42028.js +1 -0
  167. package/apps/pwa/out/_next/static/chunks/app/trace/page-a6eb73b8a4840f1c.js +1 -0
  168. package/apps/pwa/out/_next/static/chunks/app/vault/page-c0a3f4d10edb83fa.js +1 -0
  169. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-6c0b88068c143f15.js +1 -0
  170. package/apps/pwa/out/_next/static/chunks/app/workflows/page-7f8d7fcdf058a845.js +1 -0
  171. package/apps/pwa/out/_next/static/chunks/app/workspace/page-22163556cd38bc88.js +1 -0
  172. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-141436fd26dd56fc.js +1 -0
  173. package/apps/pwa/out/_next/static/chunks/d70879db.c66c56a95a91efc6.js +66 -0
  174. package/apps/pwa/out/_next/static/chunks/{8046cadf-211ca3c0e00f2b72.js → f6231284-45726550aba08fca.js} +1 -1
  175. package/apps/pwa/out/_next/static/chunks/{2ea96958.e8c7e112de50b067.js → f6b7c9ba.64a895b8b2fedbdc.js} +1 -1
  176. package/apps/pwa/out/_next/static/chunks/f8626309.a4e5d362b1d55e59.js +1 -0
  177. package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +1 -0
  178. package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +1 -0
  179. package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +1 -0
  180. package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +1 -0
  181. package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +1 -0
  182. package/apps/pwa/out/_next/static/chunks/webpack-f2b4016b9b981bc8.js +1 -0
  183. package/apps/pwa/out/_next/static/css/{d3427996da11e480.css → b9d8151e48998417.css} +1 -1
  184. package/apps/pwa/out/_next/static/css/e81df11490b4413b.css +1 -0
  185. package/apps/pwa/out/approvals/index.html +1 -1
  186. package/apps/pwa/out/approvals/index.txt +23 -23
  187. package/apps/pwa/out/autopilot/index.html +1 -1
  188. package/apps/pwa/out/autopilot/index.txt +23 -23
  189. package/apps/pwa/out/board/index.html +1 -1
  190. package/apps/pwa/out/board/index.txt +23 -23
  191. package/apps/pwa/out/botlab/index.html +1 -1
  192. package/apps/pwa/out/botlab/index.txt +22 -22
  193. package/apps/pwa/out/bots/index.html +1 -1
  194. package/apps/pwa/out/bots/index.txt +22 -22
  195. package/apps/pwa/out/ceo/index.html +1 -1
  196. package/apps/pwa/out/ceo/index.txt +23 -23
  197. package/apps/pwa/out/chat/index.html +1 -1
  198. package/apps/pwa/out/chat/index.txt +22 -22
  199. package/apps/pwa/out/consult/index.html +1 -1
  200. package/apps/pwa/out/consult/index.txt +23 -23
  201. package/apps/pwa/out/control/index.html +1 -1
  202. package/apps/pwa/out/control/index.txt +23 -23
  203. package/apps/pwa/out/decisions/index.html +1 -1
  204. package/apps/pwa/out/decisions/index.txt +22 -22
  205. package/apps/pwa/out/design-check/index.html +1 -1
  206. package/apps/pwa/out/design-check/index.txt +23 -23
  207. package/apps/pwa/out/editor/index.html +1 -1
  208. package/apps/pwa/out/editor/index.txt +24 -24
  209. package/apps/pwa/out/exec/index.html +1 -1
  210. package/apps/pwa/out/exec/index.txt +23 -23
  211. package/apps/pwa/out/field/index.html +1 -1
  212. package/apps/pwa/out/field/index.txt +23 -23
  213. package/apps/pwa/out/healthz +1 -1
  214. package/apps/pwa/out/index.html +1 -1
  215. package/apps/pwa/out/index.txt +22 -22
  216. package/apps/pwa/out/inside/index.html +1 -1
  217. package/apps/pwa/out/inside/index.txt +23 -23
  218. package/apps/pwa/out/intake/index.html +1 -1
  219. package/apps/pwa/out/intake/index.txt +23 -23
  220. package/apps/pwa/out/live/index.html +1 -1
  221. package/apps/pwa/out/live/index.txt +23 -23
  222. package/apps/pwa/out/live-run/index.html +1 -0
  223. package/apps/pwa/out/live-run/index.txt +35 -0
  224. package/apps/pwa/out/loops/index.html +1 -1
  225. package/apps/pwa/out/loops/index.txt +22 -22
  226. package/apps/pwa/out/market/index.html +1 -1
  227. package/apps/pwa/out/market/index.txt +22 -22
  228. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  229. package/apps/pwa/out/missions/placeholder/index.txt +22 -22
  230. package/apps/pwa/out/morning/index.html +1 -1
  231. package/apps/pwa/out/morning/index.txt +23 -23
  232. package/apps/pwa/out/observatory/index.html +1 -1
  233. package/apps/pwa/out/observatory/index.txt +23 -23
  234. package/apps/pwa/out/ops/checklist/index.html +1 -1
  235. package/apps/pwa/out/ops/checklist/index.txt +22 -22
  236. package/apps/pwa/out/ops/release/index.html +1 -1
  237. package/apps/pwa/out/ops/release/index.txt +22 -22
  238. package/apps/pwa/out/ops/seats/index.html +1 -1
  239. package/apps/pwa/out/ops/seats/index.txt +22 -22
  240. package/apps/pwa/out/outputs/index.html +1 -1
  241. package/apps/pwa/out/outputs/index.txt +22 -22
  242. package/apps/pwa/out/reflection/index.html +1 -1
  243. package/apps/pwa/out/reflection/index.txt +23 -23
  244. package/apps/pwa/out/scheduler/index.html +1 -1
  245. package/apps/pwa/out/scheduler/index.txt +23 -23
  246. package/apps/pwa/out/sessions/index.html +1 -1
  247. package/apps/pwa/out/sessions/index.txt +22 -22
  248. package/apps/pwa/out/settings/devices/index.html +1 -1
  249. package/apps/pwa/out/settings/devices/index.txt +23 -23
  250. package/apps/pwa/out/settings/index.html +1 -1
  251. package/apps/pwa/out/settings/index.txt +23 -23
  252. package/apps/pwa/out/setup/done/index.html +1 -1
  253. package/apps/pwa/out/setup/done/index.txt +23 -23
  254. package/apps/pwa/out/setup/index.html +1 -1
  255. package/apps/pwa/out/setup/index.txt +23 -23
  256. package/apps/pwa/out/share/index.html +1 -1
  257. package/apps/pwa/out/share/index.txt +23 -23
  258. package/apps/pwa/out/showroom/index.html +1 -1
  259. package/apps/pwa/out/showroom/index.txt +22 -22
  260. package/apps/pwa/out/tasks/index.html +1 -1
  261. package/apps/pwa/out/tasks/index.txt +23 -23
  262. package/apps/pwa/out/term/index.html +1 -1
  263. package/apps/pwa/out/term/index.txt +23 -23
  264. package/apps/pwa/out/today/index.html +1 -1
  265. package/apps/pwa/out/today/index.txt +23 -23
  266. package/apps/pwa/out/trace/index.html +1 -1
  267. package/apps/pwa/out/trace/index.txt +23 -23
  268. package/apps/pwa/out/vault/index.html +1 -1
  269. package/apps/pwa/out/vault/index.txt +23 -23
  270. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  271. package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
  272. package/apps/pwa/out/workflows/index.html +1 -1
  273. package/apps/pwa/out/workflows/index.txt +23 -23
  274. package/apps/pwa/out/workspace/index.html +1 -1
  275. package/apps/pwa/out/workspace/index.txt +23 -23
  276. package/apps/pwa/out/worktrees/index.html +1 -1
  277. package/apps/pwa/out/worktrees/index.txt +23 -23
  278. package/graphs/demo/editor-recipes.yaml +22 -0
  279. package/graphs/drafts/recipes.yaml +4 -4
  280. package/graphs/implement-loop.yaml +277 -5
  281. package/graphs/retro/daily.yaml +25 -0
  282. package/graphs/retro/recipes.yaml +24 -0
  283. package/package.json +1 -1
  284. package/scripts/ask-marker-check.ts +3 -1
  285. package/scripts/coord-channel-watch.sh +31 -4
  286. package/scripts/docs-lint.ts +4 -1
  287. package/scripts/land-global-checks.ts +89 -0
  288. package/scripts/lib/pch-regress-map.ts +2 -1
  289. package/scripts/marketing/generate-ax-brief.ts +18 -1
  290. package/scripts/measure-nl-routing-corpus.ts +11 -3
  291. package/scripts/release-loop/gate-node.ts +296 -25
  292. package/scripts/release-loop/gate-timing.ts +116 -7
  293. package/scripts/release-loop/preflight.ts +6 -4
  294. package/scripts/release-loop/publish-window.ts +141 -0
  295. package/scripts/release-loop/release-docs.ts +1 -4
  296. package/scripts/release-loop/release-universe.ts +38 -0
  297. package/scripts/release-loop/resume-release.ts +4 -1
  298. package/scripts/release-loop/shard-plan.ts +50 -5
  299. package/scripts/release-loop/unattended-release.ts +10 -2
  300. package/scripts/release-story/draft.ts +5 -1
  301. package/scripts/retro/shadow-compare.ts +71 -0
  302. package/scripts/rhythm/daily.ts +8 -2
  303. package/src/agent-mission/driver.ts +34 -6
  304. package/src/autopilot/build/llm-conflict-merge.ts +132 -7
  305. package/src/autopilot/build/merge-intent.ts +135 -7
  306. package/src/away/release-watch.ts +20 -3
  307. package/src/browser-cdp/client.ts +10 -5
  308. package/src/cli/config-drift.ts +513 -0
  309. package/src/cli/log-event-names.ts +4 -0
  310. package/src/cli/ops-cli.ts +11 -0
  311. package/src/cli/ops-overview-cli.ts +50 -0
  312. package/src/cli/pod-cli.ts +1 -0
  313. package/src/cli/pr-cli.ts +20 -0
  314. package/src/cli/release-cli.ts +20 -5
  315. package/src/cli/retro-cli.ts +78 -0
  316. package/src/cli/tasks-cli.ts +200 -8
  317. package/src/dashboard/index.ts +40 -4
  318. package/src/dashboard/slash-runtime/dashboard-handlers.ts +5 -4
  319. package/src/dashboard/slash-runtime/help-from-registry.ts +57 -31
  320. package/src/domains/outbound-log-probe.ts +6 -1
  321. package/src/esc-abort-gate.ts +3 -1
  322. package/src/graph-runner/graph-cli.ts +57 -0
  323. package/src/graph-runner/graph-run-api.ts +218 -0
  324. package/src/graph-runner/runner.ts +11 -1
  325. package/src/graph-wizard/archetypes.ts +88 -0
  326. package/src/graph-wizard/generate.ts +545 -0
  327. package/src/graph-wizard/saved-steps.ts +120 -0
  328. package/src/graph-wizard/steps.ts +193 -0
  329. package/src/harness/harness-cli-command.ts +482 -59
  330. package/src/harness/harness-queue.ts +315 -25
  331. package/src/harness/harness-salvage-cli.ts +62 -0
  332. package/src/hq/fence-outcomes.ts +63 -0
  333. package/src/hq/fence-wrapper.ts +33 -0
  334. package/src/hq/hq.ts +11 -1
  335. package/src/index.ts +76 -10
  336. package/src/intake-plane/digest.ts +18 -11
  337. package/src/maturity/feature-maturity.ts +4 -3
  338. package/src/nexus/api/draft-metrics.ts +114 -0
  339. package/src/nexus/api/graph-kinds.ts +10 -4
  340. package/src/nexus/api/graph-wizard-api.ts +53 -0
  341. package/src/nexus/api/graphs-api.ts +220 -10
  342. package/src/nexus/api/harness-traversal.ts +205 -0
  343. package/src/nexus/api/http-server.ts +69 -3
  344. package/src/nexus/api/loop-edges.ts +4 -2
  345. package/src/nexus/api/ops-api.ts +22 -7
  346. package/src/nexus/api/ops-board-route.ts +48 -0
  347. package/src/nexus/outbound/router.ts +28 -8
  348. package/src/onboarding/entry-hints.ts +5 -0
  349. package/src/onboarding/first-run.ts +3 -0
  350. package/src/onboarding.ts +24 -6
  351. package/src/ops/overview-collect.ts +345 -0
  352. package/src/ops/overview-seat-era.ts +90 -0
  353. package/src/ops/overview.ts +223 -0
  354. package/src/release-loop/feature-store.ts +14 -11
  355. package/src/release-loop/gate-shards.ts +163 -0
  356. package/src/release-loop/release-schedule.ts +6 -2
  357. package/src/retro/daily.ts +372 -0
  358. package/src/seat-loop/op-decide.ts +173 -0
  359. package/src/seat-loop/seat-loop.ts +60 -3
  360. package/src/seat-loop/seat-report.ts +58 -3
  361. package/src/self-dev/ask-launch-flow.ts +9 -6
  362. package/src/self-dev/ask-launch-io.ts +52 -2
  363. package/src/self-dev/ask-path-candidates.ts +40 -2
  364. package/src/self-dev/draft-sweep.ts +371 -16
  365. package/src/self-dev/draft-triage-rules.ts +19 -4
  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/pr-preview.ts +84 -16
  370. package/src/self-dev/release-path-guard.ts +7 -4
  371. package/src/self-implement/blocked-draft-policy.ts +101 -0
  372. package/src/self-implement/execution-clarification.ts +11 -0
  373. package/src/self-implement/frozen-merges.ts +23 -0
  374. package/src/self-implement/gate-env-retry.ts +83 -0
  375. package/src/self-implement/goal-author.ts +14 -0
  376. package/src/self-implement/heal-intake.ts +3 -0
  377. package/src/self-implement/orchestrator.ts +234 -6
  378. package/src/self-implement/run-stop.ts +123 -0
  379. package/src/self-implement/run-ttl.ts +258 -0
  380. package/src/self-implement/salvage-retention.ts +123 -0
  381. package/src/self-implement/seams.ts +7 -0
  382. package/src/self-implement/self-implement-cli.ts +2 -0
  383. package/src/self-implement/self-implement-runtime.ts +12 -0
  384. package/src/self-implement/sibling-resync.ts +223 -0
  385. package/src/self-implement/stop-autoheal.ts +204 -0
  386. package/src/session-runtime/index.ts +5 -3
  387. package/src/shell-env-bootstrap.ts +17 -3
  388. package/src/task-agent/board.ts +1 -1
  389. package/src/task-agent/card-evidence.ts +105 -10
  390. package/src/task-agent/judge.ts +7 -0
  391. package/src/task-agent/live-moves.ts +120 -20
  392. package/src/task-agent/mission.ts +1 -1
  393. package/src/task-agent/parent-host.ts +315 -0
  394. package/src/task-agent/shadow.ts +10 -1
  395. package/src/task-agent/task-hand.ts +16 -4
  396. package/src/task-orchestrator/surfaces/pod-command-job.ts +60 -11
  397. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +4 -0
  398. package/src/task-orchestrator/surfaces/pod-gate-deps.ts +123 -0
  399. package/src/task-orchestrator/surfaces/pod-install-slots.ts +89 -0
  400. package/src/task-orchestrator/surfaces/pod-lease.ts +93 -10
  401. package/src/task-orchestrator/surfaces/pod-pool.ts +147 -34
  402. package/src/task-orchestrator/surfaces/self-implement-pod.ts +58 -9
  403. package/src/telegram-commands.ts +41 -0
  404. package/src/testing/real-cli-spawn.ts +106 -0
  405. package/src/tool-runtime/browser-runtime.ts +87 -3
  406. package/src/tool-runtime/index.ts +1 -0
  407. package/src/user-config.ts +76 -7
  408. package/src/version/packed-revision.json +1 -1
  409. package/apps/pwa/out/_next/static/J5H7BvqgSI8DUjtRRcA_S/_buildManifest.js +0 -1
  410. package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +0 -1
  411. package/apps/pwa/out/_next/static/chunks/1157-cba0e8553d35a980.js +0 -1
  412. package/apps/pwa/out/_next/static/chunks/171-23a877ed11720de9.js +0 -1
  413. package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +0 -1
  414. package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +0 -1
  415. package/apps/pwa/out/_next/static/chunks/2336-e830c777eb6bce04.js +0 -1
  416. package/apps/pwa/out/_next/static/chunks/2375-e061bb24cd2b5cde.js +0 -1
  417. package/apps/pwa/out/_next/static/chunks/2415.ed6428ff334c7ce2.js +0 -1
  418. package/apps/pwa/out/_next/static/chunks/3094.67339318c7f72a3a.js +0 -2
  419. package/apps/pwa/out/_next/static/chunks/338-ceb956612dcfaa86.js +0 -1
  420. package/apps/pwa/out/_next/static/chunks/3395-835e4ceaa6335ba4.js +0 -1
  421. package/apps/pwa/out/_next/static/chunks/3403.4bc97b4066945a94.js +0 -5
  422. package/apps/pwa/out/_next/static/chunks/3441-be82e3595aef3a20.js +0 -1
  423. package/apps/pwa/out/_next/static/chunks/3689-89905039bf3e7257.js +0 -1
  424. package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +0 -1
  425. package/apps/pwa/out/_next/static/chunks/3822-6f268bc26c6a84ac.js +0 -1
  426. package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +0 -1
  427. package/apps/pwa/out/_next/static/chunks/4457-d7df81c90f9d0268.js +0 -1
  428. package/apps/pwa/out/_next/static/chunks/4551-77dadae76f6cbace.js +0 -1
  429. package/apps/pwa/out/_next/static/chunks/4978.6fd93b974441e87f.js +0 -1
  430. package/apps/pwa/out/_next/static/chunks/5012-f746f6549b869f91.js +0 -1
  431. package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +0 -1
  432. package/apps/pwa/out/_next/static/chunks/5791-bb82743fa6299562.js +0 -1
  433. package/apps/pwa/out/_next/static/chunks/62-5bfed1e23790b745.js +0 -1
  434. package/apps/pwa/out/_next/static/chunks/6320-9bc8561fde416e07.js +0 -1
  435. package/apps/pwa/out/_next/static/chunks/649-ca96d1c5d09e02b8.js +0 -1
  436. package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +0 -1
  437. package/apps/pwa/out/_next/static/chunks/6726-85a7db59b288c53f.js +0 -1
  438. package/apps/pwa/out/_next/static/chunks/6735-33267c4630d0d963.js +0 -1
  439. package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +0 -1
  440. package/apps/pwa/out/_next/static/chunks/7633-141682015873beb6.js +0 -1
  441. package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +0 -1
  442. package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +0 -1
  443. package/apps/pwa/out/_next/static/chunks/8067-143791231c99bd02.js +0 -1
  444. package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +0 -1
  445. package/apps/pwa/out/_next/static/chunks/8430-a10cfba8ef282d4e.js +0 -1
  446. package/apps/pwa/out/_next/static/chunks/8870-9614a213b990b10b.js +0 -25
  447. package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +0 -1
  448. package/apps/pwa/out/_next/static/chunks/9454-12c2f30dea62291c.js +0 -1
  449. package/apps/pwa/out/_next/static/chunks/976-4a9b4ebdca7c739a.js +0 -1
  450. package/apps/pwa/out/_next/static/chunks/9989-15cdf568bafa9fc8.js +0 -1
  451. package/apps/pwa/out/_next/static/chunks/ae787e4c-e5b1ef00b2dbceca.js +0 -1
  452. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +0 -1
  453. package/apps/pwa/out/_next/static/chunks/app/approvals/page-deef5ee191830f14.js +0 -1
  454. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-b193da485f9a14b2.js +0 -1
  455. package/apps/pwa/out/_next/static/chunks/app/board/page-b6e1a79df0e214f4.js +0 -1
  456. package/apps/pwa/out/_next/static/chunks/app/botlab/page-e84df4d907f703c7.js +0 -1
  457. package/apps/pwa/out/_next/static/chunks/app/bots/page-a5fb9a5db89ae4ea.js +0 -1
  458. package/apps/pwa/out/_next/static/chunks/app/ceo/page-1499f481d7fc52d6.js +0 -1
  459. package/apps/pwa/out/_next/static/chunks/app/chat/page-c7cf753aeb648790.js +0 -1
  460. package/apps/pwa/out/_next/static/chunks/app/consult/page-4118469e514e10fd.js +0 -1
  461. package/apps/pwa/out/_next/static/chunks/app/control/page-e968f7630654332a.js +0 -1
  462. package/apps/pwa/out/_next/static/chunks/app/decisions/page-505915052f947022.js +0 -1
  463. package/apps/pwa/out/_next/static/chunks/app/design-check/page-c2f962fb4a3b06c6.js +0 -1
  464. package/apps/pwa/out/_next/static/chunks/app/editor/page-7b5ed0bd8a589ec4.js +0 -1
  465. package/apps/pwa/out/_next/static/chunks/app/exec/page-cf8193250dd42dd4.js +0 -1
  466. package/apps/pwa/out/_next/static/chunks/app/field/page-cf3f5dfa35dea8e9.js +0 -1
  467. package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +0 -1
  468. package/apps/pwa/out/_next/static/chunks/app/inside/page-5b29d9b06ceadade.js +0 -1
  469. package/apps/pwa/out/_next/static/chunks/app/intake/page-a15e7fcc283e9ca7.js +0 -1
  470. package/apps/pwa/out/_next/static/chunks/app/layout-d2915e7b70ff3d0e.js +0 -1
  471. package/apps/pwa/out/_next/static/chunks/app/loops/page-2595f0eba29f57e1.js +0 -1
  472. package/apps/pwa/out/_next/static/chunks/app/market/page-c776322454156e36.js +0 -1
  473. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-77bbd17f4435aa10.js +0 -1
  474. package/apps/pwa/out/_next/static/chunks/app/morning/page-049689e611ecc936.js +0 -1
  475. package/apps/pwa/out/_next/static/chunks/app/not-found-85954a4fb09c44eb.js +0 -1
  476. package/apps/pwa/out/_next/static/chunks/app/observatory/page-e773e552e1068ee6.js +0 -1
  477. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-5633c4bfa7684b4a.js +0 -1
  478. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-d829b92bd01b4994.js +0 -1
  479. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-061c317fc771becf.js +0 -1
  480. package/apps/pwa/out/_next/static/chunks/app/outputs/page-3a45ece8865ed4b6.js +0 -1
  481. package/apps/pwa/out/_next/static/chunks/app/page-0be990618c0d56bd.js +0 -1
  482. package/apps/pwa/out/_next/static/chunks/app/reflection/page-bfea4865dc244b73.js +0 -1
  483. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-0d6ba8e337741484.js +0 -1
  484. package/apps/pwa/out/_next/static/chunks/app/sessions/page-76a88b55d816482e.js +0 -1
  485. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-bba1542df59f61ce.js +0 -1
  486. package/apps/pwa/out/_next/static/chunks/app/settings/page-4185c95689bb372e.js +0 -1
  487. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-b699e1a8d5d34b8b.js +0 -1
  488. package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +0 -1
  489. package/apps/pwa/out/_next/static/chunks/app/setup/page-c225e338c31bf274.js +0 -1
  490. package/apps/pwa/out/_next/static/chunks/app/share/page-449353f70507ca1f.js +0 -1
  491. package/apps/pwa/out/_next/static/chunks/app/showroom/page-90b889714102476d.js +0 -1
  492. package/apps/pwa/out/_next/static/chunks/app/tasks/page-f1ee7fbeb7f96ddc.js +0 -1
  493. package/apps/pwa/out/_next/static/chunks/app/term/page-d59a95e1434dfaa0.js +0 -1
  494. package/apps/pwa/out/_next/static/chunks/app/today/page-71491a23414d1a47.js +0 -1
  495. package/apps/pwa/out/_next/static/chunks/app/trace/page-de7112bdb65fa4fe.js +0 -1
  496. package/apps/pwa/out/_next/static/chunks/app/vault/page-feb4d0d88079660d.js +0 -1
  497. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-f17d887b40dd6b1d.js +0 -1
  498. package/apps/pwa/out/_next/static/chunks/app/workflows/page-8da77be2192bcd5d.js +0 -1
  499. package/apps/pwa/out/_next/static/chunks/app/workspace/page-72c3b9a5e9582b29.js +0 -1
  500. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3afe4c8d0c48c39.js +0 -1
  501. package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +0 -1
  502. package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +0 -1
  503. package/apps/pwa/out/_next/static/chunks/main-app-b9308f2d8a931531.js +0 -1
  504. package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +0 -1
  505. package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +0 -1
  506. package/apps/pwa/out/_next/static/chunks/webpack-6f35f9d892fa630b.js +0 -1
  507. package/apps/pwa/out/_next/static/css/85f53c6f35d11382.css +0 -1
  508. /package/apps/pwa/out/_next/static/{J5H7BvqgSI8DUjtRRcA_S → MObU8ujkMciAKr6ljAV05}/_ssgManifest.js +0 -0
  509. /package/skills/project-onboarding/{skill.md → SKILL.md} +0 -0
@@ -1,15 +1,33 @@
1
- import { mkdtempSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
1
+ import { existsSync, mkdtempSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
2
2
  import { tmpdir } from 'node:os';
3
3
  import { join } from 'node:path';
4
4
  import { parse as parseYaml } from 'yaml';
5
5
  import { elanousStateRoot } from '../../autopilot/state-paths.js';
6
+ import { debug } from '../../debug/log.js';
6
7
  import { defaultGraphsDir, loadGraphTemplatesFrom } from '../../self-implement/graph-templates.js';
7
8
  import { loadGraphTemplates } from '../../self-implement/graph-yaml.js';
9
+ import { validateGraphYaml } from './graph-kinds.js';
10
+ import { parseWizardSteps, readWizardSteps, withWizardSteps } from '../../graph-wizard/saved-steps.js';
11
+ import type { WizardNodeStep } from '../../graph-wizard/steps.js';
8
12
  import { jsonResponse } from './json-response.js';
9
13
 
10
14
  /** Declared graph ids only. A user string is never joined onto a filesystem path. */
11
15
  const GRAPH_ID = /^[a-z0-9-]+$/;
12
16
 
17
+ /**
18
+ * Sidecar names a graph directory loads specially. `recipes.yaml` next to a graph is what
19
+ * the graph runner executes (`cmd:` recipes), so an HTTP write to `<mine>/recipes.yaml`
20
+ * would be a remote command path. These ids are never created, saved or reverted, and
21
+ * these files are never indexed as graphs.
22
+ */
23
+ export const RESERVED_GRAPH_IDS: ReadonlySet<string> = new Set(['recipes', 'overlay', 'overlays']);
24
+
25
+ function reserved(id: string): Response | null {
26
+ if (!RESERVED_GRAPH_IDS.has(id)) return null;
27
+ debug.log('nexus.graphs', 'rejected-reserved', { id });
28
+ return jsonResponse({ error: 'reserved-id', id }, 400);
29
+ }
30
+
13
31
  export interface GraphCatalogEntry {
14
32
  id: string;
15
33
  source: 'core' | 'mine';
@@ -45,6 +63,7 @@ function readDirYaml(dir: string): string[] {
45
63
  function indexByDeclaredId(dir: string): Map<string, { file: string; text: string }> {
46
64
  const found = new Map<string, { file: string; text: string }>();
47
65
  for (const file of readDirYaml(dir)) {
66
+ if (RESERVED_GRAPH_IDS.has(file.replace(/\.ya?ml$/, ''))) continue;
48
67
  let text: string;
49
68
  try { text = readFileSync(join(dir, file), 'utf8'); }
50
69
  catch { continue; }
@@ -92,7 +111,12 @@ function detailOf(id: string, source: 'core' | 'mine', editable: boolean, dir: s
92
111
  editable,
93
112
  entry_node: template.entryNode,
94
113
  terminal_nodes: template.terminalNodes,
95
- nodes: template.nodes.map(({ nodeId, kind, recipe, maxVisits }) => ({ node_id: nodeId, kind, recipe, max_visits: maxVisits })),
114
+ // GRAPH-GROUPS — a node's `group:` tag (when the loader carries it) rides along so the PWA can box/fold units.
115
+ nodes: template.nodes.map((node) => {
116
+ const { nodeId, kind, recipe, maxVisits } = node;
117
+ const group = (node as { group?: unknown }).group;
118
+ return { node_id: nodeId, kind, recipe, max_visits: maxVisits, ...(typeof group === 'string' && group.trim() ? { group: group.trim() } : {}) };
119
+ }),
96
120
  edges: spec.edges.map(({ from, to, on, map }) => ({
97
121
  from,
98
122
  ...(to === undefined ? {} : { to }),
@@ -125,6 +149,21 @@ function validateInTemp(text: string, expectedId: string): { ok: true } | { ok:
125
149
  }
126
150
  }
127
151
 
152
+ /** Same validator as POST /v1/graphs/validate. Invalid → 422; declared graph_id ≠ id → 400. Null = ok. */
153
+ function rejectGraphYaml(text: string, id: string, version?: string): Response | null {
154
+ const checked = validateGraphYaml('harness', text);
155
+ if (!checked.ok) {
156
+ debug.log('nexus.graphs', 'rejected-invalid', { id, ...(version ? { version } : {}), issues: checked.errors });
157
+ return jsonResponse({ error: 'invalid-graph', errors: checked.errors, ignoredKeys: checked.ignoredKeys }, 422);
158
+ }
159
+ if (checked.graphId !== id) {
160
+ const errors = [{ path: `${id}/graph_id`, message: 'graph_id 가 요청 id 와 다르다' }];
161
+ debug.log('nexus.graphs', 'rejected-invalid', { id, issues: errors });
162
+ return jsonResponse({ error: 'graph-id-mismatch', errors }, 400);
163
+ }
164
+ return null;
165
+ }
166
+
128
167
  /** Core graphs are read from the same YAML loader as the harness, never rewritten by this API. */
129
168
  export function handleGraphsGet(pathname: string, dir = defaultGraphsDir(), deps: GraphsApiDeps = {}): Response {
130
169
  const coreDir = deps.coreDir ?? dir;
@@ -146,7 +185,8 @@ export function handleGraphsGet(pathname: string, dir = defaultGraphsDir(), deps
146
185
  const coreHit = core.ok ? indexByDeclaredId(coreDir).get(id) : undefined;
147
186
  const hit = mineHit ?? coreHit;
148
187
  if (!hit) return jsonResponse({ error: 'not-found', id }, 404);
149
- return jsonResponse({ id, source: mineHit ? 'mine' : 'core', editable: Boolean(mineHit), yaml: hit.text });
188
+ const steps = mineHit ? readWizardSteps(mineDir, id, hit.text) : null;
189
+ return jsonResponse({ id, source: mineHit ? 'mine' : 'core', editable: Boolean(mineHit), yaml: hit.text, ...(steps ? { steps } : {}) });
150
190
  }
151
191
 
152
192
  const match = /^\/v1\/graphs\/([^/]+)$/.exec(pathname);
@@ -168,6 +208,8 @@ export async function handleGraphsPut(pathname: string, req: Request, deps: Grap
168
208
  if (!match) return jsonResponse({ error: 'not-found' }, 404);
169
209
  const id = resolveId(match[1]!);
170
210
  if (!id) return jsonResponse({ error: 'bad_request', reason: 'invalid graph id' }, 400);
211
+ const reservedId = reserved(id);
212
+ if (reservedId) return reservedId;
171
213
  const coreDir = deps.coreDir ?? defaultGraphsDir();
172
214
  const mineDir = deps.mineDir ?? mineGraphsDir();
173
215
  const core = coreCatalog(coreDir);
@@ -175,15 +217,179 @@ export async function handleGraphsPut(pathname: string, req: Request, deps: Grap
175
217
  const mineOwns = indexByDeclaredId(mineDir).has(id);
176
218
  if (coreOwns && !mineOwns) return jsonResponse({ error: 'core-read-only', id }, 403);
177
219
  if (!mineOwns) return jsonResponse({ error: 'not-found', id }, 404);
178
- let body: { yaml?: unknown };
179
- try { body = await req.json() as { yaml?: unknown }; }
220
+ let body: { yaml?: unknown; steps?: unknown };
221
+ try { body = await req.json() as { yaml?: unknown; steps?: unknown }; }
180
222
  catch { return jsonResponse({ error: 'bad_request', reason: 'json body required' }, 400); }
181
- if (typeof body.yaml !== 'string') return jsonResponse({ error: 'bad_request', reason: 'yaml string required' }, 400);
182
- const checked = validateInTemp(body.yaml, id);
183
- if (!checked.ok) return jsonResponse({ error: 'invalid-graph', errors: checked.errors }, 400);
223
+ if (typeof body?.yaml !== 'string') return jsonResponse({ error: 'bad_request', reason: 'yaml string required' }, 400);
224
+ const rejected = rejectGraphYaml(body.yaml, id);
225
+ if (rejected) return rejected;
226
+ const steps = stepsOf(body.steps, body.yaml);
227
+ if (steps instanceof Response) return steps;
184
228
  const existing = indexByDeclaredId(mineDir).get(id)!;
185
- writeFileSync(join(mineDir, existing.file), body.yaml);
186
- return jsonResponse({ id, source: 'mine', editable: true, saved: true });
229
+ // Writes go only to <mine>/<id>.yaml; a hand-placed file under another name is not edited over HTTP.
230
+ if (existing.file !== `${id}.yaml`) return jsonResponse({ error: 'file-name-mismatch', id, file: existing.file }, 409);
231
+ let written: { version: string; previous: string };
232
+ try { written = withWizardSteps(mineDir, id, steps, () => writeWithVersion(mineDir, id, existing, body.yaml as string)); }
233
+ catch (error) { return saveFailed(id, error); }
234
+ const { version, previous } = written;
235
+ debug.log('nexus.graphs', 'saved', { id, version, previous, steps: steps ? Object.keys(steps).length : 0 });
236
+ return jsonResponse({ id, source: 'mine', editable: true, saved: true, version, previous });
237
+ }
238
+
239
+ const VERSION_ID = /^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}Z(?:-\d{4})?$/;
240
+ export const GRAPH_VERSION_CAP = 20;
241
+
242
+ function versionsDir(mineDir: string, id: string): string {
243
+ return join(mineDir, '.versions', id);
244
+ }
245
+
246
+ /** Version ids are ISO timestamps (`:`/`.` → `-`), so name order is save order. */
247
+ export function listGraphVersions(mineDir: string, id: string): Array<{ version: string; savedAt: string; bytes: number }> {
248
+ const dir = versionsDir(mineDir, id);
249
+ // Only `.yaml` is ever written, read and reverted — a stray `.yml` is not a version.
250
+ return readDirYaml(dir)
251
+ .filter((file) => file.endsWith('.yaml'))
252
+ .map((file) => file.replace(/\.yaml$/, ''))
253
+ .filter((version) => VERSION_ID.test(version))
254
+ .sort()
255
+ .map((version) => {
256
+ const iso = /^(\d{4}-\d{2}-\d{2})T(\d{2})-(\d{2})-(\d{2})-(\d{3})Z/.exec(version);
257
+ let bytes = 0;
258
+ try { bytes = statSync(join(dir, `${version}.yaml`)).size; } catch { /* listed above; raced away */ }
259
+ return { version, savedAt: iso ? `${iso[1]}T${iso[2]}:${iso[3]}:${iso[4]}.${iso[5]}Z` : version, bytes };
260
+ });
261
+ }
262
+
263
+ function recordVersion(mineDir: string, id: string, text: string): string {
264
+ const dir = versionsDir(mineDir, id);
265
+ mkdirSync(dir, { recursive: true });
266
+ const base = new Date().toISOString().replace(/[:.]/g, '-');
267
+ let version = base;
268
+ for (let n = 1; existsSync(join(dir, `${version}.yaml`)); n += 1) version = `${base}-${String(n).padStart(4, '0')}`;
269
+ writeFileSync(join(dir, `${version}.yaml`), text);
270
+ const all = listGraphVersions(mineDir, id);
271
+ for (const old of all.slice(0, Math.max(0, all.length - GRAPH_VERSION_CAP))) {
272
+ rmSync(join(dir, `${old.version}.yaml`), { force: true });
273
+ }
274
+ return version;
275
+ }
276
+
277
+ /**
278
+ * Every saved state is a version. A graph with no history yet (e.g. a fresh clone)
279
+ * first records its current bytes, so `previous` always points at restorable bytes.
280
+ */
281
+ function writeWithVersion(mineDir: string, id: string, existing: { file: string; text: string }, text: string): { version: string; previous: string } {
282
+ const history = listGraphVersions(mineDir, id);
283
+ const previous = history.at(-1)?.version ?? recordVersion(mineDir, id, existing.text);
284
+ // Version first, then an atomic replace of the current file: a crash in between leaves
285
+ // the old current bytes intact (they are `previous`), never bytes that no version holds.
286
+ const version = recordVersion(mineDir, id, text);
287
+ const target = join(mineDir, `${id}.yaml`);
288
+ const temp = join(mineDir, `.${id}.yaml.${process.pid}.tmp`);
289
+ writeFileSync(temp, text);
290
+ renameSync(temp, target);
291
+ return { version, previous };
292
+ }
293
+
294
+ function mineOwnership(id: string, deps: GraphsApiDeps): { mineDir: string; existing: { file: string; text: string } } | Response {
295
+ const reservedId = reserved(id);
296
+ if (reservedId) return reservedId;
297
+ const coreDir = deps.coreDir ?? defaultGraphsDir();
298
+ const mineDir = deps.mineDir ?? mineGraphsDir();
299
+ const existing = indexByDeclaredId(mineDir).get(id);
300
+ if (!existing) {
301
+ const core = coreCatalog(coreDir);
302
+ if (core.ok && indexByDeclaredId(coreDir).has(id)) return jsonResponse({ error: 'core-read-only', id }, 403);
303
+ return jsonResponse({ error: 'not-found', id }, 404);
304
+ }
305
+ return { mineDir, existing };
306
+ }
307
+
308
+ /** GET /v1/graphs/<id>/versions — only «mine» graphs have history. */
309
+ export function handleGraphVersionsGet(pathname: string, deps: GraphsApiDeps = {}): Response {
310
+ const match = /^\/v1\/graphs\/([^/]+)\/versions$/.exec(pathname);
311
+ if (!match) return jsonResponse({ error: 'not-found' }, 404);
312
+ const id = resolveId(match[1]!);
313
+ if (!id) return jsonResponse({ error: 'bad_request', reason: 'invalid graph id' }, 400);
314
+ const owned = mineOwnership(id, deps);
315
+ if (owned instanceof Response) return owned;
316
+ return jsonResponse({ id, versions: listGraphVersions(owned.mineDir, id) });
317
+ }
318
+
319
+ /** POST /v1/graphs/<id>/revert {version} — restores that version; the restore is itself a new version. */
320
+ export async function handleGraphsRevert(pathname: string, req: Request, deps: GraphsApiDeps = {}): Promise<Response> {
321
+ const match = /^\/v1\/graphs\/([^/]+)\/revert$/.exec(pathname);
322
+ if (!match) return jsonResponse({ error: 'not-found' }, 404);
323
+ const id = resolveId(match[1]!);
324
+ if (!id) return jsonResponse({ error: 'bad_request', reason: 'invalid graph id' }, 400);
325
+ const owned = mineOwnership(id, deps);
326
+ if (owned instanceof Response) return owned;
327
+ if (owned.existing.file !== `${id}.yaml`) return jsonResponse({ error: 'file-name-mismatch', id, file: owned.existing.file }, 409);
328
+ let body: { version?: unknown };
329
+ try { body = await req.json() as { version?: unknown }; }
330
+ catch { return jsonResponse({ error: 'bad_request', reason: 'json body required' }, 400); }
331
+ if (typeof body?.version !== 'string' || !VERSION_ID.test(body.version)) {
332
+ return jsonResponse({ error: 'bad_request', reason: 'version string required' }, 400);
333
+ }
334
+ const target = body.version;
335
+ // Only a listed version id is ever joined onto a path.
336
+ if (!listGraphVersions(owned.mineDir, id).some((entry) => entry.version === target)) {
337
+ return jsonResponse({ error: 'version-not-found', id, version: target }, 404);
338
+ }
339
+ const text = readFileSync(join(versionsDir(owned.mineDir, id), `${target}.yaml`), 'utf8');
340
+ const rejected = rejectGraphYaml(text, id, target);
341
+ if (rejected) return rejected;
342
+ const { version, previous } = writeWithVersion(owned.mineDir, id, owned.existing, text);
343
+ debug.log('nexus.graphs', 'reverted', { id, version, previous, restoredFrom: target });
344
+ return jsonResponse({ id, source: 'mine', editable: true, reverted: true, version, previous, restoredFrom: target });
345
+ }
346
+
347
+ /** GRAPH-WIZARD-SAVE-RECIPES — optional `steps` on save (the wizard's node → library step map). Absent = keep
348
+ * whatever sidecar the graph has; present = validated against the step library and stored as a sidecar. The
349
+ * run builds commands from it server-side (src/graph-wizard/saved-steps.ts) — no client command text is kept. */
350
+ function stepsOf(raw: unknown, yaml: string): Record<string, WizardNodeStep> | null | Response {
351
+ // Only an absent field means «keep what is there»; an explicit null or any other non-object is a bad request.
352
+ if (raw === undefined) return null;
353
+ const parsed = parseWizardSteps(raw, yaml);
354
+ if (!parsed.ok) return jsonResponse({ error: 'bad_request', reason: parsed.reason }, 400);
355
+ return parsed.steps;
356
+ }
357
+
358
+ function saveFailed(id: string, error: unknown): Response {
359
+ const reason = error instanceof Error ? error.message.slice(0, 200) : String(error);
360
+ debug.log('nexus.graphs', 'save-failed', { id, reason }, { level: 'warn' });
361
+ return jsonResponse({ error: 'save-failed', id, reason }, 500);
362
+ }
363
+
364
+ /** POST /v1/graphs {id, yaml} — a brand-new «mine» graph (blank canvas → save). Records version v1. */
365
+ export async function handleGraphsCreate(req: Request, deps: GraphsApiDeps = {}): Promise<Response> {
366
+ let body: { id?: unknown; yaml?: unknown; steps?: unknown };
367
+ try { body = await req.json() as { id?: unknown; yaml?: unknown; steps?: unknown }; }
368
+ catch { return jsonResponse({ error: 'bad_request', reason: 'json body required' }, 400); }
369
+ const id = typeof body?.id === 'string' ? resolveId(body.id) : null;
370
+ if (!id) return jsonResponse({ error: 'bad_request', reason: 'id must match [a-z0-9-]' }, 400);
371
+ const reservedId = reserved(id);
372
+ if (reservedId) return reservedId;
373
+ if (typeof body.yaml !== 'string') return jsonResponse({ error: 'bad_request', reason: 'yaml string required' }, 400);
374
+ const coreDir = deps.coreDir ?? defaultGraphsDir();
375
+ const mineDir = deps.mineDir ?? mineGraphsDir();
376
+ if (indexByDeclaredId(coreDir).has(id)) return jsonResponse({ error: 'core-read-only', id }, 403);
377
+ if (indexByDeclaredId(mineDir).has(id) || existsSync(join(mineDir, `${id}.yaml`))) return jsonResponse({ error: 'exists', id }, 409);
378
+ const rejected = rejectGraphYaml(body.yaml, id);
379
+ if (rejected) return rejected;
380
+ const steps = stepsOf(body.steps, body.yaml);
381
+ if (steps instanceof Response) return steps;
382
+ let version: string;
383
+ try {
384
+ version = withWizardSteps(mineDir, id, steps, () => {
385
+ mkdirSync(mineDir, { recursive: true });
386
+ writeFileSync(join(mineDir, `${id}.yaml`), body.yaml as string, { flag: 'wx' });
387
+ try { return recordVersion(mineDir, id, body.yaml as string); }
388
+ catch (error) { rmSync(join(mineDir, `${id}.yaml`), { force: true }); throw error; }
389
+ });
390
+ } catch (error) { return saveFailed(id, error); }
391
+ debug.log('nexus.graphs', 'saved', { id, version, created: true, steps: steps ? Object.keys(steps).length : 0 });
392
+ return jsonResponse({ id, source: 'mine', editable: true, saved: true, version, previous: null }, 201);
187
393
  }
188
394
 
189
395
  /** Copy core bytes into «my graphs», changing only the declared graph_id. */
@@ -198,6 +404,8 @@ export async function handleGraphsClone(pathname: string, req: Request, deps: Gr
198
404
  if (typeof body.newId !== 'string' || !GRAPH_ID.test(body.newId) || body.newId === id) {
199
405
  return jsonResponse({ error: 'bad_request', reason: 'newId must match [a-z0-9-]' }, 400);
200
406
  }
407
+ const reservedId = reserved(body.newId);
408
+ if (reservedId) return reservedId;
201
409
  const coreDir = deps.coreDir ?? defaultGraphsDir();
202
410
  const mineDir = deps.mineDir ?? mineGraphsDir();
203
411
  const source = indexByDeclaredId(coreDir).get(id) ?? indexByDeclaredId(mineDir).get(id);
@@ -215,7 +423,9 @@ export async function handleGraphsClone(pathname: string, req: Request, deps: Gr
215
423
  }
216
424
 
217
425
  export async function handleGraphsMutation(pathname: string, req: Request, deps: GraphsApiDeps = {}): Promise<Response | null> {
426
+ if (req.method === 'POST' && pathname === '/v1/graphs') return handleGraphsCreate(req, deps);
218
427
  if (req.method === 'PUT' && /^\/v1\/graphs\/[^/]+\/yaml$/.test(pathname)) return handleGraphsPut(pathname, req, deps);
219
428
  if (req.method === 'POST' && /^\/v1\/graphs\/[^/]+\/clone$/.test(pathname)) return handleGraphsClone(pathname, req, deps);
429
+ if (req.method === 'POST' && /^\/v1\/graphs\/[^/]+\/revert$/.test(pathname)) return handleGraphsRevert(pathname, req, deps);
220
430
  return null;
221
431
  }
@@ -0,0 +1,205 @@
1
+ // HARNESS-RUN-LIVE-GRAPH (0.2.21) — one harness run's «which node, which outcome, when» for the PWA
2
+ // live/replay graph page (`/live-run/?run=<runId>`).
3
+ //
4
+ // Canonical source = the run's own ledger `~/.elanous/run-ledger/<runId>.jsonl` (federated: pod ledgers synced
5
+ // home are found through the same directories as the running-runs query):
6
+ // · `pipeline-node-entry` — one per node visit (node · round · graphId)
7
+ // · `graph-visit-budget` — the visit count the runtime itself counted
8
+ // · per-node outcome events — implemented · gated · reviewed(+review-reflect) · rework-budget · pre-pr-sync ·
9
+ // gate.postsync · pr-opened · merge-decision
10
+ // · `run-status` — terminal status of the whole run
11
+ // Durations are derived (gap to the next node entry, or to the terminal status); nothing writes a node «end».
12
+ //
13
+ // Node ids are never hardcoded for routing: any node the graph declares and the runtime enters (including the journey
14
+ // nodes HARNESS-FULL-GRAPH adds — entrance, queue, author, dispatch, landing, failure side) shows up as a step. A
15
+ // generic `pipeline-node-exit {node, outcome, detail?}` wins over the per-node heuristics below when present.
16
+ //
17
+ // ⛔ No goal body: the title is the first line of `start.feature`, cut to 80 chars. No artifact paths, no tokens.
18
+
19
+ import { loadFederatedRunLedger, loadRunLedger, runLedgerDir, type RunLedgerEntry } from '../../self-implement/run-ledger.js';
20
+ import { redactSecretText } from '../../debug/log.js';
21
+
22
+ export interface TraversalStepWire {
23
+ node: string;
24
+ round: number | null;
25
+ /** `pass` · `fail` · null (still running). */
26
+ outcome: string | null;
27
+ at: string;
28
+ durationMs: number | null;
29
+ visit: number;
30
+ maxVisits: number | null;
31
+ detail: string | null;
32
+ }
33
+
34
+ export interface RunTraversalWire {
35
+ runId: string;
36
+ graphId: string | null;
37
+ title: string | null;
38
+ /** `running` until the ledger carries a terminal `run-status`. */
39
+ status: string;
40
+ stage: string | null;
41
+ startedAt: string | null;
42
+ endedAt: string | null;
43
+ prNumber: number | null;
44
+ substrate: string | null;
45
+ steps: TraversalStepWire[];
46
+ ledgerEvents: number;
47
+ }
48
+
49
+ const str = (value: unknown): string | null => typeof value === 'string' && value.trim() ? value : null;
50
+ const num = (value: unknown): number | null => typeof value === 'number' && Number.isFinite(value) ? value : null;
51
+ const ms = (iso: string | undefined | null): number => iso ? Date.parse(iso) : Number.NaN;
52
+
53
+ const TERMINAL_FAIL = new Set(['failed', 'abandoned', 'cancelled', 'error', 'aborted', 'stopped']);
54
+
55
+ /** A display title from a goal's first line: path-like tokens shrink to their basename (`a/b/c.tsx` → `c.tsx`),
56
+ * then the line is redacted and cut to 80 chars. Shared with the PWA picker (`apps/pwa/src/lib/live-run-view.ts`). */
57
+ export function displayTitle(text: string | null | undefined, max = 80): string | null {
58
+ const line = text?.split('\n').map((part) => part.trim()).find(Boolean) ?? '';
59
+ const shrunk = line.replace(/\s*\[truncated[^\]]*\]\s*$/, '').replace(/(?:~|\.{0,2})?\/?(?:[\w.@-]+\/)+([\w.@-]+)/g, '$1');
60
+ const cut = shrunk.length > max ? `${shrunk.slice(0, max - 1)}…` : shrunk;
61
+ return cut ? redactSecretText(cut) : null;
62
+ }
63
+
64
+ function titleOf(entries: readonly RunLedgerEntry[]): string | null {
65
+ return displayTitle(str(entries.find((entry) => entry.event === 'start')?.data.feature));
66
+ }
67
+
68
+ /** Outcome + one-line detail of a node visit, read from the events between its entry and the next entry. */
69
+ function judgeVisit(node: string, window: readonly RunLedgerEntry[], lookback: readonly RunLedgerEntry[] = []): { outcome: 'pass' | 'fail' | null; detail: string | null } {
70
+ const last = (event: string) => [...window].reverse().find((entry) => entry.event === event)?.data;
71
+ // Some decisions are written just before the node they belong to is entered (merge-decision lands in the regate
72
+ // window, right before open-pr) — look back to the previous node entry for those.
73
+ const near = (event: string) => last(event) ?? [...lookback].reverse().find((entry) => entry.event === event)?.data;
74
+ const exit = [...window].reverse().find((entry) => entry.event === 'pipeline-node-exit' && entry.data.node === node)?.data;
75
+ const exitOutcome = str(exit?.outcome);
76
+ if (exitOutcome) {
77
+ const failed = TERMINAL_FAIL.has(exitOutcome) || exitOutcome === 'fail';
78
+ const exitDetail = str(exit?.detail)?.replace(/\s+/g, ' ').trim();
79
+ return { outcome: failed ? 'fail' : 'pass', detail: exitDetail ? redactSecretText(exitDetail.slice(0, 120)) : exitOutcome };
80
+ }
81
+ switch (node) {
82
+ case 'implement': {
83
+ const done = last('implemented');
84
+ if (!done) return { outcome: null, detail: null };
85
+ const evidence = last('off-diff-evidence');
86
+ const covered = num(evidence?.coveredEvidence); const required = num(evidence?.requiredEvidence);
87
+ const proof = covered !== null && required !== null ? ` · 증거 ${covered}/${required}` : '';
88
+ return done.ok === false ? { outcome: 'fail', detail: '구현 실패' } : { outcome: 'pass', detail: `구현 완료${proof}` };
89
+ }
90
+ case 'gate': {
91
+ const gated = last('gated');
92
+ if (!gated) return { outcome: null, detail: null };
93
+ return gated.passed === true ? { outcome: 'pass', detail: '게이트 통과' } : { outcome: 'fail', detail: '게이트 실패' };
94
+ }
95
+ case 'regate': {
96
+ const gated = last('gate.postsync');
97
+ if (!gated) return { outcome: null, detail: null };
98
+ return gated.passed === true ? { outcome: 'pass', detail: 'main 동기화 뒤 재게이트 통과' } : { outcome: 'fail', detail: '재게이트 실패' };
99
+ }
100
+ case 'review': {
101
+ const review = last('reviewed');
102
+ if (!review) return { outcome: null, detail: null };
103
+ const verdict = str(review.verdict) ?? '?';
104
+ const mustFix = num(review.mustFix);
105
+ const converged = window.some((entry) => entry.event === 'review-reflect' && entry.data.converged === true);
106
+ const base = `리뷰 ${verdict}${mustFix !== null ? ` · 꼭 고칠 것 ${mustFix}` : ''}`;
107
+ if (converged) return { outcome: 'pass', detail: `${base} · 반박 수렴` };
108
+ return { outcome: verdict === 'fail' ? 'fail' : 'pass', detail: base };
109
+ }
110
+ case 'rework': {
111
+ const budget = last('rework-budget');
112
+ const verdict = str(budget?.verdict);
113
+ return { outcome: 'pass', detail: verdict ? `재작업 예산 ${verdict}` : '재작업' };
114
+ }
115
+ case 'main-sync': {
116
+ const sync = last('pre-pr-sync');
117
+ if (!sync) return { outcome: null, detail: null };
118
+ const status = str(sync.status) ?? '?';
119
+ return { outcome: status === 'merged' || status === 'up-to-date' ? 'pass' : 'fail', detail: `main 동기화 ${status}` };
120
+ }
121
+ case 'open-pr': {
122
+ const opened = last('pr-opened');
123
+ const decision = str(near('merge-decision')?.decision);
124
+ if (!opened) return { outcome: null, detail: decision ? `병합 결정 ${decision}` : null };
125
+ const number = num(opened.number);
126
+ return { outcome: 'pass', detail: `PR${number !== null ? ` #${number}` : ''} 열림${decision ? ` · 병합 결정 ${decision}` : ''}` };
127
+ }
128
+ default:
129
+ return { outcome: null, detail: null };
130
+ }
131
+ }
132
+
133
+ export function buildRunTraversal(runId: string, entries: readonly RunLedgerEntry[]): RunTraversalWire {
134
+ const sorted = [...entries].map((entry, index) => ({ entry, index }))
135
+ .sort((a, b) => (ms(a.entry.timestamp) - ms(b.entry.timestamp)) || a.index - b.index).map(({ entry }) => entry);
136
+ const terminal = [...sorted].reverse().find((entry) => entry.event === 'run-status' && str(entry.data.runStatus));
137
+ const status = terminal ? str(terminal.data.runStatus)! : 'running';
138
+ const entryIndexes = sorted.map((entry, index) => entry.event === 'pipeline-node-entry' && str(entry.data.node) ? index : -1).filter((index) => index >= 0);
139
+ const visits = new Map<string, number>();
140
+ const steps: TraversalStepWire[] = entryIndexes.map((at, position) => {
141
+ const entry = sorted[at]!;
142
+ const node = str(entry.data.node)!;
143
+ const nextAt = entryIndexes[position + 1];
144
+ const window = sorted.slice(at + 1, nextAt ?? sorted.length);
145
+ const budget = [...sorted.slice(Math.max(0, at - 3), at)].reverse()
146
+ .find((candidate) => candidate.event === 'graph-visit-budget' && candidate.data.node === node);
147
+ const visit = num(budget?.data.visits) ?? (visits.get(node) ?? 0) + 1;
148
+ visits.set(node, visit);
149
+ const prevAt = position > 0 ? entryIndexes[position - 1]! : -1;
150
+ let { outcome, detail } = judgeVisit(node, window, sorted.slice(prevAt + 1, at));
151
+ const isLast = nextAt === undefined;
152
+ if (!isLast && outcome === null) outcome = 'pass';
153
+ if (isLast && terminal) outcome = TERMINAL_FAIL.has(status) ? 'fail' : (outcome ?? 'pass');
154
+ const endIso = nextAt !== undefined ? sorted[nextAt]!.timestamp : terminal?.timestamp;
155
+ const duration = ms(endIso) - ms(entry.timestamp);
156
+ return {
157
+ node,
158
+ round: num(entry.data.round),
159
+ outcome,
160
+ at: entry.timestamp ?? '',
161
+ durationMs: Number.isFinite(duration) && duration >= 0 ? duration : null,
162
+ visit,
163
+ maxVisits: num(budget?.data.maxVisits),
164
+ detail,
165
+ };
166
+ });
167
+
168
+ const contract = sorted.find((entry) => entry.event === 'graph-run-contract-resolved')?.data;
169
+ const pr = [...sorted].reverse().find((entry) => entry.event === 'pr-opened')?.data;
170
+ return {
171
+ runId,
172
+ graphId: str(contract?.graphId) ?? str(sorted.find((entry) => entry.event === 'pipeline-node-entry')?.data.graphId),
173
+ title: titleOf(sorted),
174
+ status,
175
+ stage: terminal ? str(terminal.data.stage) : null,
176
+ startedAt: sorted[0]?.timestamp ?? null,
177
+ endedAt: terminal?.timestamp ?? null,
178
+ prNumber: num(pr?.number),
179
+ substrate: str(contract?.actualSubstrate),
180
+ steps,
181
+ ledgerEvents: sorted.length,
182
+ };
183
+ }
184
+
185
+ export interface TraversalDeps {
186
+ loadLedger?: (runId: string) => RunLedgerEntry[] | null;
187
+ }
188
+
189
+ const USAGE = 'usage: GET /v1/harness/run-traversal?runId=<runId>';
190
+
191
+ /** GET /v1/harness/run-traversal?runId= — ordered node steps of one harness run. */
192
+ export function handleHarnessRunTraversalGet(req: Request, deps: TraversalDeps = {}): Response {
193
+ const runId = new URL(req.url).searchParams.get('runId')?.trim() ?? '';
194
+ if (!runId || /[\\/]|\.\./.test(runId)) return Response.json({ error: USAGE }, { status: 400 });
195
+ let entries: RunLedgerEntry[] | null;
196
+ try {
197
+ entries = deps.loadLedger
198
+ ? deps.loadLedger(runId)
199
+ : loadRunLedger(runId, runLedgerDir()) ?? loadFederatedRunLedger(runId);
200
+ } catch (error) {
201
+ return Response.json({ error: 'ledger-unreadable', reason: error instanceof Error ? error.message.slice(0, 200) : 'unknown' }, { status: 422 });
202
+ }
203
+ if (!entries) return Response.json({ error: 'run-not-found', runId }, { status: 404 });
204
+ return Response.json(buildRunTraversal(runId, entries));
205
+ }