@maycombhq/atticus 2.0.0-beta.18 → 2.0.0-beta.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 (270) hide show
  1. package/dist/bin.js +8 -4
  2. package/dist/{chunk-MUSJJ6CQ.js → chunk-265LWMOA.js} +16 -16
  3. package/dist/chunk-26WBY34Y.js +37 -0
  4. package/dist/{chunk-6H5JG5GL.js → chunk-26Z7OGQW.js} +63 -30
  5. package/dist/{chunk-4REDBQOZ.js → chunk-2D7PWSOK.js} +20 -20
  6. package/dist/{chunk-YPH6ZJ6R.js → chunk-2HH3UCZM.js} +4 -4
  7. package/dist/{chunk-RTUUX7PX.js → chunk-2HTEMYTB.js} +12 -26
  8. package/dist/{chunk-EHQUY26D.js → chunk-2UCRFXFZ.js} +11 -11
  9. package/dist/{chunk-ILDGIXNH.js → chunk-2V2ZPQQU.js} +81 -13
  10. package/dist/{chunk-YSEZQ6D3.js → chunk-36RY7ZXM.js} +107 -64
  11. package/dist/{chunk-QXFDM2FZ.js → chunk-37WE3LS2.js} +3 -3
  12. package/dist/{chunk-BAXM3JFQ.js → chunk-3AQ2R5NL.js} +1212 -754
  13. package/dist/chunk-3G6S7LAA.js +43 -0
  14. package/dist/{chunk-7TOAS7KI.js → chunk-3J5I7ZOU.js} +4 -4
  15. package/dist/{chunk-TAKAEJRD.js → chunk-3LZ6P5EP.js} +1 -1
  16. package/dist/chunk-3SSTNB4E.js +191 -0
  17. package/dist/chunk-3TD6S4TC.js +49 -0
  18. package/dist/{chunk-UUL6ZGMU.js → chunk-3UTYPHN3.js} +1 -1
  19. package/dist/{chunk-6AP2ZUUM.js → chunk-3WON4G3M.js} +2 -2
  20. package/dist/{chunk-AXY6LBW4.js → chunk-42EPP7OB.js} +12 -12
  21. package/dist/{chunk-QKMDT7VU.js → chunk-455SYIIE.js} +2 -2
  22. package/dist/{chunk-MFUFB3LN.js → chunk-4B6BT75O.js} +110 -20
  23. package/dist/{chunk-75MYLZSB.js → chunk-4CLK2RJO.js} +7 -7
  24. package/dist/chunk-4CYTJ5TZ.js +38 -0
  25. package/dist/{chunk-IH7RANGB.js → chunk-4D62IZII.js} +5 -5
  26. package/dist/{chunk-2QMZOCWN.js → chunk-4FOAYLI4.js} +14 -7
  27. package/dist/{chunk-V3UEPUHT.js → chunk-4KNOJC6J.js} +294 -215
  28. package/dist/{chunk-O6ILVBYB.js → chunk-4PYC2UGC.js} +1 -1
  29. package/dist/{chunk-EZBAS5BL.js → chunk-4SLBCEPD.js} +82 -50
  30. package/dist/{chunk-VIRTPODJ.js → chunk-4TKVU2I2.js} +6 -14
  31. package/dist/{chunk-IVCDU443.js → chunk-4WXRPQDE.js} +281 -51
  32. package/dist/{chunk-RMEEHH24.js → chunk-5CMPVC3N.js} +24 -24
  33. package/dist/chunk-5G7ILBUG.js +66 -0
  34. package/dist/{chunk-Z75XDWQR.js → chunk-5PV7S2ZP.js} +3 -3
  35. package/dist/{chunk-WWPC2AZ2.js → chunk-5QUOPNLN.js} +4 -4
  36. package/dist/{chunk-7OVH2L7L.js → chunk-5TDRBOAV.js} +5 -5
  37. package/dist/{chunk-YXUIUOQZ.js → chunk-5WERBKLE.js} +199 -63
  38. package/dist/{chunk-6WNNXEG6.js → chunk-62DLJSLV.js} +8 -8
  39. package/dist/{chunk-KCQS52TH.js → chunk-64AADMTW.js} +99 -25
  40. package/dist/{chunk-KLRUXRPN.js → chunk-67BP7HXL.js} +17 -17
  41. package/dist/chunk-6DQYZU2W.js +379 -0
  42. package/dist/{chunk-RONDYDMQ.js → chunk-6EXDI5GI.js} +51 -34
  43. package/dist/chunk-6FNAKCET.js +38 -0
  44. package/dist/{chunk-6SNEA73E.js → chunk-75ASLHBT.js} +21 -15
  45. package/dist/{chunk-EFT4KM6K.js → chunk-75ZOZFGH.js} +18 -18
  46. package/dist/{chunk-B3KW7EQW.js → chunk-77O633L5.js} +12 -12
  47. package/dist/{chunk-TLDL2ZZI.js → chunk-7AB6OWTV.js} +3 -3
  48. package/dist/{chunk-R5YXY6NT.js → chunk-7CZHUZBL.js} +25 -25
  49. package/dist/{chunk-F4D4G2A6.js → chunk-7FHNFCHK.js} +306 -125
  50. package/dist/{chunk-CC5RNYZF.js → chunk-7FUIVIBG.js} +19 -9
  51. package/dist/{chunk-UBTOSSNK.js → chunk-7QPCGTPS.js} +21 -21
  52. package/dist/{chunk-CDLTRJ4N.js → chunk-A2G72UI4.js} +7 -7
  53. package/dist/{chunk-YGSOQ5LX.js → chunk-AE4P57KK.js} +23 -17
  54. package/dist/{chunk-PGQL5YGH.js → chunk-AHW3RY3X.js} +7 -7
  55. package/dist/chunk-AM6FP6JW.js +43 -0
  56. package/dist/{chunk-DYNC6BAQ.js → chunk-AOCWFA7T.js} +93 -10
  57. package/dist/{chunk-CYX36GKZ.js → chunk-API5TOUB.js} +1 -1
  58. package/dist/{chunk-BJ5T3KDF.js → chunk-AZDITBCT.js} +3 -3
  59. package/dist/{chunk-U6GZSUMC.js → chunk-BA2HUMDN.js} +25 -3
  60. package/dist/chunk-BA3U7ALS.js +99 -0
  61. package/dist/{chunk-63565QTF.js → chunk-BC6KKZ67.js} +5 -5
  62. package/dist/{chunk-XO5RQKNM.js → chunk-BEPCKDCP.js} +1843 -577
  63. package/dist/{chunk-KUH62AQ3.js → chunk-BPPU3PW7.js} +4 -4
  64. package/dist/{chunk-52ULYRV4.js → chunk-BVWWPBKD.js} +18 -18
  65. package/dist/{chunk-R2FGEBIU.js → chunk-BXZ4PKIT.js} +13 -11
  66. package/dist/{chunk-4BU26GFB.js → chunk-C7CEK3KW.js} +4 -4
  67. package/dist/chunk-CFH3QH26.js +29 -0
  68. package/dist/chunk-CG6CULP6.js +170 -0
  69. package/dist/chunk-CLD4RVTA.js +45 -0
  70. package/dist/{chunk-BU543ZEC.js → chunk-CNPY4NTZ.js} +13 -13
  71. package/dist/{chunk-PLBIWEPV.js → chunk-CNU5Q2D5.js} +32 -31
  72. package/dist/{chunk-JTYYEYRR.js → chunk-COYN3UAN.js} +124 -21
  73. package/dist/{chunk-TNCROWPQ.js → chunk-CPK7QSBG.js} +10 -10
  74. package/dist/{chunk-43E6XX67.js → chunk-D2WSW4GU.js} +1 -1
  75. package/dist/{chunk-SHEUEHKC.js → chunk-D57MFJPP.js} +3 -3
  76. package/dist/chunk-D5KV5PSK.js +45 -0
  77. package/dist/chunk-D5MASS4I.js +48 -0
  78. package/dist/{chunk-TQYBXDVH.js → chunk-DCWN3HFV.js} +1 -1
  79. package/dist/{chunk-FF4IGX4Z.js → chunk-DDJPM5YX.js} +2 -2
  80. package/dist/chunk-DN4A2Q2O.js +57 -0
  81. package/dist/{chunk-4ZCFCIEL.js → chunk-DUPZPDCJ.js} +52 -16
  82. package/dist/{chunk-CUJ5TIEB.js → chunk-E2VXBSIR.js} +4 -4
  83. package/dist/{chunk-BX47X5DJ.js → chunk-E57AOFYN.js} +13 -13
  84. package/dist/{chunk-SSTI7GAV.js → chunk-ECLXZWAG.js} +12 -12
  85. package/dist/{chunk-KYK3TA4Q.js → chunk-EDZ45WIR.js} +14 -14
  86. package/dist/chunk-EFCQSMXB.js +37 -0
  87. package/dist/{chunk-YC2K3FJG.js → chunk-EZIN5E5B.js} +8 -8
  88. package/dist/{chunk-MIZNACDI.js → chunk-F6H2RJ3N.js} +1 -1
  89. package/dist/{chunk-EMZVHDJP.js → chunk-FAJDKNSD.js} +7 -3
  90. package/dist/chunk-FGAGWYZB.js +47 -0
  91. package/dist/{chunk-KBGQ3IAM.js → chunk-FLVZFTLQ.js} +18 -18
  92. package/dist/{chunk-2NNXLH53.js → chunk-FQ6WH6PB.js} +22 -7
  93. package/dist/{chunk-QWSLMVHI.js → chunk-FRHD22WM.js} +61 -23
  94. package/dist/{chunk-5MEN4SM5.js → chunk-FVMPFCDS.js} +4 -4
  95. package/dist/{chunk-KLRZKW7Y.js → chunk-FY3QIRTL.js} +3 -3
  96. package/dist/{chunk-B3SWPNJF.js → chunk-FYBF5MB2.js} +13 -11
  97. package/dist/{chunk-CVZCW3NI.js → chunk-G5YJBNDZ.js} +14 -5
  98. package/dist/{chunk-U6EPS5PF.js → chunk-G6IQYM25.js} +5 -5
  99. package/dist/chunk-G6X4YL4D.js +161 -0
  100. package/dist/{chunk-DOSX3MRW.js → chunk-GC2VK3JT.js} +25 -25
  101. package/dist/{chunk-EEWBTFRP.js → chunk-GCS3FZOB.js} +27 -3
  102. package/dist/{chunk-6H6OPNEP.js → chunk-GDZB57IU.js} +5 -5
  103. package/dist/chunk-GIFMBCTW.js +221 -0
  104. package/dist/{chunk-X5WYPIH3.js → chunk-GK4PYVEC.js} +18 -8
  105. package/dist/{chunk-VBSJ5NRB.js → chunk-GNWAOVFQ.js} +5 -5
  106. package/dist/{chunk-TXFDQRBL.js → chunk-GS76RXQE.js} +11 -11
  107. package/dist/{chunk-U3YC4RGC.js → chunk-GSGIZ2OO.js} +23 -23
  108. package/dist/chunk-GUYL7RMK.js +60 -0
  109. package/dist/{chunk-AZYBHPV4.js → chunk-GX74YWR7.js} +14 -13
  110. package/dist/{chunk-YY7VP7KW.js → chunk-HE3HRDNZ.js} +2 -2
  111. package/dist/{chunk-Z7HYYDZM.js → chunk-HGHRSF3E.js} +14 -13
  112. package/dist/{chunk-Q2X4IREC.js → chunk-HK3VOHIE.js} +106 -36
  113. package/dist/{chunk-P5JYMO7Y.js → chunk-HS4BGB4G.js} +22 -22
  114. package/dist/{chunk-IOW6BF3U.js → chunk-HSZMPH5T.js} +798 -141
  115. package/dist/{chunk-L3T33FA7.js → chunk-HWD4JPU7.js} +4 -2
  116. package/dist/{chunk-7NYCH5HY.js → chunk-HXXP2NTE.js} +6 -6
  117. package/dist/{chunk-7S7OFVIU.js → chunk-I2A3Z5AK.js} +4 -4
  118. package/dist/{chunk-WQSDKX7M.js → chunk-ICHDSWEY.js} +7 -7
  119. package/dist/chunk-IFFRGC3N.js +31 -0
  120. package/dist/{chunk-HMZQAIR5.js → chunk-IFHDV6KQ.js} +24 -17
  121. package/dist/chunk-J2O4LFVI.js +80 -0
  122. package/dist/chunk-J5AAZ6OJ.js +161 -0
  123. package/dist/{chunk-CVRWAG76.js → chunk-JAABT3DA.js} +3 -3
  124. package/dist/chunk-JITJP64K.js +34 -0
  125. package/dist/chunk-JKIGL3XQ.js +162 -0
  126. package/dist/chunk-JRVNXLZP.js +615 -0
  127. package/dist/chunk-JRXHON4E.js +41 -0
  128. package/dist/{chunk-XFCK5B27.js → chunk-JS6WMC3J.js} +8 -8
  129. package/dist/chunk-JU4QNJSR.js +62 -0
  130. package/dist/{chunk-IPSFMTK2.js → chunk-JYMIB5XY.js} +152 -42
  131. package/dist/{chunk-L336AJCZ.js → chunk-JYTIHTIK.js} +8 -6
  132. package/dist/{chunk-VEJYZEPC.js → chunk-K332XW7K.js} +135 -16
  133. package/dist/{chunk-KCF7TFOE.js → chunk-K6JB6XTX.js} +11 -11
  134. package/dist/{chunk-LFIIX4OV.js → chunk-KDAHXQIE.js} +46 -34
  135. package/dist/chunk-KYZMRSPS.js +93 -0
  136. package/dist/chunk-L4CESDPB.js +22 -0
  137. package/dist/{chunk-3LR4522O.js → chunk-LEBR5NHW.js} +14 -14
  138. package/dist/{chunk-US3QZU23.js → chunk-LIJJCBQI.js} +67 -7
  139. package/dist/{chunk-XLHL6WTI.js → chunk-LIRVU3HW.js} +16 -16
  140. package/dist/{chunk-BAT4CBB7.js → chunk-LN77DXBM.js} +19 -10
  141. package/dist/{chunk-E5FM44EJ.js → chunk-LNWEWYCM.js} +2474 -315
  142. package/dist/{chunk-TX4OGZKU.js → chunk-LO3BLMVG.js} +3 -3
  143. package/dist/{chunk-RUOGHYSK.js → chunk-LPV6T362.js} +5 -5
  144. package/dist/{chunk-2T5CQH4R.js → chunk-LQH4P45K.js} +1 -1
  145. package/dist/chunk-LU4MQVAL.js +41 -0
  146. package/dist/{chunk-HW4RRIJA.js → chunk-LWDNKE4X.js} +12 -10
  147. package/dist/chunk-M4H2UG4R.js +218 -0
  148. package/dist/{chunk-CANMOOE6.js → chunk-M6D2U3YM.js} +3 -3
  149. package/dist/{chunk-AB5E5HCN.js → chunk-MD7KUEJ3.js} +14 -272
  150. package/dist/{chunk-ENTAAEDK.js → chunk-MMZUN7ZO.js} +20 -20
  151. package/dist/{chunk-L4MHJG3I.js → chunk-MZGRDONP.js} +46 -36
  152. package/dist/{chunk-IYI2S6BB.js → chunk-N3RUGTGV.js} +18 -18
  153. package/dist/{chunk-WZRCGGXG.js → chunk-NCB6656Z.js} +7 -7
  154. package/dist/{chunk-SBE773JU.js → chunk-NRQ65LTQ.js} +5 -5
  155. package/dist/{chunk-NRPMPZWJ.js → chunk-NTAVOWYD.js} +117 -18
  156. package/dist/{chunk-UJBZS5I7.js → chunk-NYOABJNK.js} +36 -27
  157. package/dist/{chunk-LXAPJ3BL.js → chunk-O4IKAIZ2.js} +24 -20
  158. package/dist/{chunk-MMB2OHUY.js → chunk-ODMBPDDV.js} +48 -13
  159. package/dist/chunk-OKUB6RJA.js +533 -0
  160. package/dist/{chunk-MKFYQQ5Q.js → chunk-OMPENNWY.js} +4 -1
  161. package/dist/chunk-OPQZISG3.js +25 -0
  162. package/dist/{chunk-ROJMXG4E.js → chunk-OV57AHCG.js} +24 -24
  163. package/dist/{chunk-7KGXDIWV.js → chunk-OZJ3JLYI.js} +9 -8
  164. package/dist/{chunk-G5BYKJIM.js → chunk-P2VBXX7N.js} +11 -10
  165. package/dist/{chunk-YK6CSODO.js → chunk-PAINMSID.js} +4353 -1106
  166. package/dist/chunk-PKJAV25S.js +43 -0
  167. package/dist/{chunk-R5MNPIMD.js → chunk-PNQLCRSS.js} +4 -4
  168. package/dist/{chunk-3SMMFQED.js → chunk-PQH56YF6.js} +37 -5
  169. package/dist/chunk-PRTBPXYA.js +50 -0
  170. package/dist/{chunk-FF4ZMLSL.js → chunk-PRW3HXM3.js} +2 -2
  171. package/dist/chunk-PRZ7KMWL.js +42 -0
  172. package/dist/{chunk-IFV6MKYV.js → chunk-PTQ3ZALZ.js} +42 -4
  173. package/dist/{chunk-ELQSLAP6.js → chunk-PUEWDYGP.js} +2 -2
  174. package/dist/{chunk-F2ZCZYIK.js → chunk-Q26LDIUV.js} +12 -11
  175. package/dist/chunk-Q46QDDMF.js +277 -0
  176. package/dist/chunk-QBNWF5ZU.js +19 -0
  177. package/dist/{chunk-ANETFNTV.js → chunk-QE527XRY.js} +24 -21
  178. package/dist/{chunk-QNMLLYTN.js → chunk-QHJJMZLX.js} +397 -32
  179. package/dist/chunk-QJCJOMW3.js +425 -0
  180. package/dist/{chunk-3HOONZUE.js → chunk-QKMRFNMA.js} +15 -15
  181. package/dist/{chunk-7S6VJ643.js → chunk-QOOEYDNJ.js} +101 -13
  182. package/dist/{chunk-4YNKN4DF.js → chunk-QSIDVFB7.js} +20 -18
  183. package/dist/{chunk-2IT7SIJY.js → chunk-RA33EYS4.js} +19 -18
  184. package/dist/{chunk-P7NV3FEJ.js → chunk-RALY3E5U.js} +4 -4
  185. package/dist/{chunk-6NESNLVU.js → chunk-S2RY3BOD.js} +35 -21
  186. package/dist/chunk-SB5GRUFM.js +54 -0
  187. package/dist/{chunk-EUQHM2CH.js → chunk-SH27FYPF.js} +18 -18
  188. package/dist/chunk-T74WQQGS.js +40 -0
  189. package/dist/{chunk-LGDW4G73.js → chunk-TGVVUKTG.js} +2 -2
  190. package/dist/chunk-THN26IEF.js +48 -0
  191. package/dist/{chunk-ZV2VCBMQ.js → chunk-TJH2EUAZ.js} +18 -12
  192. package/dist/{chunk-NGT5LLSE.js → chunk-TQ4TG57V.js} +6 -6
  193. package/dist/{chunk-WEDXFJPG.js → chunk-TQTRWENN.js} +12 -12
  194. package/dist/{chunk-ZRWS54RI.js → chunk-TSM6T6GQ.js} +23 -23
  195. package/dist/{chunk-KET7KOVE.js → chunk-TW3KOSQK.js} +15 -7
  196. package/dist/{chunk-RBXDHCN7.js → chunk-TWQRPM76.js} +2 -2
  197. package/dist/{chunk-IO6DQCZX.js → chunk-TYD6I4CZ.js} +4 -4
  198. package/dist/{chunk-5NLJH6R4.js → chunk-U3NBK4EU.js} +56 -17
  199. package/dist/{chunk-U542BJME.js → chunk-UDXWIPCB.js} +7 -7
  200. package/dist/{chunk-OSB6AKF2.js → chunk-UEVZZZU2.js} +4 -4
  201. package/dist/{chunk-5ZV7BK22.js → chunk-UF4UHCIG.js} +2 -2
  202. package/dist/{chunk-PHJOOL6U.js → chunk-UFE36WJH.js} +4 -4
  203. package/dist/{chunk-IHRP4OYV.js → chunk-URIKUID7.js} +12 -12
  204. package/dist/chunk-UYLBRRFK.js +1106 -0
  205. package/dist/chunk-UZACEG2C.js +515 -0
  206. package/dist/{chunk-LR4XBH4L.js → chunk-VAMHOHUU.js} +12 -12
  207. package/dist/{chunk-S5PSGAQ2.js → chunk-VUVGX5L2.js} +1 -1
  208. package/dist/{chunk-2TG2QGKW.js → chunk-VYOCPTUB.js} +18 -16
  209. package/dist/{chunk-VTEDQ6GY.js → chunk-WAZEXEJH.js} +175 -3
  210. package/dist/{chunk-UTSFKZ72.js → chunk-WCEF7H6L.js} +3 -3
  211. package/dist/{chunk-DXPUYG5L.js → chunk-WEOMJHPV.js} +12 -12
  212. package/dist/{chunk-NFKBU2UH.js → chunk-WFU77T3A.js} +5 -5
  213. package/dist/chunk-WIWTJMMR.js +292 -0
  214. package/dist/{chunk-MRFSUAJH.js → chunk-WNTIOBOG.js} +18 -18
  215. package/dist/{chunk-UROTYIGJ.js → chunk-X2CUXTWU.js} +6 -6
  216. package/dist/chunk-X7EMBM5X.js +34 -0
  217. package/dist/{chunk-OO5TKEAG.js → chunk-XEQCQO66.js} +8 -8
  218. package/dist/{chunk-NBQO75XT.js → chunk-XG4WJHLQ.js} +17 -1
  219. package/dist/{chunk-ZQC7MNQB.js → chunk-XOL3GFW2.js} +101 -30
  220. package/dist/{chunk-GXDATVSY.js → chunk-XQ3YZ4QW.js} +22 -22
  221. package/dist/{chunk-GTW54D3D.js → chunk-Y6K3CJBU.js} +726 -88
  222. package/dist/{chunk-CQCAJI6G.js → chunk-YAH5IQAL.js} +5 -5
  223. package/dist/{chunk-U6TD7UOH.js → chunk-YBOVNMHC.js} +51 -14
  224. package/dist/{chunk-OJICVOZH.js → chunk-YF5DZCPN.js} +79 -39
  225. package/dist/chunk-YIGBJNH4.js +37 -0
  226. package/dist/{chunk-7C7OZARH.js → chunk-YJ3TBOTK.js} +25 -25
  227. package/dist/{chunk-HXFSM4LG.js → chunk-YKTE2RU3.js} +2 -2
  228. package/dist/{chunk-7IM4BVVH.js → chunk-YO2PKTXT.js} +268 -36
  229. package/dist/{chunk-Z3UU7XV6.js → chunk-YTTNTQOX.js} +15 -15
  230. package/dist/{chunk-2ZICHO3V.js → chunk-YWAKDNMH.js} +183 -33
  231. package/dist/{chunk-OXOSJXOI.js → chunk-YZDZOEV4.js} +4 -4
  232. package/dist/{chunk-MRCETHW5.js → chunk-Z3QFH7YG.js} +4 -4
  233. package/dist/{chunk-5KZTRD4V.js → chunk-Z6VLC5X4.js} +17 -17
  234. package/dist/{chunk-EDURU5DF.js → chunk-ZFQMMU5Y.js} +2 -2
  235. package/dist/{chunk-3GLDB4IT.js → chunk-ZKYRDV7A.js} +12 -12
  236. package/dist/{chunk-GBGJ6WSO.js → chunk-ZLS6PU2O.js} +3 -3
  237. package/dist/{chunk-JZOOX3PQ.js → chunk-ZOSD4FXV.js} +3 -3
  238. package/dist/chunk-ZPDDEVQX.js +47 -0
  239. package/dist/{chunk-2TTSX7NB.js → chunk-ZQLRCA2V.js} +7 -7
  240. package/dist/{chunk-ESP3RUUR.js → chunk-ZX7235KR.js} +128 -54
  241. package/dist/{chunk-MH4XG5XS.js → chunk-ZZFHLRQW.js} +18 -16
  242. package/dist/files.json +559 -439
  243. package/dist/packages/sdk/npm/index.js +4679 -2672
  244. package/dist/packages/ui/dist/assets/{elk-api-BuFBvNyV.js → elk-api-CowTnhEJ.js} +2 -2
  245. package/dist/packages/ui/dist/assets/index-Dw20IRhQ.css +1 -0
  246. package/dist/packages/ui/dist/assets/index-z2wv0d5d.js +47 -0
  247. package/dist/packages/ui/dist/index.html +2 -2
  248. package/package.json +3 -3
  249. package/dist/chunk-25TJC2CL.js +0 -48
  250. package/dist/chunk-3Q5RHGCA.js +0 -47
  251. package/dist/chunk-4W7FOGAB.js +0 -41
  252. package/dist/chunk-6AF3CVJT.js +0 -48
  253. package/dist/chunk-A3KEXFQS.js +0 -34
  254. package/dist/chunk-BAUINGGJ.js +0 -41
  255. package/dist/chunk-BOFUCRJX.js +0 -47
  256. package/dist/chunk-GGKGILIP.js +0 -54
  257. package/dist/chunk-IKN6C67W.js +0 -53
  258. package/dist/chunk-K7MYVIFK.js +0 -54
  259. package/dist/chunk-KHA5LVPE.js +0 -34
  260. package/dist/chunk-LSXGM6MB.js +0 -37
  261. package/dist/chunk-M54TDRM3.js +0 -29
  262. package/dist/chunk-M5GF4NAB.js +0 -45
  263. package/dist/chunk-UTZGBCXE.js +0 -38
  264. package/dist/chunk-UVTSMBM4.js +0 -43
  265. package/dist/chunk-XXE3S3OG.js +0 -44
  266. package/dist/chunk-YJRQCUVJ.js +0 -43
  267. package/dist/chunk-ZFTK4BDN.js +0 -41
  268. package/dist/chunk-ZMARAW37.js +0 -38
  269. package/dist/packages/ui/dist/assets/index-C5QnK82A.css +0 -1
  270. package/dist/packages/ui/dist/assets/index-CQ0a1cBg.js +0 -29
@@ -3,17 +3,14 @@ import { fileURLToPath as __atticusFileURLToPath } from "node:url";
3
3
  var require = __atticusCreateRequire(import.meta.url);
4
4
  var __atticusUrl = (p) => new URL("./" + p, import.meta.url).href;
5
5
  var __atticusPath = (p) => __atticusFileURLToPath(__atticusUrl(p));
6
- import {
7
- listRuns,
8
- runTranscript
9
- } from "./chunk-U6GZSUMC.js";
10
6
  import {
11
7
  historyEntry,
12
8
  listHistory
13
- } from "./chunk-6AP2ZUUM.js";
9
+ } from "./chunk-3WON4G3M.js";
10
+ import "./chunk-JYTIHTIK.js";
14
11
  import {
15
- uiFactsRoutes
16
- } from "./chunk-7S6VJ643.js";
12
+ uiComponentTypeRoutes
13
+ } from "./chunk-CG6CULP6.js";
17
14
  import {
18
15
  answerAudit,
19
16
  auditLines,
@@ -21,38 +18,52 @@ import {
21
18
  precisionOf,
22
19
  prepareAudit,
23
20
  writeAudit
24
- } from "./chunk-CC5RNYZF.js";
25
- import "./chunk-BAT4CBB7.js";
26
- import "./chunk-5NLJH6R4.js";
27
- import "./chunk-QKMDT7VU.js";
28
- import "./chunk-4W7FOGAB.js";
21
+ } from "./chunk-7FUIVIBG.js";
22
+ import {
23
+ listRuns,
24
+ partStates,
25
+ runTranscript
26
+ } from "./chunk-BA2HUMDN.js";
27
+ import {
28
+ uiCiteRoutes
29
+ } from "./chunk-WCEF7H6L.js";
30
+ import {
31
+ isHandoffStep
32
+ } from "./chunk-4Q3NN7VF.js";
29
33
  import {
30
34
  estimateOf
31
- } from "./chunk-NFKBU2UH.js";
35
+ } from "./chunk-WFU77T3A.js";
36
+ import {
37
+ changeMapBody,
38
+ changeMapOf,
39
+ targetFlowsOf
40
+ } from "./chunk-GNWAOVFQ.js";
32
41
  import {
33
42
  changeArchive,
34
43
  changeHandoff,
35
44
  changeNew,
45
+ changeReleasePrs,
36
46
  changeSync
37
- } from "./chunk-JTYYEYRR.js";
47
+ } from "./chunk-COYN3UAN.js";
38
48
  import {
39
- uiCiteRoutes
40
- } from "./chunk-UTSFKZ72.js";
49
+ isSandbox,
50
+ learnCheck,
51
+ learnStart,
52
+ lessonsFor,
53
+ readProgress,
54
+ sandboxCreate
55
+ } from "./chunk-ZQLRCA2V.js";
41
56
  import {
42
- HttpError,
43
- badRequest,
44
- conflict,
45
- notFound,
46
- startServer,
47
- strParam
48
- } from "./chunk-Q2X4IREC.js";
49
- import "./chunk-L336AJCZ.js";
57
+ defencesScan,
58
+ evalRun
59
+ } from "./chunk-FVMPFCDS.js";
50
60
  import {
51
- isHandoffStep
52
- } from "./chunk-4Q3NN7VF.js";
61
+ listReports,
62
+ readQuality
63
+ } from "./chunk-FYBF5MB2.js";
53
64
  import {
54
65
  conformanceCommand
55
- } from "./chunk-CVZCW3NI.js";
66
+ } from "./chunk-G5YJBNDZ.js";
56
67
  import {
57
68
  outcomeList,
58
69
  outcomeRecord,
@@ -61,162 +72,232 @@ import {
61
72
  releaseNotesCommand,
62
73
  releasePlanCommand,
63
74
  releaseSecurityCommand
64
- } from "./chunk-BJ5T3KDF.js";
75
+ } from "./chunk-AZDITBCT.js";
65
76
  import {
66
- changeMapBody,
67
- changeMapOf
68
- } from "./chunk-VBSJ5NRB.js";
77
+ readServiceRequests
78
+ } from "./chunk-36RY7ZXM.js";
79
+ import {
80
+ runDone,
81
+ taskRun,
82
+ taskRunNotWaiting
83
+ } from "./chunk-AOCWFA7T.js";
69
84
  import {
70
85
  executorsCheck,
71
86
  todoDone,
72
87
  todoRun,
73
88
  todoShow
74
- } from "./chunk-EDURU5DF.js";
89
+ } from "./chunk-ZFQMMU5Y.js";
75
90
  import {
76
91
  inputsSha256,
77
92
  loadExecutors
78
- } from "./chunk-FF4IGX4Z.js";
93
+ } from "./chunk-DDJPM5YX.js";
79
94
  import {
80
- isSandbox,
81
- learnCheck,
82
- learnStart,
83
- lessonsFor,
84
- readProgress,
85
- sandboxCreate
86
- } from "./chunk-2TTSX7NB.js";
95
+ initiativeStatus,
96
+ readView
97
+ } from "./chunk-2V2ZPQQU.js";
87
98
  import {
88
- defencesScan,
89
- evalRun
90
- } from "./chunk-5MEN4SM5.js";
99
+ DecideError,
100
+ correctDecision,
101
+ decisionsView,
102
+ undoBatch
103
+ } from "./chunk-UYLBRRFK.js";
91
104
  import {
92
- listReports,
93
- readQuality
94
- } from "./chunk-B3SWPNJF.js";
105
+ boxesOf,
106
+ candidatesOf,
107
+ uiLinkRoutes
108
+ } from "./chunk-6DQYZU2W.js";
95
109
  import {
96
- readServiceRequests
97
- } from "./chunk-YSEZQ6D3.js";
110
+ matchDone,
111
+ quotesOf,
112
+ readMatch,
113
+ runMatch
114
+ } from "./chunk-QJCJOMW3.js";
115
+ import "./chunk-3SSTNB4E.js";
116
+ import "./chunk-JRVNXLZP.js";
98
117
  import {
99
- initiativeStatus,
100
- readView
101
- } from "./chunk-ILDGIXNH.js";
118
+ copilotAcpAllowed
119
+ } from "./chunk-LIJJCBQI.js";
120
+ import "./chunk-26Z7OGQW.js";
121
+ import "./chunk-OKUB6RJA.js";
102
122
  import {
103
- taskRun
104
- } from "./chunk-DYNC6BAQ.js";
105
- import "./chunk-GTW54D3D.js";
123
+ filesUnder,
124
+ memoByFiles,
125
+ recheckNow,
126
+ uiFactsRoutes
127
+ } from "./chunk-QOOEYDNJ.js";
106
128
  import {
107
- digestCommand
108
- } from "./chunk-UROTYIGJ.js";
129
+ HttpError,
130
+ badRequest,
131
+ conflict,
132
+ notFound,
133
+ startServer,
134
+ strParam
135
+ } from "./chunk-HK3VOHIE.js";
136
+ import {
137
+ safeMessage,
138
+ withFileLock
139
+ } from "./chunk-THN26IEF.js";
140
+ import "./chunk-LN77DXBM.js";
141
+ import "./chunk-L4CESDPB.js";
142
+ import "./chunk-U3NBK4EU.js";
143
+ import "./chunk-455SYIIE.js";
109
144
  import {
110
145
  reconcileCommand
111
- } from "./chunk-OXOSJXOI.js";
146
+ } from "./chunk-YZDZOEV4.js";
112
147
  import {
113
148
  sourcesAdd,
114
149
  sourcesSync
115
- } from "./chunk-7NYCH5HY.js";
150
+ } from "./chunk-HXXP2NTE.js";
116
151
  import {
117
152
  INPUTS_SOURCE_ID
118
- } from "./chunk-SHEUEHKC.js";
119
- import "./chunk-WWPC2AZ2.js";
153
+ } from "./chunk-D57MFJPP.js";
154
+ import "./chunk-5QUOPNLN.js";
155
+ import {
156
+ readRepos
157
+ } from "./chunk-4TKVU2I2.js";
158
+ import {
159
+ uiOnboardingRoutes
160
+ } from "./chunk-PNQLCRSS.js";
161
+ import "./chunk-3J5I7ZOU.js";
162
+ import {
163
+ uiTrustRoutes
164
+ } from "./chunk-4FOAYLI4.js";
165
+ import {
166
+ contractCheckOf,
167
+ estateJoinOnFile,
168
+ stampOf
169
+ } from "./chunk-GX74YWR7.js";
170
+ import "./chunk-G6IQYM25.js";
171
+ import "./chunk-LPV6T362.js";
172
+ import {
173
+ digestCommand
174
+ } from "./chunk-X2CUXTWU.js";
120
175
  import {
121
176
  baselineCommand,
122
177
  setupSteps
123
- } from "./chunk-X5WYPIH3.js";
178
+ } from "./chunk-GK4PYVEC.js";
179
+ import {
180
+ doctorChecks
181
+ } from "./chunk-HGHRSF3E.js";
182
+ import "./chunk-4B6BT75O.js";
124
183
  import {
125
184
  readMe,
126
185
  writeMe
127
- } from "./chunk-5ZV7BK22.js";
128
- import {
129
- uiOnboardingRoutes
130
- } from "./chunk-R5MNPIMD.js";
186
+ } from "./chunk-UF4UHCIG.js";
131
187
  import {
132
- uiTrustRoutes
133
- } from "./chunk-2QMZOCWN.js";
188
+ cancelTaskRunWait,
189
+ findTaskRunOfRun,
190
+ previewTaskRun,
191
+ raiseTaskPr,
192
+ readTaskRun,
193
+ readyTasksOf,
194
+ runReadyTasks,
195
+ saveTaskRun,
196
+ settleTaskPr,
197
+ stopAllBuilds,
198
+ taskRunOfRun
199
+ } from "./chunk-Y6K3CJBU.js";
200
+ import "./chunk-GIFMBCTW.js";
201
+ import "./chunk-MD7KUEJ3.js";
134
202
  import {
135
- contractCheckOf,
136
- estateJoinOnFile
137
- } from "./chunk-AZYBHPV4.js";
138
- import "./chunk-AB5E5HCN.js";
139
- import "./chunk-LGDW4G73.js";
203
+ checkoutOf
204
+ } from "./chunk-Q46QDDMF.js";
205
+ import "./chunk-TGVVUKTG.js";
140
206
  import {
141
207
  intakeContext
142
- } from "./chunk-NGT5LLSE.js";
143
- import "./chunk-U6EPS5PF.js";
144
- import "./chunk-RUOGHYSK.js";
208
+ } from "./chunk-TQ4TG57V.js";
209
+ import {
210
+ workspaceOpen,
211
+ workspaceSync
212
+ } from "./chunk-IFHDV6KQ.js";
213
+ import "./chunk-LQH4P45K.js";
214
+ import "./chunk-P2VBXX7N.js";
215
+ import {
216
+ archiveGate,
217
+ ciArchiveGate
218
+ } from "./chunk-ZX7235KR.js";
219
+ import {
220
+ traceMatrix
221
+ } from "./chunk-EZIN5E5B.js";
222
+ import "./chunk-3LZ6P5EP.js";
145
223
  import {
146
224
  scanCommand
147
- } from "./chunk-WZRCGGXG.js";
148
- import "./chunk-IO6DQCZX.js";
225
+ } from "./chunk-NCB6656Z.js";
226
+ import "./chunk-TYD6I4CZ.js";
227
+ import {
228
+ readChangeTasks,
229
+ readStory,
230
+ setTaskDone
231
+ } from "./chunk-YWAKDNMH.js";
149
232
  import {
150
233
  catalogRecords,
151
234
  openChanges,
152
235
  systemGuide
153
- } from "./chunk-KLRZKW7Y.js";
154
- import {
155
- doctorChecks
156
- } from "./chunk-Z7HYYDZM.js";
236
+ } from "./chunk-FY3QIRTL.js";
157
237
  import "./chunk-3G4YI4ZU.js";
158
- import "./chunk-MFUFB3LN.js";
159
- import "./chunk-RTUUX7PX.js";
160
- import {
161
- handoffDone,
162
- workspaceOpen,
163
- workspaceSync
164
- } from "./chunk-HMZQAIR5.js";
165
- import "./chunk-2T5CQH4R.js";
166
- import "./chunk-G5BYKJIM.js";
167
- import {
168
- readChangeTasks,
169
- readStory
170
- } from "./chunk-2ZICHO3V.js";
238
+ import "./chunk-2HTEMYTB.js";
171
239
  import {
172
240
  ASSISTANT_FORMATS,
173
241
  readProbes,
174
242
  whatRunsWhere
175
- } from "./chunk-BAXM3JFQ.js";
243
+ } from "./chunk-3AQ2R5NL.js";
176
244
  import {
177
245
  critic,
178
246
  criticStats,
179
247
  findingAction
180
- } from "./chunk-IFV6MKYV.js";
181
- import "./chunk-CDLTRJ4N.js";
248
+ } from "./chunk-PTQ3ZALZ.js";
249
+ import "./chunk-A2G72UI4.js";
182
250
  import {
183
251
  planStories,
184
252
  snowDraft,
185
253
  snowSubmit
186
- } from "./chunk-7S7OFVIU.js";
187
- import "./chunk-MIZNACDI.js";
188
- import {
189
- archiveGate,
190
- ciArchiveGate
191
- } from "./chunk-ESP3RUUR.js";
192
- import {
193
- traceMatrix
194
- } from "./chunk-YC2K3FJG.js";
195
- import "./chunk-TAKAEJRD.js";
254
+ } from "./chunk-I2A3Z5AK.js";
255
+ import "./chunk-F6H2RJ3N.js";
196
256
  import {
257
+ CORRECTIONS_FILE,
258
+ FEATURE_FILE,
197
259
  NEEDS_FILE,
198
260
  PlannerError,
199
261
  SYNC_FILE,
262
+ SnowFillError,
263
+ Stroke,
200
264
  TARGET_DIR,
265
+ addCorrection,
266
+ applyStroke,
201
267
  codeFactRecords,
202
268
  commentOnFinding,
203
269
  conflictTouches,
270
+ draftItems,
271
+ draftProvenance,
272
+ drawnStrokes,
273
+ fillSnowFields,
274
+ loadEstate,
204
275
  markNeed,
205
276
  mergeDelta,
206
277
  plainWhy,
278
+ planSketchWrite,
279
+ readCorrections,
207
280
  readDeltas,
208
281
  readKnowledgeApplies,
209
282
  readNeeds,
210
283
  readStageLoop,
284
+ recordPersonSave,
285
+ removeCorrection,
286
+ renderFeature,
211
287
  setKnowledgeStatus,
288
+ sketchFingerprint,
212
289
  stageComments,
213
- stageGate
214
- } from "./chunk-XO5RQKNM.js";
290
+ stageGate,
291
+ writeSketch
292
+ } from "./chunk-BEPCKDCP.js";
215
293
  import {
294
+ FeaturePlacement,
295
+ STAGES,
216
296
  STAGE_PHASE,
217
297
  fieldNames,
218
298
  rerunOf
219
- } from "./chunk-L3T33FA7.js";
299
+ } from "./chunk-HWD4JPU7.js";
300
+ import "./chunk-5WERBKLE.js";
220
301
  import "./chunk-VOGCSYOI.js";
221
302
  import {
222
303
  layerRules,
@@ -230,8 +311,9 @@ import {
230
311
  readStoryIndex,
231
312
  reviewedGates,
232
313
  roundsOf
233
- } from "./chunk-VEJYZEPC.js";
314
+ } from "./chunk-K332XW7K.js";
234
315
  import {
316
+ ChangeOwnersError,
235
317
  artifactFiles,
236
318
  artifactPhase,
237
319
  changeFolder,
@@ -241,24 +323,27 @@ import {
241
323
  detectConflicts,
242
324
  liveHandoff,
243
325
  loadChangeContext,
244
- requiredArtifacts
245
- } from "./chunk-IOW6BF3U.js";
246
- import "./chunk-43E6XX67.js";
326
+ requiredArtifacts,
327
+ setChangeOwners
328
+ } from "./chunk-HSZMPH5T.js";
329
+ import "./chunk-D2WSW4GU.js";
247
330
  import {
248
331
  fileStepStore,
249
332
  manualAdapter,
250
333
  stepOfProvisional
251
- } from "./chunk-V3UEPUHT.js";
334
+ } from "./chunk-4KNOJC6J.js";
252
335
  import {
253
336
  componentRenames,
254
337
  estateFacts,
338
+ estateRepos,
255
339
  forgetCatalogs,
256
340
  hostAliases,
257
341
  joinReposOf,
258
342
  keepCatalogsInMemory,
259
343
  outsideProfileSystems,
260
- readCatalog
261
- } from "./chunk-EZBAS5BL.js";
344
+ readCatalog,
345
+ targetPicker
346
+ } from "./chunk-4SLBCEPD.js";
262
347
  import {
263
348
  OrgPackProposal,
264
349
  SNAPSHOT_FILE,
@@ -282,67 +367,86 @@ import {
282
367
  snapshotDates,
283
368
  snapshotSources,
284
369
  writeReview
285
- } from "./chunk-KCQS52TH.js";
370
+ } from "./chunk-64AADMTW.js";
286
371
  import {
287
372
  NoSuchNode,
288
373
  buildEstate,
289
374
  focusView,
290
375
  nodeView,
291
376
  viewKey
292
- } from "./chunk-IPSFMTK2.js";
377
+ } from "./chunk-JYMIB5XY.js";
293
378
  import {
294
379
  readServiceRequestExports,
295
380
  readVendorSpec,
296
381
  readWorkItemExports
297
- } from "./chunk-TNCROWPQ.js";
298
- import "./chunk-7IM4BVVH.js";
299
- import "./chunk-RBXDHCN7.js";
300
- import "./chunk-YXUIUOQZ.js";
301
- import "./chunk-S5PSGAQ2.js";
302
- import "./chunk-CYX36GKZ.js";
382
+ } from "./chunk-CPK7QSBG.js";
383
+ import "./chunk-YO2PKTXT.js";
384
+ import "./chunk-TWQRPM76.js";
385
+ import "./chunk-VUVGX5L2.js";
386
+ import "./chunk-API5TOUB.js";
303
387
  import {
388
+ baseClonePath,
389
+ loadRepos,
304
390
  readChangeRepos
305
- } from "./chunk-ZQC7MNQB.js";
306
- import "./chunk-TLDL2ZZI.js";
391
+ } from "./chunk-XOL3GFW2.js";
307
392
  import {
308
393
  COMMON_SETTINGS,
309
394
  PROVIDER_SETTINGS,
310
395
  defaultKeyStore,
311
396
  looksLikeKey
312
- } from "./chunk-FF4ZMLSL.js";
397
+ } from "./chunk-PRW3HXM3.js";
313
398
  import {
314
399
  providerFactoryFor
315
- } from "./chunk-QNMLLYTN.js";
400
+ } from "./chunk-QHJJMZLX.js";
401
+ import "./chunk-7AB6OWTV.js";
316
402
  import {
317
403
  ASSISTANTS,
318
404
  ChangeArtifact,
319
405
  DEFAULT_PORT,
320
406
  DEFAULT_RUNTIME,
407
+ DeclareError,
321
408
  DiagramLayout,
322
409
  KNOWLEDGE_KINDS,
323
410
  KNOWLEDGE_RECORD_KIND,
324
411
  LAYOUT_SCHEMA_VERSION,
325
412
  MANUAL_SYSTEMS,
413
+ ManifestEditError,
326
414
  QuestionError,
415
+ QuestionRole,
327
416
  QuestionState,
328
417
  RUN_SCHEMA_VERSION,
329
418
  RecordId,
330
419
  Runtime,
331
420
  SOURCE_KIND_SUGGESTIONS,
421
+ SPEC_HEADER_FILE,
332
422
  TARGETS_STEP_ID,
333
423
  TOPICS_STEP_ID,
334
424
  ViewKey,
335
425
  acceptProposal,
426
+ addProfileSystem,
427
+ addRolesTeam,
428
+ answerByWords,
336
429
  answerQuestion,
337
430
  api_exports,
338
431
  catalogDirOf,
432
+ changedLines,
339
433
  changesDirOf,
340
434
  changesRelOf,
435
+ copilotAcpOf,
436
+ currentSignal,
437
+ decideOf,
438
+ declareLink,
439
+ declaredOf,
440
+ diagramsRelOf,
441
+ eolOf,
341
442
  external_exports,
342
443
  factIdPart,
444
+ factsDirOf,
343
445
  factsRelOf,
344
446
  gateRoundLimit,
447
+ inEol,
345
448
  inputsDirOf,
449
+ isAdopted,
346
450
  joinConventions,
347
451
  joinSharedMaps,
348
452
  joinVendorOperations,
@@ -350,10 +454,13 @@ import {
350
454
  knowledgeKindOf,
351
455
  knowledgeRelOf,
352
456
  layoutPath,
457
+ lfOnly,
353
458
  listQuestions,
354
459
  loadOrgPack,
460
+ manifestFiles,
355
461
  orgPackDirOf,
356
462
  pageStanding,
463
+ parseCorrections,
357
464
  proposalsDirOf,
358
465
  proposalsRelOf,
359
466
  readEstateJoins,
@@ -363,20 +470,25 @@ import {
363
470
  require_dist,
364
471
  runsDirOf,
365
472
  runsRelOf,
473
+ setManifestField,
474
+ specDirOf,
366
475
  specRelOf,
367
- specRepos
368
- } from "./chunk-YK6CSODO.js";
476
+ specRepos,
477
+ undeclareLink
478
+ } from "./chunk-PAINMSID.js";
369
479
  import {
480
+ FileChangedError,
370
481
  createLog,
371
- writeFileAtomic
372
- } from "./chunk-MMB2OHUY.js";
482
+ writeFileAtomic,
483
+ writeFileIfUnchanged
484
+ } from "./chunk-ODMBPDDV.js";
373
485
  import {
374
486
  __toESM
375
487
  } from "./chunk-TV45EUGM.js";
376
488
 
377
489
  // packages/cli/dist/ui-commands.js
378
490
  import { spawn } from "node:child_process";
379
- import { isAbsolute, join as join11, relative as relative2, sep as sep2 } from "node:path";
491
+ import { isAbsolute, join as join14, relative as relative2, sep as sep2 } from "node:path";
380
492
 
381
493
  // packages/ui/dist-lib/index.js
382
494
  import { dirname, join } from "node:path";
@@ -388,8 +500,45 @@ function uiDistDir() {
388
500
 
389
501
  // packages/cli/dist/ui-api.js
390
502
  import { createHash } from "node:crypto";
391
- import { readdir, readFile, writeFile } from "node:fs/promises";
392
- import { join as join2 } from "node:path";
503
+ import { existsSync } from "node:fs";
504
+ import { mkdir, readdir, readFile, stat } from "node:fs/promises";
505
+ import { dirname as dirname2, join as join2 } from "node:path";
506
+ var import_yaml = __toESM(require_dist(), 1);
507
+ var FACTS_REPO = /^[a-z0-9][a-z0-9._-]{0,99}$/i;
508
+ async function openTaskWorktree(root, change, task, gitHost) {
509
+ const p = await previewTaskRun(root, change, task, {
510
+ skipGitHost: true,
511
+ ...gitHost ? { gitHost } : {}
512
+ }).catch(() => void 0);
513
+ if (!p)
514
+ return void 0;
515
+ const text = await readFile(join2(changesDirOf(root), change, "repos.yaml"), "utf8").catch(() => "");
516
+ let has = false;
517
+ try {
518
+ const d = (0, import_yaml.parse)(text);
519
+ has = !!d?.repos?.some((r) => r.id === p.repo && r.worktree);
520
+ } catch {
521
+ }
522
+ if (has)
523
+ return void 0;
524
+ if (!existsSync(baseClonePath(root, p.repo))) {
525
+ const synced = await run((io) => workspaceSync(root, [p.repo], io));
526
+ if (synced.code !== 0)
527
+ return synced;
528
+ const opened = await run((io) => workspaceOpen(root, change, [p.repo], io));
529
+ return { ...opened, out: [...synced.out, ...opened.out] };
530
+ }
531
+ return run((io) => workspaceOpen(root, change, [p.repo], io));
532
+ }
533
+ var TASK_ERRORS = /* @__PURE__ */ new Set([
534
+ "SpecKitError",
535
+ "ChangeError",
536
+ "ChangeFileError",
537
+ "AdapterError",
538
+ "RuntimeConfigError",
539
+ "GitError",
540
+ "RuntimeError"
541
+ ]);
393
542
  async function run(f) {
394
543
  const out = [];
395
544
  const err = [];
@@ -440,6 +589,7 @@ async function changeDetail(root, id) {
440
589
  const current = report.phase ? phases.indexOf(report.phase) : phases.length;
441
590
  const artifacts = [];
442
591
  const blockedArtifacts = new Set(report.blockers.map((b) => b.artifact).filter(Boolean));
592
+ const templateOnly = new Set(report.blockers.filter((b) => b.code === "unfilled-artifact").map((b) => b.artifact));
443
593
  const cctx = await loadChangeContext(root);
444
594
  const onStages = (await Promise.all(cctx.policy.stages.map((s) => readFile(join2(dir, ...s.loop.split("/"))).then(() => true, () => false)))).some(Boolean);
445
595
  const type = report.type.artifacts;
@@ -459,6 +609,8 @@ async function changeDetail(root, id) {
459
609
  artifact: a,
460
610
  path: `${changesRelOf(root)}/${id}/${files[0] ?? `${a}.md`}`,
461
611
  status: done ? "done" : at <= current ? "ready" : "blocked",
612
+ // Seventh UX walk (2.70.0): ready or blocked says what the phase allows, not that it is there.
613
+ written: files.length > 0 && !templateOnly.has(a),
462
614
  openFindings: findings.filter((f) => f.state === "open" && f.artifact.startsWith(a)).length
463
615
  });
464
616
  }
@@ -473,6 +625,8 @@ async function changeDetail(root, id) {
473
625
  openFindings: open,
474
626
  updatedAt: c.history.at(-1)?.at ?? c.createdAt,
475
627
  ...fast ? { fastPath: true } : {},
628
+ // Ninth UX walk (2.83.0): who owns each role, so the change page can name them.
629
+ owners: c.owners,
476
630
  artifacts,
477
631
  ...h ? {
478
632
  handoff: {
@@ -483,6 +637,74 @@ async function changeDetail(root, id) {
483
637
  } : {}
484
638
  };
485
639
  }
640
+ async function applyFirstOf(root, files) {
641
+ const mtime = (p) => stat(p).then((s) => s.mtimeMs, () => void 0);
642
+ const out = [];
643
+ for (const f of files) {
644
+ const repo = /(?:^|\/)facts\/([^/]+)\.corrections\.jsonl$/.exec(f.path)?.[1] ?? await answeredMapQuestionRepo(root, f.path);
645
+ if (!repo || out.includes(repo))
646
+ continue;
647
+ const saved = await mtime(join2(root, ...f.path.split("/")));
648
+ if (saved === void 0)
649
+ continue;
650
+ const applied = await mtime(join2(specDirOf(root, repo), SPEC_HEADER_FILE)) ?? await mtime(join2(factsDirOf(root), `${repo}.verify.yaml`));
651
+ if (applied === void 0 || saved > applied)
652
+ out.push(repo);
653
+ }
654
+ return out;
655
+ }
656
+ async function answeredMapQuestionRepo(root, path) {
657
+ if (!/(?:^|\/)questions\/q-[^/]+\.yaml$/.test(path))
658
+ return void 0;
659
+ const q = await readFile(join2(root, ...path.split("/")), "utf8").then((t) => (0, import_yaml.parse)(t), () => void 0);
660
+ if (!q || q.state !== "answered" || typeof q.repo !== "string")
661
+ return void 0;
662
+ const of = /^atticus verify ([a-z0-9][a-z0-9._-]*) --write$/.exec(String(q.rerun ?? "").trim());
663
+ return of?.[1] === q.repo ? q.repo : void 0;
664
+ }
665
+ async function taskViews(root, change, prs) {
666
+ const dir = join2(changesDirOf(root), change);
667
+ let tasks = readChangeTasks(await readChangeFiles(dir));
668
+ const states = prs ? await Promise.all(tasks.map((t) => settleTaskPr(root, change, t.id, {
669
+ ...prs.gitHost ? { gitHost: prs.gitHost } : {},
670
+ ...prs.now ? { now: () => new Date(prs.now) } : {}
671
+ }).catch(() => ({})))) : [];
672
+ if (states.some((s) => s.ticked)) {
673
+ tasks = readChangeTasks(await readChangeFiles(dir));
674
+ prs?.onTick?.();
675
+ }
676
+ const waits = new Map((await readyTasksOf(root, change).catch(() => ({ tasks: [] }))).tasks.map((x) => [x.id, x]));
677
+ const out = [];
678
+ for (const [i, t] of tasks.entries()) {
679
+ const w = t.done ? void 0 : waits.get(t.id);
680
+ const tr = await readTaskRun(root, change, t.id).catch(() => void 0);
681
+ const run4 = tr ? tr.waiting ?? tr.runs[0] : void 0;
682
+ const prState = tr?.state === "pr-open" ? states[i]?.state : void 0;
683
+ out.push({
684
+ id: t.id,
685
+ repo: t.repo,
686
+ description: t.title,
687
+ done: t.done,
688
+ ...t.stories[0] ? { story: t.stories[0] } : {},
689
+ branch: `task/${change}/${t.id}`,
690
+ ...run4 ? { run: run4 } : {},
691
+ ...tr ? {
692
+ taskRun: {
693
+ state: tr.state,
694
+ level: tr.level,
695
+ branch: tr.branch,
696
+ ...tr.commit ? { commit: tr.commit } : {},
697
+ ...tr.pr ? { pr: { ...tr.pr, ...prState ? { state: prState } : {} } } : {},
698
+ ...tr.ticked ? { ticked: tr.ticked } : {},
699
+ ...tr.marked ? { marked: tr.marked } : {},
700
+ notes: tr.notes
701
+ }
702
+ } : {},
703
+ ...w?.waitsFor.length ? { waits: { tasks: w.waitsFor, sameRepo: w.sameRepo ?? [] } } : {}
704
+ });
705
+ }
706
+ return out;
707
+ }
486
708
  async function runView(root, change, runId) {
487
709
  const dir = join2(runsDirOf(root), change, runId);
488
710
  const text = await readFile(join2(dir, "run.json"), "utf8").catch(() => void 0);
@@ -490,6 +712,7 @@ async function runView(root, change, runId) {
490
712
  return void 0;
491
713
  const r = JSON.parse(text);
492
714
  const manifest = await readFile(join2(dir, "context-manifest.json"), "utf8").catch(() => "");
715
+ const tr = await taskRunOfRun(root, change, runId).catch(() => void 0);
493
716
  return {
494
717
  schemaVersion: RUN_SCHEMA_VERSION,
495
718
  runId: r.runId,
@@ -506,7 +729,22 @@ async function runView(root, change, runId) {
506
729
  attempt: r.attempt ?? 1,
507
730
  ...r.stepClass ? { stepClass: r.stepClass } : {},
508
731
  ...r.model ? { model: r.model } : {},
509
- promptPath: `${runsRelOf(root)}/${change}/${runId}/prompt.md`
732
+ ...r.batch ? { batch: r.batch } : {},
733
+ ...r.parts?.length ? { parts: await partStates(root, change, r.parts) } : {},
734
+ promptPath: `${runsRelOf(root)}/${change}/${runId}/prompt.md`,
735
+ ...tr ? {
736
+ taskRun: {
737
+ task: tr.task,
738
+ repo: tr.repo,
739
+ level: tr.level,
740
+ state: tr.state,
741
+ branch: tr.branch,
742
+ worktree: tr.worktree,
743
+ notes: tr.notes,
744
+ ...tr.commit ? { commit: tr.commit } : {},
745
+ ...tr.attempts.at(-1) ? { log: tr.attempts.at(-1).log } : {}
746
+ }
747
+ } : {}
510
748
  };
511
749
  }
512
750
  async function findRun(root, runId) {
@@ -519,23 +757,137 @@ async function findRun(root, runId) {
519
757
  }
520
758
  throw notFound(`There is no run ${runId}.`);
521
759
  }
522
- async function artifactPath(root, id, artifact) {
760
+ async function artifactPath(root, id, artifact, o = {}) {
523
761
  const a = ChangeArtifact.safeParse(artifact);
524
762
  if (!a.success)
525
763
  throw badRequest(`${artifact} is not an artifact. Use one of ${ChangeArtifact.options.join(", ")}.`);
526
764
  const dir = join2(changesDirOf(root), id);
527
765
  const files = await artifactFiles(dir, a.data);
766
+ if (o.file) {
767
+ if (!files.includes(o.file))
768
+ throw notFound(`${o.file} isn't one of change ${id}'s ${artifact} files.`);
769
+ return o.file;
770
+ }
771
+ if (files.length === 0 && o.orNew && a.data !== "specs")
772
+ return a.data === "stories" ? "stories.md" : `${a.data}.md`;
528
773
  if (files.length === 0)
529
774
  throw notFound(`Change ${id} has no ${artifact} yet.`);
530
775
  return files[0];
531
776
  }
532
777
  function uiRoutes(r, ctx, deps = {}) {
533
778
  const { root, user, jobs } = ctx;
779
+ const readyView = async (change) => {
780
+ const { deployOrderOf } = await import("./chunk-JKIGL3XQ.js");
781
+ const repoOrder = await deployOrderOf(root, change);
782
+ const prStates = /* @__PURE__ */ new Map();
783
+ for (const t of await taskViews(root, change, {
784
+ ...deps.gitHost ? { gitHost: deps.gitHost } : {},
785
+ ...deps.now ? { now: deps.now } : {}
786
+ }))
787
+ if (t.taskRun?.pr?.state)
788
+ prStates.set(t.id, t.taskRun.pr.state);
789
+ const plan = await readyTasksOf(root, change, {
790
+ ...repoOrder ? { repoOrder } : {},
791
+ prStates
792
+ });
793
+ return api_exports.ReadyTasks.parse({ change, ...plan, ...repoOrder ? { repoOrder } : {} });
794
+ };
795
+ const doneBusy = /* @__PURE__ */ new Set();
534
796
  const changeDeps = {
535
797
  ...deps.gitHost ? { gitHost: deps.gitHost } : {},
536
798
  ...deps.now ? { now: deps.now } : {}
537
799
  };
538
- return r.add("POST", "/api/changes", async ({ body }) => {
800
+ const localWhat = /* @__PURE__ */ new Map();
801
+ const asJob = (what, work) => {
802
+ const busy = ctx.jobs.runningOf("local");
803
+ if (busy)
804
+ throw conflict(`${localWhat.get(busy.id) ?? busy.progress.message} is running on this server (${busy.id}): wait for it to finish, then try again.`);
805
+ const job = ctx.jobs.start("local", async (c) => {
806
+ c.progress(0, 1, what);
807
+ const res = await work();
808
+ c.progress(1, 1, res.code === 0 ? "Done" : "Stopped");
809
+ ctx.events.publish({ type: "file.changed", path: "." });
810
+ const lines = [...res.out, ...res.err];
811
+ return { result: { code: res.code, lines }, errors: res.code === 0 ? [] : lines };
812
+ });
813
+ localWhat.set(job.id, what);
814
+ return { status: 202, body: { jobId: job.id } };
815
+ };
816
+ return r.add("POST", "/api/facts/{repo}/apply", async ({ params }) => {
817
+ const { verifyCommand } = await import("./chunk-T74WQQGS.js");
818
+ const repo = params.repo;
819
+ if (!FACTS_REPO.test(repo))
820
+ throw badRequest(`${JSON.stringify(repo)} isn't a repo name.`);
821
+ if (!(await loadRepos(root)).some((x) => x.id === repo))
822
+ throw notFound(`There is no repo ${repo} in this workspace.`);
823
+ return asJob(`Applying to ${repo}`, () => run((io) => verifyCommand(root, repo, { write: true, verbose: false, json: false }, io)));
824
+ }).add("GET", "/api/upgrade", async () => {
825
+ const { upgradeCheck } = await import("./chunk-AM6FP6JW.js");
826
+ const c = await upgradeCheck(root);
827
+ const body = {
828
+ atticus: c.atticus,
829
+ newer: c.newer,
830
+ ...c.stamp ? { stamp: c.stamp.atticus } : {},
831
+ pending: c.pending
832
+ };
833
+ return { status: 200, body };
834
+ }).add("POST", "/api/upgrade", async () => {
835
+ const { upgradeCheck, upgradeCommand } = await import("./chunk-AM6FP6JW.js");
836
+ const c = await upgradeCheck(root);
837
+ if (c.newer)
838
+ throw conflict(`This metarepo was upgraded by Atticus ${c.stamp.atticus}; this is ${c.atticus}. Install ${c.stamp.atticus} or later first.`);
839
+ if (!c.pending.length)
840
+ throw conflict(`Nothing to upgrade: up to date for Atticus ${c.atticus}.`);
841
+ return asJob("Upgrading the metarepo", () => run((io) => upgradeCommand(root, { by: user }, io)));
842
+ }).add("GET", "/api/propose", async ({ query }) => {
843
+ const { proposePlan, PROPOSE_SCOPES } = await import("./chunk-UZACEG2C.js");
844
+ const scope = query.get("scope") ?? "";
845
+ if (!PROPOSE_SCOPES.includes(scope))
846
+ throw refused({
847
+ code: 2,
848
+ out: [],
849
+ err: [`scope is one of ${PROPOSE_SCOPES.join(", ")}.`]
850
+ });
851
+ const plan = await proposePlan(root, scope, {
852
+ by: user
853
+ }).catch((e) => {
854
+ throw refused({
855
+ code: 1,
856
+ out: [],
857
+ err: [
858
+ `Atticus can't list what changed here: ${e.message.split("\n")[0]}. Propose for review needs the metarepo to be a Git repository with a remote.`
859
+ ]
860
+ });
861
+ });
862
+ return { status: 200, body: { ...plan, applyFirst: await applyFirstOf(root, plan.files) } };
863
+ }).add("POST", "/api/propose", async ({ body }) => {
864
+ const b = parseBody(api_exports.ProposeRequest, body);
865
+ const { proposeForReview } = await import("./chunk-UZACEG2C.js");
866
+ return asJob("Proposing for review", () => run((io) => proposeForReview(root, {
867
+ scope: b.scope,
868
+ by: user,
869
+ files: b.files,
870
+ ...b.title ? { title: b.title } : {},
871
+ ...b.branch ? { branch: b.branch } : {}
872
+ }, io)));
873
+ }).add("POST", "/api/propose/bring-in", async () => {
874
+ const { bringInMerged } = await import("./chunk-UZACEG2C.js");
875
+ return asJob("Bringing in what merged", () => run((io) => bringInMerged(root, io)));
876
+ }).add("POST", "/api/changes/{changeId}/conformance", async ({ params }) => {
877
+ await readChange(root, params.changeId);
878
+ const { changeConformCommand } = await import("./chunk-5CMPVC3N.js");
879
+ return asJob(`Checking ${params.changeId}'s code against its design`, () => run((io) => changeConformCommand(root, params.changeId, { findingsAnswer: true }, io)));
880
+ }).add("GET", "/api/change-types", async () => {
881
+ const op = await loadOrgPack(orgPackDirOf(root));
882
+ return {
883
+ status: 200,
884
+ body: op.changeTypes.types.map((t) => ({
885
+ id: t.id,
886
+ name: t.name,
887
+ ...t.description ? { description: t.description } : {}
888
+ }))
889
+ };
890
+ }).add("POST", "/api/changes", async ({ body }) => {
539
891
  const b = parseBody(NewChange, body);
540
892
  const owners = Object.entries(b.owners ?? {}).map(([role, who]) => `${role}=${who.join(",")}`);
541
893
  const res = await run((io) => changeNew(root, {
@@ -557,7 +909,22 @@ function uiRoutes(r, ctx, deps = {}) {
557
909
  }).add("GET", "/api/changes/{changeId}", async ({ params }) => ({
558
910
  status: 200,
559
911
  body: await changeDetail(root, params.changeId)
560
- })).add("GET", "/api/changes/{changeId}/status", async ({ params }) => {
912
+ })).add("PUT", "/api/changes/{changeId}/owners", async ({ params, body }) => {
913
+ await readChange(root, params.changeId);
914
+ const b = parseBody(api_exports.ChangeOwnersRequest, body);
915
+ try {
916
+ await setChangeOwners(root, params.changeId, b.owners);
917
+ } catch (e) {
918
+ if (e instanceof ChangeOwnersError)
919
+ throw badRequest(e.message);
920
+ throw e;
921
+ }
922
+ ctx.events.publish({
923
+ type: "file.changed",
924
+ path: `${changesRelOf(root)}/${params.changeId}/change.yaml`
925
+ });
926
+ return { status: 200, body: await changeDetail(root, params.changeId) };
927
+ }).add("GET", "/api/changes/{changeId}/status", async ({ params }) => {
561
928
  await readChange(root, params.changeId);
562
929
  const rep = await changeReport(root, params.changeId);
563
930
  return {
@@ -581,10 +948,13 @@ function uiRoutes(r, ctx, deps = {}) {
581
948
  reasons: rep.reasons
582
949
  }
583
950
  };
584
- }).add("GET", "/api/changes/{changeId}/artifacts/{artifact}", async ({ params }) => {
951
+ }).add("GET", "/api/changes/{changeId}/artifacts/{artifact}", async ({ params, query }) => {
585
952
  await readChange(root, params.changeId);
586
- const rel = await artifactPath(root, params.changeId, params.artifact);
587
- const content = await readFile(join2(changesDirOf(root), params.changeId, rel), "utf8");
953
+ const rel = await artifactPath(root, params.changeId, params.artifact, {
954
+ orNew: true,
955
+ file: query.get("file")
956
+ });
957
+ const content = await readFile(join2(changesDirOf(root), params.changeId, rel), "utf8").catch(() => "");
588
958
  return {
589
959
  status: 200,
590
960
  body: {
@@ -594,15 +964,27 @@ function uiRoutes(r, ctx, deps = {}) {
594
964
  sha256: sha(content)
595
965
  }
596
966
  };
597
- }).add("PUT", "/api/changes/{changeId}/artifacts/{artifact}", async ({ params, body }) => {
967
+ }).add("PUT", "/api/changes/{changeId}/artifacts/{artifact}", async ({ params, body, query }) => {
598
968
  await readChange(root, params.changeId);
599
969
  const b = parseBody(api_exports.SaveArtifactRequest, body);
600
- const rel = await artifactPath(root, params.changeId, params.artifact);
970
+ const rel = await artifactPath(root, params.changeId, params.artifact, {
971
+ orNew: true,
972
+ file: query.get("file")
973
+ });
601
974
  const file = join2(changesDirOf(root), params.changeId, rel);
602
- const now = await readFile(file, "utf8");
975
+ const was = await readFile(file, "utf8").catch(() => void 0);
976
+ const now = was ?? "";
603
977
  if (sha(now) !== b.baseSha256)
604
978
  throw conflict(`${rel} changed since you opened it. Reload it, then make your edit again.`);
605
- await writeFile(file, b.content, "utf8");
979
+ await mkdir(dirname2(file), { recursive: true });
980
+ await writeFileIfUnchanged(file, was, b.content);
981
+ const { recordPersonSave: recordPersonSave2 } = await import("./chunk-NTAVOWYD.js");
982
+ await recordPersonSave2(join2(changesDirOf(root), params.changeId), params.changeId, {
983
+ file: rel,
984
+ who: user,
985
+ at: (deps.now ? new Date(deps.now) : /* @__PURE__ */ new Date()).toISOString(),
986
+ content: b.content
987
+ }).catch(() => void 0);
606
988
  ctx.events.publish({
607
989
  type: "file.changed",
608
990
  path: `${changesRelOf(root)}/${params.changeId}/${rel}`
@@ -618,17 +1000,142 @@ function uiRoutes(r, ctx, deps = {}) {
618
1000
  };
619
1001
  }).add("GET", "/api/changes/{changeId}/tasks", async ({ params }) => {
620
1002
  await readChange(root, params.changeId);
621
- const files = await readChangeFiles(join2(changesDirOf(root), params.changeId));
1003
+ const change = params.changeId;
622
1004
  return {
623
1005
  status: 200,
624
- body: readChangeTasks(files).map((t) => ({
625
- id: t.id,
626
- repo: t.repo,
627
- description: t.title,
628
- done: t.done,
629
- ...t.stories[0] ? { story: t.stories[0] } : {},
630
- branch: `task/${params.changeId}/${t.id}`
631
- }))
1006
+ body: await taskViews(root, change, {
1007
+ ...deps.gitHost ? { gitHost: deps.gitHost } : {},
1008
+ ...deps.now ? { now: deps.now } : {},
1009
+ // Seventh UX walk: a task ticked because its PR merged changed tasks.md.
1010
+ onTick: () => ctx.events.publish({
1011
+ type: "file.changed",
1012
+ path: `${changesRelOf(root)}/${change}/tasks.md`
1013
+ })
1014
+ })
1015
+ };
1016
+ }).add("POST", "/api/changes/{changeId}/tasks/{taskId}/done", async ({ params, body }) => {
1017
+ await readChange(root, params.changeId);
1018
+ const change = params.changeId;
1019
+ const task = params.taskId;
1020
+ const b = parseBody(api_exports.TaskDoneRequest, body);
1021
+ if (!(await taskViews(root, change)).some((t) => t.id === task))
1022
+ throw notFound(`Change ${change} has no task ${task}.`);
1023
+ if (await setTaskDone(root, change, task, b.done))
1024
+ ctx.events.publish({
1025
+ type: "file.changed",
1026
+ path: `${changesRelOf(root)}/${change}/tasks.md`
1027
+ });
1028
+ const tr = await readTaskRun(root, change, task).catch(() => void 0);
1029
+ if (tr) {
1030
+ tr.marked = {
1031
+ done: b.done,
1032
+ by: user,
1033
+ at: (deps.now ? new Date(deps.now) : /* @__PURE__ */ new Date()).toISOString().replace(/\.000Z$/, "Z")
1034
+ };
1035
+ await saveTaskRun(root, tr);
1036
+ }
1037
+ return {
1038
+ status: 200,
1039
+ body: (await taskViews(root, change)).find((t) => t.id === task)
1040
+ };
1041
+ }).add("GET", "/api/changes/{changeId}/tasks/ready", async ({ params }) => {
1042
+ await readChange(root, params.changeId);
1043
+ return { status: 200, body: await readyView(params.changeId) };
1044
+ }).add("POST", "/api/changes/{changeId}/tasks/run-ready", async ({ params, body }) => {
1045
+ const change = params.changeId;
1046
+ await readChange(root, change);
1047
+ const b = parseBody(api_exports.RunReadyRequest, body);
1048
+ const view3 = await readyView(change);
1049
+ const tasks = b.tasks ? view3.ready.filter((t) => b.tasks.includes(t)) : view3.ready;
1050
+ if (!tasks.length)
1051
+ throw refused({
1052
+ code: 1,
1053
+ out: [],
1054
+ err: [
1055
+ b.tasks ? `None of ${b.tasks.join(", ")} is ready to run now: the Task board says why.` : `No task of ${change} is ready to run: each waits for the tasks before it, its PR, the assistant or a person.`
1056
+ ]
1057
+ });
1058
+ const repoOf2 = new Map(view3.tasks.map((t) => [t.id, t.repo]));
1059
+ const busy = ctx.jobs.runningOf("step");
1060
+ if (busy)
1061
+ throw conflict(`An AI step is running on this server (${busy.id}): wait for it to finish, or cancel it.`);
1062
+ const job = ctx.jobs.start("step", async (c) => {
1063
+ const r2 = await runReadyTasks(root, change, {
1064
+ by: user,
1065
+ only: tasks,
1066
+ ...view3.repoOrder ? { repoOrder: view3.repoOrder } : {},
1067
+ ...b.level ? { level: b.level } : {},
1068
+ ...b.runtime ? { mode: b.runtime } : {},
1069
+ // As Run with AI: runtime.yaml's level, lowered to the repo's cap, and at most draft.
1070
+ levelFor: async (task) => {
1071
+ const p = await previewTaskRun(root, change, task, {
1072
+ skipGitHost: true,
1073
+ ...deps.gitHost ? { gitHost: deps.gitHost } : {}
1074
+ });
1075
+ return p.level === "pr" ? "draft" : p.level;
1076
+ },
1077
+ // The task's repo gets its worktree when it has none yet, as Run with AI does.
1078
+ before: async (task) => {
1079
+ const opened = await openTaskWorktree(root, change, task, deps.gitHost);
1080
+ return opened && opened.code !== 0 ? opened.err.join(" ") || opened.out.join(" ") || "Its worktree couldn't be opened." : void 0;
1081
+ },
1082
+ progress: (done, total, message) => c.progress(done, total, message),
1083
+ cancelled: () => c.cancelled,
1084
+ ...deps.gitHost ? { gitHost: deps.gitHost } : {},
1085
+ ...deps.taskRun?.git ? { git: deps.taskRun.git } : {},
1086
+ ...deps.taskRun?.commands ? { commands: deps.taskRun.commands } : {}
1087
+ });
1088
+ for (const x of r2.ran)
1089
+ ctx.events.publish({
1090
+ type: "file.changed",
1091
+ path: `${changesRelOf(root)}/${change}/tasks/${x.task}/task-run.yaml`
1092
+ });
1093
+ const ran = r2.ran.map((x) => "record" in x ? { task: x.task, repo: x.repo, state: x.record.state, branch: x.record.branch } : { task: x.task, repo: x.repo || (repoOf2.get(x.task) ?? ""), refused: x.refused });
1094
+ const left = r2.stopped ? r2.planned.slice(r2.planned.indexOf(r2.stopped.task) + 1) : [];
1095
+ return {
1096
+ result: api_exports.RunReadyResult.parse({
1097
+ ran,
1098
+ ...r2.stopped ? { stopped: r2.stopped } : {},
1099
+ left
1100
+ })
1101
+ };
1102
+ });
1103
+ return { status: 202, body: api_exports.RunReadyAccepted.parse({ jobId: job.id, tasks }) };
1104
+ }).add("POST", "/api/changes/{changeId}/tasks/{taskId}/pr", async ({ params }) => {
1105
+ await readChange(root, params.changeId);
1106
+ const change = params.changeId;
1107
+ const task = params.taskId;
1108
+ if (!(await taskViews(root, change)).some((t) => t.id === task))
1109
+ throw notFound(`Change ${change} has no task ${task}.`);
1110
+ let raised;
1111
+ try {
1112
+ raised = await raiseTaskPr(root, change, task, {
1113
+ approval: {
1114
+ by: user,
1115
+ at: (deps.now ? new Date(deps.now) : /* @__PURE__ */ new Date()).toISOString(),
1116
+ ref: `task ${task} of ${change}: push and PR (the app's Raise the task's PR)`
1117
+ },
1118
+ ...deps.taskRun?.git ? { git: deps.taskRun.git } : {},
1119
+ ...deps.gitHost ? { gitHost: deps.gitHost } : {}
1120
+ });
1121
+ } catch (e) {
1122
+ if (e instanceof Error && (e.name === "TaskRunError" || TASK_ERRORS.has(e.name)))
1123
+ throw refused({ code: 1, out: [], err: [e.message] });
1124
+ throw e;
1125
+ }
1126
+ ctx.events.publish({
1127
+ type: "file.changed",
1128
+ path: `${changesRelOf(root)}/${change}/tasks/${task}/task-run.yaml`
1129
+ });
1130
+ if (raised.state !== "pr-open")
1131
+ throw refused({
1132
+ code: 1,
1133
+ out: [],
1134
+ err: [raised.notes.at(-1) ?? "The PR wasn't raised."]
1135
+ });
1136
+ return {
1137
+ status: 201,
1138
+ body: (await taskViews(root, change, { ...deps.gitHost ? { gitHost: deps.gitHost } : {} })).find((t) => t.id === task)
632
1139
  };
633
1140
  }).add("POST", "/api/changes/{changeId}/workspace", async ({ params, body }) => {
634
1141
  await readChange(root, params.changeId);
@@ -644,41 +1151,69 @@ function uiRoutes(r, ctx, deps = {}) {
644
1151
  const res = await run((io) => changeHandoff(root, params.changeId, { by: user, approve: true, ...b.repo ? { repo: b.repo } : {} }, io, changeDeps));
645
1152
  if (res.code !== 0)
646
1153
  throw refused(res);
647
- return { status: 201, body: await changeDetail(root, params.changeId) };
1154
+ return {
1155
+ status: 201,
1156
+ body: { ...await changeDetail(root, params.changeId), notes: res.out }
1157
+ };
1158
+ }).add("POST", "/api/changes/{changeId}/release-prs", async ({ params, body }) => {
1159
+ const id = params.changeId;
1160
+ await readChange(root, id);
1161
+ parseBody(api_exports.ReleasePrsRequest, body);
1162
+ const res = await run((io) => changeReleasePrs(root, id, { by: user, approve: true }, io, changeDeps));
1163
+ ctx.events.publish({ type: "file.changed", path: `${id}/repos.yaml` });
1164
+ if (res.code !== 0)
1165
+ throw refused(res);
1166
+ return { status: 201, body: api_exports.LinesView.parse({ lines: res.out }) };
648
1167
  }).add("POST", "/api/changes/{changeId}/sync", async ({ params, body }) => {
649
1168
  await readChange(root, params.changeId);
650
1169
  const b = parseBody(api_exports.SyncRequest, body);
651
1170
  const res = await run((io) => changeSync(root, params.changeId, { by: user, approve: true, ...b.repo ? { repo: b.repo } : {} }, io, changeDeps));
652
1171
  if (res.code === 2)
653
1172
  throw refused(res);
1173
+ const { appLines } = await import("./chunk-J2O4LFVI.js");
654
1174
  return {
655
1175
  status: 200,
656
1176
  body: {
657
1177
  ...await changeDetail(root, params.changeId),
658
- lines: [...res.out, ...res.err]
1178
+ lines: appLines([...res.out, ...res.err])
659
1179
  }
660
1180
  };
661
1181
  }).add("GET", "/api/changes/{changeId}/inputs", async ({ params }) => {
662
1182
  const c = await readChange(root, params.changeId);
663
- const { readChangeInputs } = await import("./chunk-U542BJME.js");
1183
+ const { readChangeInputs } = await import("./chunk-UDXWIPCB.js");
664
1184
  return {
665
1185
  status: 200,
666
1186
  body: api_exports.ChangeInputList.parse(await readChangeInputs(join2(changesDirOf(root), c.id)))
667
1187
  };
668
1188
  }).add("GET", "/api/changes/{changeId}/prototypes", async ({ params }) => {
669
1189
  const c = await readChange(root, params.changeId);
670
- const { changePrototypes } = await import("./chunk-2TG2QGKW.js");
1190
+ const { changePrototypes } = await import("./chunk-VYOCPTUB.js");
671
1191
  return {
672
1192
  status: 200,
673
1193
  body: api_exports.ChangePrototypeList.parse(await changePrototypes(root, c.id))
674
1194
  };
1195
+ }).add("POST", "/api/changes/{changeId}/prototypes", async ({ params, body }) => {
1196
+ const c = await readChange(root, params.changeId);
1197
+ const b = parseBody(api_exports.ComparePrototypeRequest, body);
1198
+ const { attachPrototype, changePrototypes } = await import("./chunk-VYOCPTUB.js");
1199
+ const res = await run((io) => attachPrototype(root, c.id, b.repo, { by: user, ...deps.now ? { now: new Date(deps.now) } : {} }, io));
1200
+ if (res.code !== 0)
1201
+ throw refused(res);
1202
+ ctx.events.publish({ type: "file.changed", path: `${changesRelOf(root)}/${c.id}/inputs` });
1203
+ return {
1204
+ status: 201,
1205
+ body: api_exports.ComparePrototypeView.parse({
1206
+ ...await changePrototypes(root, c.id),
1207
+ lines: res.out
1208
+ })
1209
+ };
675
1210
  }).add("POST", "/api/changes/{changeId}/inputs", async ({ params, body }) => {
676
1211
  const c = await readChange(root, params.changeId);
677
1212
  const b = parseBody(api_exports.AttachFileRequest, body);
678
1213
  const bytes = new Uint8Array(Buffer.from(b.content, "base64"));
679
1214
  if (bytes.length > MAX_ATTACH)
680
1215
  throw new HttpError(413, "The file is too large", "Attach a file of 15 MB or less.");
681
- const { attachFiles } = await import("./chunk-GXDATVSY.js");
1216
+ const { attachFiles } = await import("./chunk-XQ3YZ4QW.js");
682
1217
  const r2 = await attachFiles(root, c.id, [{ name: b.name, bytes }], user);
683
1218
  if (!r2.added.length)
684
1219
  throw new HttpError(422, "Atticus can't attach that", r2.refused[0]?.why ?? "", {
@@ -700,15 +1235,42 @@ function uiRoutes(r, ctx, deps = {}) {
700
1235
  const b = parseBody(api_exports.StartRunRequest, body);
701
1236
  const before = new Set(await readdir(join2(runsDirOf(root), params.changeId)).catch(() => []));
702
1237
  let res;
703
- if (b.task)
704
- res = await run((io) => taskRun(root, params.changeId, b.task, {
705
- mode: b.runtime,
706
- by: user,
707
- ...b.autonomy ? {
708
- level: b.autonomy === "draft-and-pr" ? "pr" : b.autonomy === "plan-only" ? "plan" : "draft"
709
- } : {}
710
- }, io));
711
- else if (b.agent === "story-writer")
1238
+ if (b.task) {
1239
+ let level = b.autonomy ? b.autonomy === "draft-and-pr" ? "pr" : b.autonomy === "plan-only" ? "plan" : "draft" : void 0;
1240
+ if (level === "pr")
1241
+ throw refused({
1242
+ code: 1,
1243
+ out: [],
1244
+ err: [
1245
+ "The pr level pushes the task branch and opens a PR, which needs the person's yes: start it from VS Code, IntelliJ or the command line (atticus task run <change> <task> --level pr --approve)."
1246
+ ]
1247
+ });
1248
+ if (!level) {
1249
+ try {
1250
+ const p = await previewTaskRun(root, params.changeId, b.task, {
1251
+ skipGitHost: true,
1252
+ ...deps.gitHost ? { gitHost: deps.gitHost } : {}
1253
+ });
1254
+ level = p.level === "pr" ? "draft" : p.level;
1255
+ } catch (e) {
1256
+ if (e instanceof Error && e.name === "TaskRunError")
1257
+ throw conflict(e.message);
1258
+ if (e instanceof Error && TASK_ERRORS.has(e.name))
1259
+ throw refused({ code: 1, out: [], err: [e.message] });
1260
+ throw e;
1261
+ }
1262
+ }
1263
+ const lv = level;
1264
+ const opened = await openTaskWorktree(root, params.changeId, b.task, deps.gitHost);
1265
+ if (opened && opened.code !== 0)
1266
+ throw refused(opened);
1267
+ res = await run((io) => taskRun(root, params.changeId, b.task, { mode: b.runtime, by: user, level: lv }, io, {
1268
+ ...deps.gitHost ? { gitHost: deps.gitHost } : {},
1269
+ ...deps.taskRun?.git ? { git: deps.taskRun.git } : {}
1270
+ }));
1271
+ if (opened)
1272
+ res = { ...res, out: [...opened.out, ...res.out] };
1273
+ } else if (b.agent === "story-writer")
712
1274
  res = await run((io) => planStories(root, params.changeId, { mode: b.runtime }, io));
713
1275
  else if (b.agent.endsWith("-critic"))
714
1276
  res = await run((io) => critic(root, params.changeId, { mode: b.runtime }, io));
@@ -759,32 +1321,110 @@ function uiRoutes(r, ctx, deps = {}) {
759
1321
  const before = await findRun(root, params.runId);
760
1322
  if (before.state === "validated" || before.state === "cancelled")
761
1323
  throw conflict(`Run ${params.runId} is already ${before.state}.`);
762
- const res = await run((io) => handoffDone(root, params.runId, io));
1324
+ if (doneBusy.has(params.runId))
1325
+ throw conflict(`Done for ${params.runId} is already running; wait for it to finish.`);
1326
+ doneBusy.add(params.runId);
1327
+ let res;
1328
+ try {
1329
+ res = await run((io) => runDone(root, params.runId, { by: user }, io, deps.taskRun));
1330
+ } finally {
1331
+ doneBusy.delete(params.runId);
1332
+ }
763
1333
  const after = await findRun(root, params.runId);
764
1334
  ctx.events.publish({ type: "run.changed", runId: after.runId, state: after.state });
765
- return { status: 200, body: { ...after, lines: [...res.out, ...res.err] } };
1335
+ const { resumeAfterDone, appLines } = await import("./chunk-J2O4LFVI.js");
1336
+ if (res.code !== 0)
1337
+ throw refused({ ...res, out: appLines(res.out), err: appLines(res.err) });
1338
+ const stepRunner = deps.runStep ?? (async (args) => {
1339
+ const { main } = await import("./chunk-7FHNFCHK.js");
1340
+ const out = [];
1341
+ const err = [];
1342
+ const code = await main([...args, "--root", root], { out: (l) => out.push(l), err: (l) => err.push(l) }, { quiet: true });
1343
+ return { code, out, err };
1344
+ });
1345
+ const resumed = await resumeAfterDone(root, stepRunner, after.change, after.runId).catch((e) => ({ lines: [e.message] }));
1346
+ if (resumed)
1347
+ ctx.events.publish({ type: "file.changed", path: changesRelOf(root) });
1348
+ return {
1349
+ status: 200,
1350
+ body: {
1351
+ ...after,
1352
+ // When the app resumed the step, runDone's "run the step again with --resume" is done;
1353
+ // sixth UX walk: what is left to do is said in the app's words.
1354
+ lines: [
1355
+ ...resumed ? appLines([...res.out, ...res.err].filter((l) => !/--resume run-/.test(l))) : [...res.out, ...res.err],
1356
+ ...resumed?.lines ?? []
1357
+ ],
1358
+ ...resumed?.next ? { next: resumed.next } : {}
1359
+ }
1360
+ };
766
1361
  }).add("POST", "/api/runs/{runId}/retry", async ({ params }) => {
767
1362
  const v = await findRun(root, params.runId);
768
1363
  if (v.state !== "failed")
769
1364
  throw conflict(`Only a failed run can be retried; ${v.runId} is ${v.state}.`);
1365
+ const owner = await findTaskRunOfRun(root, v.runId);
1366
+ if (owner && owner.waiting !== v.runId)
1367
+ throw conflict(taskRunNotWaiting(v.runId, owner));
770
1368
  const file = join2(runsDirOf(root), v.change, v.runId, "run.json");
771
- const rec = JSON.parse(await readFile(file, "utf8"));
1369
+ const was = await readFile(file, "utf8");
1370
+ const rec = JSON.parse(was);
772
1371
  rec.state = "prepared";
773
1372
  rec.attempt = (rec.attempt ?? 1) + 1;
774
1373
  rec.errors = [];
775
- await writeFile(file, JSON.stringify(rec, null, 2) + "\n");
1374
+ await writeFileIfUnchanged(file, was, JSON.stringify(rec, null, 2) + "\n");
776
1375
  const after = await findRun(root, params.runId);
777
1376
  ctx.events.publish({ type: "run.changed", runId: after.runId, state: after.state });
778
1377
  return { status: 200, body: after };
779
1378
  }).add("POST", "/api/runs/{runId}/cancel", async ({ params }) => {
1379
+ const here = await findRun(root, params.runId).catch(() => void 0);
1380
+ if (!here) {
1381
+ let ended;
1382
+ try {
1383
+ ended = await cancelTaskRunWait(root, params.runId, { by: user });
1384
+ } catch (e) {
1385
+ if (e instanceof Error && e.name === "TaskRunError")
1386
+ throw conflict(e.message);
1387
+ throw e;
1388
+ }
1389
+ if (!ended)
1390
+ throw notFound(`There is no run ${params.runId}.`);
1391
+ const at = (/* @__PURE__ */ new Date()).toISOString();
1392
+ const body = {
1393
+ schemaVersion: RUN_SCHEMA_VERSION,
1394
+ runId: params.runId,
1395
+ change: ended.change,
1396
+ agent: "task-runner",
1397
+ runtime: "hand-off",
1398
+ state: "cancelled",
1399
+ startedAt: ended.startedAt,
1400
+ finishedAt: at,
1401
+ contextManifestSha256: sha(""),
1402
+ errors: [],
1403
+ attempt: 1
1404
+ };
1405
+ ctx.events.publish({ type: "run.changed", runId: params.runId, state: "cancelled" });
1406
+ return { status: 200, body };
1407
+ }
780
1408
  const v = await findRun(root, params.runId);
781
1409
  if (["validated", "cancelled"].includes(v.state))
782
1410
  throw conflict(`Run ${v.runId} is already ${v.state}.`);
1411
+ if (doneBusy.has(v.runId))
1412
+ throw conflict(`Done for ${v.runId} is running; wait for it to finish.`);
783
1413
  const file = join2(runsDirOf(root), v.change, v.runId, "run.json");
784
- const rec = JSON.parse(await readFile(file, "utf8"));
785
- rec.state = "cancelled";
786
- rec.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
787
- await writeFile(file, JSON.stringify(rec, null, 2) + "\n");
1414
+ const cancelRun = async () => {
1415
+ const was = await readFile(file, "utf8");
1416
+ const rec = JSON.parse(was);
1417
+ rec.state = "cancelled";
1418
+ rec.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
1419
+ await writeFileIfUnchanged(file, was, JSON.stringify(rec, null, 2) + "\n");
1420
+ };
1421
+ try {
1422
+ await cancelTaskRunWait(root, v.runId, { by: user, cancelRun });
1423
+ } catch (e) {
1424
+ if (e instanceof Error && e.name === "TaskRunError")
1425
+ throw conflict(e.message);
1426
+ throw e;
1427
+ }
788
1428
  const after = await findRun(root, params.runId);
789
1429
  ctx.events.publish({ type: "run.changed", runId: after.runId, state: after.state });
790
1430
  return { status: 200, body: after };
@@ -867,8 +1507,10 @@ function uiRoutes(r, ctx, deps = {}) {
867
1507
  }
868
1508
 
869
1509
  // packages/cli/dist/ui-api-run.js
870
- var import_yaml = __toESM(require_dist(), 1);
871
- import { readdir as readdir2, readFile as readFile2, stat } from "node:fs/promises";
1510
+ var import_yaml2 = __toESM(require_dist(), 1);
1511
+ import { createHash as createHash2 } from "node:crypto";
1512
+ import { readdir as readdir2, readFile as readFile2, stat as stat2 } from "node:fs/promises";
1513
+ import { homedir } from "node:os";
872
1514
  import { join as join3, resolve } from "node:path";
873
1515
  async function run2(f) {
874
1516
  const out = [];
@@ -925,7 +1567,7 @@ async function latestFindings2(root, change) {
925
1567
  }
926
1568
  return out;
927
1569
  }
928
- async function filesUnder(dir, prefix = "") {
1570
+ async function filesUnder2(dir, prefix = "") {
929
1571
  const out = [];
930
1572
  let entries;
931
1573
  try {
@@ -936,9 +1578,9 @@ async function filesUnder(dir, prefix = "") {
936
1578
  for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
937
1579
  const rel = prefix ? `${prefix}/${e.name}` : e.name;
938
1580
  if (e.isDirectory())
939
- out.push(...await filesUnder(join3(dir, e.name), rel));
1581
+ out.push(...await filesUnder2(join3(dir, e.name), rel));
940
1582
  else
941
- out.push({ path: rel, bytes: (await stat(join3(dir, e.name))).size });
1583
+ out.push({ path: rel, bytes: (await stat2(join3(dir, e.name))).size });
942
1584
  }
943
1585
  return out;
944
1586
  }
@@ -998,6 +1640,8 @@ async function findStep(root, id, change) {
998
1640
  throw notFound(`There is no step ${stepRef(id, change)}.`);
999
1641
  return s;
1000
1642
  }
1643
+ var sha2 = (s) => createHash2("sha256").update(s).digest("hex");
1644
+ var snowText = (dir) => readFile2(join3(dir, "snow.md"), "utf8").catch(() => "");
1001
1645
  var SyncEntries = external_exports.object({
1002
1646
  requests: external_exports.array(external_exports.object({ key: external_exports.string(), id: external_exports.string() })).default([])
1003
1647
  });
@@ -1006,9 +1650,13 @@ async function changeRequests(root, change, templates, steps) {
1006
1650
  const drafts = readSnowDrafts(await readChangeFiles(dir));
1007
1651
  if (drafts.length === 0)
1008
1652
  return [];
1653
+ const snow = {
1654
+ file: `${changesRelOf(root)}/${change}/snow.md`,
1655
+ sha256: sha2(await snowText(dir))
1656
+ };
1009
1657
  let synced = [];
1010
1658
  try {
1011
- synced = SyncEntries.parse((0, import_yaml.parse)(await readFile2(join3(dir, SYNC_FILE), "utf8"))).requests;
1659
+ synced = SyncEntries.parse((0, import_yaml2.parse)(await readFile2(join3(dir, SYNC_FILE), "utf8"))).requests;
1012
1660
  } catch {
1013
1661
  }
1014
1662
  return drafts.map((d) => {
@@ -1028,7 +1676,8 @@ async function changeRequests(root, change, templates, steps) {
1028
1676
  record,
1029
1677
  approvers: t?.approvers ?? [],
1030
1678
  fields,
1031
- missing: fields.filter((f) => f.required && !f.value.trim()).map((f) => f.name)
1679
+ missing: fields.filter((f) => f.required && !f.value.trim()).map((f) => f.name),
1680
+ ...snow
1032
1681
  };
1033
1682
  const entry = t ? synced.find((e) => e.key === `${t.id}|${record}`) : void 0;
1034
1683
  if (!entry)
@@ -1079,6 +1728,23 @@ function uiRunRoutes(r, ctx, deps = {}) {
1079
1728
  const nowDate = deps.now ? () => new Date(deps.now) : void 0;
1080
1729
  const pack = () => loadOrgPack(orgPackDirOf(root));
1081
1730
  const rel = deps.now ? { now: () => new Date(deps.now) } : {};
1731
+ const syncAfterStep = async (s) => {
1732
+ if (s.system !== "git-host" || !s.change || s.state === "open")
1733
+ return void 0;
1734
+ const c = await changeStore(root).tryRead(s.change).catch(() => void 0);
1735
+ const live = c && c.state !== "returned" ? liveHandoff(c) : void 0;
1736
+ const steps = await fileStepStore(root).list({ change: s.change, system: "git-host" });
1737
+ const open = s.action === "open-pull-request" ? s : steps.find((x) => x.action === "open-pull-request" && s.dependsOn.includes(x.id));
1738
+ if (!live || !open || open.inputs.head !== live.branch)
1739
+ return void 0;
1740
+ const repo = typeof open.inputs.repo === "string" ? open.inputs.repo : void 0;
1741
+ const res = await run2((io) => changeSync(root, s.change, { by: user, ...repo ? { repo } : {} }, io, {
1742
+ ...deps.gitHost ? { gitHost: deps.gitHost } : {},
1743
+ ...deps.now ? { now: deps.now } : {}
1744
+ }));
1745
+ ctx.events.publish({ type: "file.changed", path: `${changesRelOf(root)}/${s.change}` });
1746
+ return [...res.out, ...res.err];
1747
+ };
1082
1748
  return r.add("GET", "/api/todo", async ({ query }) => {
1083
1749
  const change = strParam(query, "change");
1084
1750
  const op = await pack();
@@ -1153,7 +1819,8 @@ function uiRunRoutes(r, ctx, deps = {}) {
1153
1819
  }
1154
1820
  ctx.events.publish({ type: "file.changed", path: `steps/${params.stepId}` });
1155
1821
  const s = await findStep(root, params.stepId, b.change);
1156
- return { status: 200, body: { ...s, lines: res.out } };
1822
+ const synced = await syncAfterStep(s);
1823
+ return { status: 200, body: { ...s, lines: res.out, ...synced ? { synced } : {} } };
1157
1824
  }).add("GET", "/api/todo/{stepId}/run", async ({ params, query }) => {
1158
1825
  const s = await findStep(root, params.stepId, strParam(query, "change"));
1159
1826
  const op = await pack();
@@ -1226,6 +1893,38 @@ function uiRunRoutes(r, ctx, deps = {}) {
1226
1893
  throw refused2(res);
1227
1894
  ctx.events.publish({ type: "file.changed", path: `${params.changeId}/snow.md` });
1228
1895
  return { status: 200, body: { lines: res.out, toFill: res.code === 1 } };
1896
+ }).add("PUT", "/api/service-desk/{changeId}/fields", async ({ params, body }) => {
1897
+ const c = await readChange2(root, params.changeId);
1898
+ const b = parseBody2(api_exports.FillRequestFieldsRequest, body);
1899
+ const dir = changeFolder(root, c.id);
1900
+ const rel2 = `${changesRelOf(root)}/${c.id}/snow.md`;
1901
+ const was = await readFile2(join3(dir, "snow.md"), "utf8").catch(() => void 0);
1902
+ if (was === void 0)
1903
+ throw notFound(`${c.id} has no snow.md: draft its requests first.`);
1904
+ if (sha2(was) !== b.baseSha256)
1905
+ throw conflict(`${rel2} changed since you opened it (another edit, or a person's): reload the page and fill the fields again.`);
1906
+ let next;
1907
+ try {
1908
+ next = fillSnowFields(was, b.heading, b.values);
1909
+ } catch (e) {
1910
+ if (e instanceof SnowFillError)
1911
+ throw new HttpError(422, "Atticus can't fill that", e.message);
1912
+ throw e;
1913
+ }
1914
+ await writeFileIfUnchanged(join3(dir, "snow.md"), was, next);
1915
+ ctx.events.publish({ type: "file.changed", path: rel2 });
1916
+ const op = await pack();
1917
+ const now = (await changeRequests(root, c.id, op.serviceRequests, [])).find((r2) => r2.heading === b.heading);
1918
+ const names = Object.keys(b.values).join(", ");
1919
+ return {
1920
+ status: 200,
1921
+ body: {
1922
+ path: rel2,
1923
+ sha256: sha2(next),
1924
+ missing: now?.missing ?? [],
1925
+ lines: [`Filled ${names} in ${rel2}.`]
1926
+ }
1927
+ };
1229
1928
  }).add("POST", "/api/service-desk/{changeId}/submit", async ({ params }) => {
1230
1929
  await readChange2(root, params.changeId);
1231
1930
  const op = await pack();
@@ -1320,7 +2019,7 @@ function uiRunRoutes(r, ctx, deps = {}) {
1320
2019
  } : {},
1321
2020
  conflicts,
1322
2021
  archive: { pass: archive.pass, blockers: archive.blockers, notes: archive.notes },
1323
- evidence: await filesUnder(changeFolder(root, id))
2022
+ evidence: await filesUnder2(changeFolder(root, id))
1324
2023
  }
1325
2024
  };
1326
2025
  }).add("POST", "/api/release/{changeId}/archive", async ({ params, body }) => {
@@ -1497,7 +2196,7 @@ function uiRunRoutes(r, ctx, deps = {}) {
1497
2196
  const { lessons, problems } = await lessonsFor(root);
1498
2197
  const sandbox = await isSandbox(root);
1499
2198
  const progress = sandbox ? await readProgress(root) : void 0;
1500
- const view2 = lessons.map((l) => {
2199
+ const view3 = lessons.map((l) => {
1501
2200
  const p = progress?.lessons[l.id];
1502
2201
  const done = p ? l.steps.filter((s) => p.steps[s.id]).length : 0;
1503
2202
  return {
@@ -1513,8 +2212,8 @@ function uiRunRoutes(r, ctx, deps = {}) {
1513
2212
  });
1514
2213
  const roles = [...new Set(lessons.map((l) => l.role))].map((role) => ({
1515
2214
  role,
1516
- lessons: view2.filter((v) => v.role === role).length,
1517
- done: view2.filter((v) => v.role === role && v.state === "done").length
2215
+ lessons: view3.filter((v) => v.role === role).length,
2216
+ done: view3.filter((v) => v.role === role && v.state === "done").length
1518
2217
  }));
1519
2218
  const cur = progress?.current ? lessons.find((l) => l.id === progress.current) : void 0;
1520
2219
  const lp = cur ? progress.lessons[cur.id] : void 0;
@@ -1524,7 +2223,7 @@ function uiRunRoutes(r, ctx, deps = {}) {
1524
2223
  body: {
1525
2224
  sandbox,
1526
2225
  roles,
1527
- lessons: view2,
2226
+ lessons: view3,
1528
2227
  ...cur ? {
1529
2228
  current: {
1530
2229
  lesson: cur.id,
@@ -1564,7 +2263,8 @@ function uiRunRoutes(r, ctx, deps = {}) {
1564
2263
  return { status: 200, body: { ...out, ...hint ? { hint } : {} } };
1565
2264
  }).add("POST", "/api/learning/sandbox", async ({ body }) => {
1566
2265
  const b = parseBody2(api_exports.SandboxRequest, body);
1567
- const res = await run2((io) => sandboxCreate(resolve(b.dir), { json: true }, io, nowDate ? { now: nowDate() } : {}));
2266
+ const dir = b.dir.replace(/^~(?=$|[\\/])/, homedir());
2267
+ const res = await run2((io) => sandboxCreate(resolve(root, dir), { json: true }, io, nowDate ? { now: nowDate() } : {}));
1568
2268
  const out = jsonOf(res);
1569
2269
  if (res.code !== 0 || !out)
1570
2270
  throw refused2(res);
@@ -1736,10 +2436,10 @@ function uiKnowRoutes(r, ctx, o = {}) {
1736
2436
  let checked;
1737
2437
  const machine = (fresh = false) => {
1738
2438
  if (fresh || !checked || Date.now() - checked.at > 3e5) {
1739
- const run3 = { at: Date.now(), checks: doctor(ctx.root) };
1740
- checked = run3;
1741
- run3.checks.catch(() => {
1742
- if (checked === run3)
2439
+ const run4 = { at: Date.now(), checks: doctor(ctx.root) };
2440
+ checked = run4;
2441
+ run4.checks.catch(() => {
2442
+ if (checked === run4)
1743
2443
  checked = void 0;
1744
2444
  });
1745
2445
  }
@@ -1747,7 +2447,7 @@ function uiKnowRoutes(r, ctx, o = {}) {
1747
2447
  };
1748
2448
  if (o.warm)
1749
2449
  void machine().catch(() => void 0);
1750
- const view2 = async (name, arg) => {
2450
+ const view3 = async (name, arg) => {
1751
2451
  const build = VIEWS[name];
1752
2452
  if (!build)
1753
2453
  throw notFound(`There is no view ${name}.`);
@@ -1756,7 +2456,7 @@ function uiKnowRoutes(r, ctx, o = {}) {
1756
2456
  const args = build(arg);
1757
2457
  if (!args)
1758
2458
  throw badRequest(`The ${name} view needs an id: /api/view/${name}/<id>.`);
1759
- const { main } = await import("./chunk-F4D4G2A6.js");
2459
+ const { main } = await import("./chunk-7FHNFCHK.js");
1760
2460
  const out = [];
1761
2461
  const err = [];
1762
2462
  const code = await main([...args, "--json", "--root", ctx.root], { out: (l) => out.push(l), err: (l) => err.push(l) }, { quiet: true });
@@ -1773,6 +2473,12 @@ function uiKnowRoutes(r, ctx, o = {}) {
1773
2473
  } catch {
1774
2474
  body = void 0;
1775
2475
  }
2476
+ if (body === null && code !== 0) {
2477
+ const words = err.join("\n").trim() || "The command gave no answer.";
2478
+ if (/^(There is no|No .+ (is|named) )/i.test(words))
2479
+ throw notFound(words);
2480
+ throw new HttpError(422, "Atticus can't show that yet", words, scanFirst(words) ? { notYet: true } : void 0);
2481
+ }
1776
2482
  if (body === void 0) {
1777
2483
  const words = [...err, ...out].join("\n") || "No output.";
1778
2484
  throw new HttpError(422, "Atticus can't show that yet", words, scanFirst(words) ? { notYet: true } : void 0);
@@ -1780,7 +2486,7 @@ function uiKnowRoutes(r, ctx, o = {}) {
1780
2486
  return { status: 200, body: { code, data: body, problems: err } };
1781
2487
  };
1782
2488
  const text = async (args) => {
1783
- const { main } = await import("./chunk-F4D4G2A6.js");
2489
+ const { main } = await import("./chunk-7FHNFCHK.js");
1784
2490
  const out = [];
1785
2491
  const err = [];
1786
2492
  const code = await main([...args, "--root", ctx.root], { out: (l) => out.push(l), err: (l) => err.push(l) }, { quiet: true });
@@ -1798,7 +2504,7 @@ function uiKnowRoutes(r, ctx, o = {}) {
1798
2504
  throw badRequest("Some fields aren't right.", {
1799
2505
  errors: b.error.issues.map((i) => ({ path: i.path.join("."), message: i.message }))
1800
2506
  });
1801
- const { triageDecide } = await import("./chunk-3GLDB4IT.js");
2507
+ const { triageDecide } = await import("./chunk-ZKYRDV7A.js");
1802
2508
  const out = [];
1803
2509
  const err = [];
1804
2510
  const code = await triageDecide(ctx.root, params.itemId, b.data.option, { by: ctx.user, ...b.data.reason ? { reason: b.data.reason } : {} }, { out: (l) => out.push(l), err: (l) => err.push(l) });
@@ -1882,7 +2588,96 @@ function uiKnowRoutes(r, ctx, o = {}) {
1882
2588
  throw new HttpError(422, "Atticus can't show that yet", words, scanFirst(words) ? { notYet: true } : void 0);
1883
2589
  }
1884
2590
  return { status: 200, body: { view: JSON.parse(out) } };
1885
- }).add("GET", "/api/view/{name}", ({ params }) => view2(params.name)).add("GET", "/api/view/{name}/{id}", ({ params }) => view2(params.name, params.id));
2591
+ }).add("GET", "/api/coverage", async () => {
2592
+ const r2 = await view3("coverage");
2593
+ const data = r2.body.data;
2594
+ return { status: 200, body: data?.api ?? { areas: [], systems: [] } };
2595
+ }).add("GET", "/api/view/{name}", ({ params }) => view3(params.name)).add("GET", "/api/view/{name}/{id}", ({ params }) => view3(params.name, params.id));
2596
+ }
2597
+
2598
+ // packages/cli/dist/ui-api-initiatives.js
2599
+ var ID2 = /^[a-z0-9][a-z0-9._-]*$/;
2600
+ async function run3(f) {
2601
+ const out = [];
2602
+ const err = [];
2603
+ const code = await f({ out: (l) => out.push(l), err: (l) => err.push(l) });
2604
+ return { code, out, err };
2605
+ }
2606
+ function parseBody3(schema, body) {
2607
+ const r = schema.safeParse(body ?? {});
2608
+ if (!r.success)
2609
+ throw badRequest("Some fields aren't right.", {
2610
+ errors: r.error.issues.map((i) => ({ path: i.path.join("."), message: i.message }))
2611
+ });
2612
+ return r.data;
2613
+ }
2614
+ function refused3(r) {
2615
+ const detail = [...r.err, ...r.out].join("\n") || "Atticus refused.";
2616
+ if (/^There is no (initiative|commitment) /m.test(detail))
2617
+ return notFound(detail);
2618
+ if (/already exists/.test(detail))
2619
+ return conflict(detail);
2620
+ return r.code === 2 ? badRequest(detail) : new HttpError(422, "Atticus refused", detail);
2621
+ }
2622
+ function uiInitiativeRoutes(r, ctx) {
2623
+ const commands = () => import("./chunk-AE4P57KK.js");
2624
+ const changed = () => ctx.events.publish({ type: "file.changed", path: "catalog/curated" });
2625
+ return r.add("POST", "/api/initiatives", async ({ body }) => {
2626
+ const b = parseBody3(api_exports.NewInitiativeRequest, body);
2627
+ const { initiativeNew } = await commands();
2628
+ const res = await run3((io) => initiativeNew(ctx.root, {
2629
+ title: b.title,
2630
+ journey: b.journey,
2631
+ by: ctx.user,
2632
+ ...b.id ? { id: b.id } : {},
2633
+ ...b.release ? { release: b.release } : {},
2634
+ ...b.epic ? { epic: b.epic } : {},
2635
+ json: true
2636
+ }, io));
2637
+ if (res.code !== 0)
2638
+ throw refused3(res);
2639
+ const made = JSON.parse(res.out.join("\n"));
2640
+ changed();
2641
+ return {
2642
+ status: 201,
2643
+ body: {
2644
+ initiative: made.initiative.id,
2645
+ lines: [
2646
+ `Started ${made.initiative.id} (${made.initiative.journey}). Wrote ${made.written}.`
2647
+ ]
2648
+ }
2649
+ };
2650
+ }).add("POST", "/api/initiatives/{initiativeId}/plan", async ({ params, body }) => {
2651
+ const id = params.initiativeId;
2652
+ if (!ID2.test(id))
2653
+ throw badRequest(`${id} isn't an id.`);
2654
+ const b = parseBody3(api_exports.InitiativePlanRequest, body);
2655
+ const { initiativePlan } = await commands();
2656
+ const res = await run3((io) => initiativePlan(ctx.root, id, {
2657
+ by: ctx.user,
2658
+ ...b.target ? { target: b.target } : {},
2659
+ ...b.release ? { release: b.release } : {},
2660
+ ...b.systems?.length ? { systems: b.systems } : {}
2661
+ }, io));
2662
+ if (res.code !== 0)
2663
+ throw refused3(res);
2664
+ changed();
2665
+ return { status: 200, body: { initiative: id, lines: res.out } };
2666
+ }).add("POST", "/api/commitments/{commitmentId}/status", async ({ params, body }) => {
2667
+ const id = params.commitmentId;
2668
+ if (!ID2.test(id))
2669
+ throw badRequest(`${id} isn't an id.`);
2670
+ const b = parseBody3(api_exports.CommitmentStatusRequest, body);
2671
+ const c = (await readView(ctx.root)).get("Commitment", id);
2672
+ if (!c)
2673
+ throw notFound(`There is no commitment ${id}.`);
2674
+ const { initiativeCommitment } = await commands();
2675
+ const res = await run3((io) => initiativeCommitment(ctx.root, id, b.status, { by: ctx.user, ...b.note ? { note: b.note } : {} }, io));
2676
+ if (res.code !== 0)
2677
+ throw refused3(res);
2678
+ changed();
2679
+ return { status: 200, body: { initiative: c.initiative, lines: res.out } };
2680
+ });
1886
2681
  }
1887
2682
 
1888
2683
  // packages/cli/dist/ui-api-questions.js
@@ -1895,16 +2690,79 @@ function uiQuestionRoutes(r, ctx, deps = {}) {
1895
2690
  const mine = strParam(query, "mine");
1896
2691
  if (mine !== void 0 && mine !== "true")
1897
2692
  throw badRequest("mine is true, or left out.");
1898
- const listed = await listQuestions(ctx.root, state ? { state } : {});
1899
- const { routeQuestions } = await import("./chunk-MRCETHW5.js");
1900
- const routed = await routeQuestions(ctx.root, listed.questions, mine ? { mine: ctx.user } : {});
2693
+ const change = strParam(query, "change");
2694
+ if (change !== void 0 && !RecordId.safeParse(change).success)
2695
+ throw badRequest("change is a change's id, or left out.");
2696
+ const role = strParam(query, "role");
2697
+ if (role !== void 0 && !QuestionRole.safeParse(role).success)
2698
+ throw badRequest("role is product, architecture, analysis or delivery, or left out.");
2699
+ const onFile = await listQuestions(ctx.root);
2700
+ const every = { ...onFile, questions: onFile.questions.filter((q) => !q.finding) };
2701
+ const { routeQuestions } = await import("./chunk-Z3QFH7YG.js");
2702
+ const routedAll = await routeQuestions(ctx.root, every.questions, mine ? { mine: ctx.user } : {});
2703
+ const { stageQuestions, roleWithStage, isChangeQuestion, countByRole, personsRoles } = await import("./chunk-KYZMRSPS.js");
2704
+ const stages = await stageQuestions(ctx.root);
2705
+ const roleOf = (q) => roleWithStage(q, stages);
2706
+ for (const q of every.questions) {
2707
+ const r2 = roleOf(q);
2708
+ const a = routedAll.about[q.id];
2709
+ if (r2 && a)
2710
+ a.role = r2;
2711
+ }
2712
+ const base = change ? routedAll.questions.filter((q) => isChangeQuestion(q, change, { stages, restsOn: routedAll.about[q.id]?.restsOn })) : routedAll.questions;
2713
+ const routed = {
2714
+ ...routedAll,
2715
+ questions: base.filter((q) => (!state || q.state === state) && (!role || roleOf(q) === role))
2716
+ };
2717
+ const listed = { problems: every.problems };
2718
+ const n = (s) => base.filter((q) => q.state === s).length;
1901
2719
  const body = {
2720
+ counts: {
2721
+ open: n("open"),
2722
+ answered: n("answered"),
2723
+ retired: n("retired"),
2724
+ total: base.length,
2725
+ byRepo: Object.fromEntries([...new Set(base.map((q) => q.repo))].sort().map((r2) => {
2726
+ const of = base.filter((q) => q.repo === r2);
2727
+ const k = (s) => of.filter((q) => q.state === s).length;
2728
+ return [
2729
+ r2,
2730
+ {
2731
+ open: k("open"),
2732
+ answered: k("answered"),
2733
+ retired: k("retired"),
2734
+ total: of.length
2735
+ }
2736
+ ];
2737
+ })),
2738
+ byRole: countByRole(base, roleOf)
2739
+ },
1902
2740
  questions: routed.questions,
1903
2741
  problems: [...listed.problems, ...routed.problems],
1904
2742
  about: routed.about,
1905
- ...routed.me ? { me: routed.me } : {}
2743
+ ...routed.me ? { me: routed.me } : {},
2744
+ ...change ? { yourRoles: await personsRoles(ctx.root, change, ctx.user) } : {}
1906
2745
  };
1907
2746
  return { status: 200, body };
2747
+ }).add("GET", "/api/owner-questions", async () => {
2748
+ const { ownerQuestions } = await import("./chunk-M4H2UG4R.js");
2749
+ return { status: 200, body: await ownerQuestions(ctx.root, { now: now() }) };
2750
+ }).add("POST", "/api/owner-questions/{id}/answer", async ({ params, body }) => {
2751
+ const b = api_exports.OwnerAnswerRequest.safeParse(body ?? {});
2752
+ if (!b.success || !b.data.answer.trim())
2753
+ throw badRequest('Answer with { answer: "what the owner said" }.');
2754
+ const { answerOwnerQuestion, OwnerQuestionError } = await import("./chunk-M4H2UG4R.js");
2755
+ try {
2756
+ const r2 = await answerOwnerQuestion(ctx.root, params.id, b.data.answer, {
2757
+ by: ctx.user,
2758
+ now: now()
2759
+ });
2760
+ return { status: 200, body: r2 };
2761
+ } catch (e) {
2762
+ if (e instanceof OwnerQuestionError)
2763
+ throw e.code === "not-found" ? notFound(e.message) : badRequest(e.message);
2764
+ throw e;
2765
+ }
1908
2766
  }).add("GET", "/api/questions/{id}", async ({ params }) => {
1909
2767
  const q = await readQuestion(ctx.root, params.id).catch(() => {
1910
2768
  throw new HttpError(422, "Atticus can't read that question", `questions/${params.id}.yaml isn't a valid question.`);
@@ -1918,13 +2776,13 @@ function uiQuestionRoutes(r, ctx, deps = {}) {
1918
2776
  throw notFound(`There is no question ${params.id}.`);
1919
2777
  if (q.askedBy.step !== TARGETS_STEP_ID && q.askedBy.step !== TOPICS_STEP_ID)
1920
2778
  throw new HttpError(422, "That question doesn't ask where a call goes or who owns a topic", `${q.id} isn't a targets or topics question, so it has no picker.`);
1921
- const { pickerOf } = await import("./chunk-VIRTPODJ.js");
2779
+ const { pickerOf } = await import("./chunk-IFFRGC3N.js");
1922
2780
  return { status: 200, body: { picks: await pickerOf(ctx.root) } };
1923
2781
  }).add("GET", "/api/questions/{id}/evidence", async ({ params }) => {
1924
2782
  const q = await readQuestion(ctx.root, params.id).catch(() => void 0);
1925
2783
  if (!q)
1926
2784
  throw notFound(`There is no question ${params.id}.`);
1927
- const { evidenceExcerpts } = await import("./chunk-PHJOOL6U.js");
2785
+ const { evidenceExcerpts } = await import("./chunk-UFE36WJH.js");
1928
2786
  return {
1929
2787
  status: 200,
1930
2788
  body: {
@@ -1947,7 +2805,7 @@ function uiQuestionRoutes(r, ctx, deps = {}) {
1947
2805
  const q0 = await readQuestion(ctx.root, params.id).catch(() => void 0);
1948
2806
  if (!q0)
1949
2807
  throw notFound(`There is no question ${params.id}.`);
1950
- const { targetAnswerOf, offerLine } = await import("./chunk-VIRTPODJ.js");
2808
+ const { targetAnswerOf, offerLine } = await import("./chunk-IFFRGC3N.js");
1951
2809
  const t = await targetAnswerOf(ctx.root, q0, target, {
1952
2810
  ...addSystem ? { addSystem } : {},
1953
2811
  ...name ? { name } : {}
@@ -2018,15 +2876,15 @@ async function latestFindings3(root, change) {
2018
2876
  }
2019
2877
  var SEVERITY_ORDER = ["blocking", "gap", "risk", "idea"];
2020
2878
  var AT_RELEASE = /* @__PURE__ */ new Set(["release-ready", "released", "post-release-review"]);
2021
- async function acrossRows(root, view2) {
2879
+ async function acrossRows(root, view3) {
2022
2880
  const changes = await openChanges3(root);
2023
2881
  const rows = [];
2024
- const op = view2 === "requests" ? await loadOrgPack(orgPackDirOf(root)) : void 0;
2025
- const steps = view2 === "requests" ? await fileStepStore(root).list({ system: "service-desk" }).catch(() => []) : [];
2882
+ const op = view3 === "requests" ? await loadOrgPack(orgPackDirOf(root)) : void 0;
2883
+ const steps = view3 === "requests" ? await fileStepStore(root).list({ system: "service-desk" }).catch(() => []) : [];
2026
2884
  for (const c of changes) {
2027
2885
  const base = { change: c.id, changeTitle: c.title, changeState: c.state };
2028
2886
  const dir = changeFolder(root, c.id);
2029
- switch (view2) {
2887
+ switch (view3) {
2030
2888
  case "findings":
2031
2889
  for (const f of await latestFindings3(root, c.id))
2032
2890
  rows.push({
@@ -2113,9 +2971,9 @@ async function acrossRows(root, view2) {
2113
2971
  }
2114
2972
  }
2115
2973
  }
2116
- if (view2 === "findings")
2974
+ if (view3 === "findings")
2117
2975
  rows.sort((a, b) => Number(a.state !== "open") - Number(b.state !== "open") || SEVERITY_ORDER.indexOf(String(a.severity)) - SEVERITY_ORDER.indexOf(String(b.severity)) || a.change.localeCompare(b.change));
2118
- if (view2 === "releases")
2976
+ if (view3 === "releases")
2119
2977
  rows.sort((a, b) => Number(b.atRelease) - Number(a.atRelease));
2120
2978
  return rows;
2121
2979
  }
@@ -2205,13 +3063,13 @@ async function hopsFromTargetFlows(dir, mappings, interfaces) {
2205
3063
  function uiAcrossRoutes(r, ctx) {
2206
3064
  const { root } = ctx;
2207
3065
  return r.add("GET", "/api/across", async ({ query }) => {
2208
- const view2 = strParam(query, "view") ?? "";
2209
- if (!ACROSS_VIEWS.includes(view2))
3066
+ const view3 = strParam(query, "view") ?? "";
3067
+ if (!ACROSS_VIEWS.includes(view3))
2210
3068
  throw badRequest(`Say which view: ?view= one of ${ACROSS_VIEWS.join(", ")}.`);
2211
- const rows = await acrossRows(root, view2);
3069
+ const rows = await acrossRows(root, view3);
2212
3070
  return {
2213
3071
  status: 200,
2214
- body: { view: view2, changes: (await openChanges3(root)).length, rows }
3072
+ body: { view: view3, changes: (await openChanges3(root)).length, rows }
2215
3073
  };
2216
3074
  }).add("GET", "/api/changes/{changeId}/mappings", async ({ params }) => {
2217
3075
  try {
@@ -2224,8 +3082,9 @@ function uiAcrossRoutes(r, ctx) {
2224
3082
  }
2225
3083
 
2226
3084
  // packages/cli/dist/ui-api-graph.js
2227
- var import_yaml2 = __toESM(require_dist(), 1);
2228
- import { readFile as readFile3 } from "node:fs/promises";
3085
+ var import_yaml3 = __toESM(require_dist(), 1);
3086
+ import { readdir as readdir3, readFile as readFile3 } from "node:fs/promises";
3087
+ import { existsSync as existsSync2 } from "node:fs";
2229
3088
  import { join as join4 } from "node:path";
2230
3089
  var lensOf = (v, fallback) => {
2231
3090
  if (v === null)
@@ -2245,13 +3104,27 @@ var keyOf = (k) => {
2245
3104
  throw badRequest(`${k} isn't a view key. Use <lens>.<node>, e.g. architecture.estate.`);
2246
3105
  return k;
2247
3106
  };
3107
+ function vscodeFileLink(path, line) {
3108
+ const posix = path.replace(/\\/g, "/");
3109
+ const segs = (posix.startsWith("/") ? posix.slice(1) : posix).split("/").map((x) => /^[A-Za-z]:$/.test(x) ? x : encodeURIComponent(x));
3110
+ return `vscode://file/${segs.join("/")}:${line}`;
3111
+ }
2248
3112
  function uiGraphRoutes(r, ctx) {
2249
- const estate = async () => {
3113
+ const memoKey = `estate:${ctx.root}`;
3114
+ ctx.events.listen((e) => {
3115
+ if (e.type === "file.changed" && typeof e.path === "string" && /^(\.atticus[\\/])?catalog[\\/]/.test(e.path))
3116
+ recheckNow(memoKey);
3117
+ });
3118
+ const estate = async () => memoByFiles(memoKey, () => filesUnder(catalogDirOf(ctx.root)), buildFromCatalog, {
3119
+ recheckAfterMs: 1e3
3120
+ });
3121
+ const buildFromCatalog = async () => {
2250
3122
  const records = await catalogRecords(ctx.root);
2251
3123
  if (!records.length)
2252
3124
  throw new HttpError(422, "Atticus can't show that yet", "The catalog is empty: read the repos first (atticus map <repo>, then atticus verify <repo> --write).", { notYet: true });
2253
3125
  return buildEstate(records);
2254
3126
  };
3127
+ setTimeout(() => void estate().catch(() => void 0), 0).unref?.();
2255
3128
  const guard = (f) => {
2256
3129
  try {
2257
3130
  return f();
@@ -2261,11 +3134,13 @@ function uiGraphRoutes(r, ctx) {
2261
3134
  throw e;
2262
3135
  }
2263
3136
  };
2264
- const readLayout = async (view2) => {
3137
+ const layoutRel = (view3) => isAdopted(ctx.root) ? `.atticus/${layoutPath(view3)}` : layoutPath(view3);
3138
+ const viewsRel = diagramsRelOf(ctx.root) + "/views";
3139
+ const readLayout = async (view3) => {
2265
3140
  try {
2266
- const f = DiagramLayout.safeParse((0, import_yaml2.parse)(await readFile3(join4(ctx.root, layoutPath(view2)), "utf8")));
3141
+ const f = DiagramLayout.safeParse((0, import_yaml3.parse)(await readFile3(join4(ctx.root, layoutRel(view3)), "utf8")));
2267
3142
  if (!f.success)
2268
- throw new HttpError(422, "Atticus refused", `${layoutPath(view2)} isn't a layout file.`);
3143
+ throw new HttpError(422, "Atticus refused", `${layoutRel(view3)} isn't a layout file.`);
2269
3144
  return f.data;
2270
3145
  } catch (e) {
2271
3146
  if (e.code === "ENOENT")
@@ -2289,46 +3164,100 @@ function uiGraphRoutes(r, ctx) {
2289
3164
  const lens = lensOf(query.get("lens"), "data");
2290
3165
  const est = await estate();
2291
3166
  return { status: 200, body: guard(() => focusView(est, id, { lens })) };
3167
+ }).add("GET", "/api/graph/views", async () => {
3168
+ const dir = join4(ctx.root, ...viewsRel.split("/"));
3169
+ const names = (await readdir3(dir).catch(() => [])).filter((n) => n.endsWith(".yaml")).sort();
3170
+ const views = [];
3171
+ for (const n of names) {
3172
+ const v = api_exports.SavedCanvasView.safeParse((0, import_yaml3.parse)(await readFile3(join4(dir, n), "utf8").catch(() => "")));
3173
+ if (v.success)
3174
+ views.push(v.data);
3175
+ }
3176
+ return {
3177
+ status: 200,
3178
+ body: { views, dir: viewsRel }
3179
+ };
3180
+ }).add("PUT", "/api/graph/views/{name}", async ({ params, body, query }) => {
3181
+ const b = api_exports.SavedCanvasView.safeParse({ ...body, name: params.name });
3182
+ if (!b.success)
3183
+ throw badRequest("Name the view (lower case, digits and dashes) and give its lens and node.", {
3184
+ errors: b.error.issues.map((i) => ({ path: i.path.join("."), message: i.message }))
3185
+ });
3186
+ const v = { ...b.data, by: ctx.user, at: (/* @__PURE__ */ new Date()).toISOString() };
3187
+ const rel = `${viewsRel}/${v.name}.yaml`;
3188
+ if (existsSync2(join4(ctx.root, rel)) && query.get("replace") !== "1")
3189
+ throw conflict(`There is a view ${v.name} already (${rel}): pick another name, or replace it.`);
3190
+ await writeFileAtomic(join4(ctx.root, rel), (0, import_yaml3.stringify)(v));
3191
+ ctx.events.publish({ type: "file.changed", path: rel });
3192
+ return { status: 200, body: v };
3193
+ }).add("GET", "/api/code-link", async ({ query }) => {
3194
+ const repo = query.get("repo") ?? "";
3195
+ const at = query.get("at") ?? "";
3196
+ const m = /^(.+):(\d+)$/.exec(at);
3197
+ if (!/^[A-Za-z0-9._-]{1,200}$/.test(repo) || !m || m[1].includes(".."))
3198
+ throw badRequest("Say repo=<repo> and at=<file>:<line>.");
3199
+ const line = Number(m[2]);
3200
+ if (line < 1)
3201
+ throw badRequest("A line is 1 or more.");
3202
+ const dir = await checkoutOf(ctx.root, repo).catch(() => void 0);
3203
+ if (!dir || !existsSync2(dir))
3204
+ return {
3205
+ status: 200,
3206
+ body: {
3207
+ line,
3208
+ missing: `There is no checkout of ${repo} here (atticus sync clones it into the workspace).`
3209
+ }
3210
+ };
3211
+ const path = join4(dir, ...m[1].split("/"));
3212
+ return {
3213
+ status: 200,
3214
+ body: {
3215
+ path,
3216
+ line,
3217
+ vscode: vscodeFileLink(path, line),
3218
+ idea: `idea://open?file=${encodeURIComponent(path)}&line=${line}`
3219
+ }
3220
+ };
2292
3221
  }).add("GET", "/api/graph/layout/{viewKey}", async ({ params }) => {
2293
- const view2 = keyOf(params.viewKey);
2294
- const f = await readLayout(view2);
3222
+ const view3 = keyOf(params.viewKey);
3223
+ const f = await readLayout(view3);
2295
3224
  return {
2296
3225
  status: 200,
2297
- body: { view: view2, saved: !!f, path: layoutPath(view2), positions: f?.positions ?? {} }
3226
+ body: { view: view3, saved: !!f, path: layoutRel(view3), positions: f?.positions ?? {} }
2298
3227
  };
2299
3228
  }).add("PUT", "/api/graph/layout/{viewKey}", async ({ params, body }) => {
2300
- const view2 = keyOf(params.viewKey);
3229
+ const view3 = keyOf(params.viewKey);
2301
3230
  const b = api_exports.PutLayoutRequest.safeParse(body ?? {});
2302
3231
  if (!b.success)
2303
3232
  throw badRequest("Some fields aren't right.", {
2304
3233
  errors: b.error.issues.map((i) => ({ path: i.path.join("."), message: i.message }))
2305
3234
  });
2306
3235
  const { lens, node, positions } = b.data;
2307
- if (view2 !== viewKey(lens, node) && view2 !== viewKey(lens, node, "focus"))
2308
- throw badRequest(`${view2} isn't the view key of ${node} in the ${lens} lens.`);
3236
+ if (view3 !== viewKey(lens, node) && view3 !== viewKey(lens, node, "focus"))
3237
+ throw badRequest(`${view3} isn't the view key of ${node} in the ${lens} lens.`);
2309
3238
  if (Object.keys(positions).length > 5e3)
2310
3239
  throw badRequest("At most 5000 boxes per view.");
2311
3240
  const sorted = Object.fromEntries(Object.entries(positions).sort(([a], [b2]) => a < b2 ? -1 : 1).map(([k, p]) => [k, { x: Math.round(p.x), y: Math.round(p.y) }]));
2312
3241
  const file = DiagramLayout.parse({
2313
3242
  schemaVersion: LAYOUT_SCHEMA_VERSION,
2314
- view: view2,
3243
+ view: view3,
2315
3244
  lens,
2316
3245
  node,
2317
3246
  by: ctx.user,
2318
3247
  at: (/* @__PURE__ */ new Date()).toISOString(),
2319
3248
  positions: sorted
2320
3249
  });
2321
- await writeFileAtomic(join4(ctx.root, layoutPath(view2)), (0, import_yaml2.stringify)(file));
2322
- ctx.events.publish({ type: "file.changed", path: layoutPath(view2) });
3250
+ await writeFileAtomic(join4(ctx.root, layoutRel(view3)), (0, import_yaml3.stringify)(file));
3251
+ ctx.events.publish({ type: "file.changed", path: layoutRel(view3) });
2323
3252
  return {
2324
3253
  status: 200,
2325
- body: { view: view2, saved: true, path: layoutPath(view2), positions: file.positions }
3254
+ body: { view: view3, saved: true, path: layoutRel(view3), positions: file.positions }
2326
3255
  };
2327
3256
  });
2328
3257
  }
2329
3258
 
2330
3259
  // packages/cli/dist/ui-api-specs.js
2331
- import { stat as stat2 } from "node:fs/promises";
3260
+ import { stat as stat3 } from "node:fs/promises";
2332
3261
  import { join as join5 } from "node:path";
2333
3262
  var idOf2 = (v, what) => {
2334
3263
  if (!RecordId.safeParse(v).success)
@@ -2435,7 +3364,7 @@ function uiSpecRoutes(r, ctx) {
2435
3364
  });
2436
3365
  const deltasOf = async (change) => {
2437
3366
  const dir = join5(changesDirOf(root), change);
2438
- if (!await stat2(join5(dir, "change.yaml")).catch(() => void 0))
3367
+ if (!await stat3(join5(dir, "change.yaml")).catch(() => void 0))
2439
3368
  throw notFound(`There is no change ${change}.`);
2440
3369
  return readDeltas(dir);
2441
3370
  };
@@ -2459,7 +3388,7 @@ function uiSpecRoutes(r, ctx) {
2459
3388
  });
2460
3389
  r.add("GET", "/api/changes/{changeId}/conformance", async ({ params }) => {
2461
3390
  const change = idOf2(params.changeId, "change");
2462
- const { conformanceOf } = await import("./chunk-QWSLMVHI.js");
3391
+ const { conformanceOf } = await import("./chunk-FRHD22WM.js");
2463
3392
  const c = await conformanceOf(root, change);
2464
3393
  if (!c)
2465
3394
  throw notFound(`There is no change ${change}.`);
@@ -2474,7 +3403,7 @@ function uiSpecRoutes(r, ctx) {
2474
3403
  });
2475
3404
  r.add("GET", "/api/changes/{changeId}/impact", async ({ params }) => {
2476
3405
  const change = idOf2(params.changeId, "change");
2477
- const { changeImpactOf } = await import("./chunk-4REDBQOZ.js");
3406
+ const { changeImpactOf } = await import("./chunk-2D7PWSOK.js");
2478
3407
  const i = await changeImpactOf(root, change);
2479
3408
  if (!i)
2480
3409
  throw notFound(`There is no change ${change}.`);
@@ -2508,8 +3437,8 @@ function uiSpecRoutes(r, ctx) {
2508
3437
  }
2509
3438
 
2510
3439
  // packages/cli/dist/ui-api-knowledge.js
2511
- var import_yaml3 = __toESM(require_dist(), 1);
2512
- import { readFile as readFile4, stat as stat3 } from "node:fs/promises";
3440
+ var import_yaml4 = __toESM(require_dist(), 1);
3441
+ import { readFile as readFile4, stat as stat4 } from "node:fs/promises";
2513
3442
  import { join as join6 } from "node:path";
2514
3443
  var STALE_DAYS = 14;
2515
3444
  var SLUG = /^[a-z0-9][a-z0-9-]*$/;
@@ -2579,7 +3508,7 @@ function candidateView(c) {
2579
3508
  keepAs: keepAs(r.kind)
2580
3509
  };
2581
3510
  }
2582
- var exists = (p) => stat3(p).then(() => true, () => false);
3511
+ var exists = (p) => stat4(p).then(() => true, () => false);
2583
3512
  function capture() {
2584
3513
  const lines = [];
2585
3514
  const problems = [];
@@ -2670,7 +3599,7 @@ function uiKnowledgeRoutes(r, ctx, deps = {}) {
2670
3599
  const platforms = [];
2671
3600
  for (const { platform } of op.profiles) {
2672
3601
  const text = await readFile4(join6(proposalsDirOf(root), "orgpack", `${platform}.yaml`), "utf8").catch(() => void 0);
2673
- const data = text === void 0 ? void 0 : (0, import_yaml3.parse)(text);
3602
+ const data = text === void 0 ? void 0 : (0, import_yaml4.parse)(text);
2674
3603
  const proposals = Array.isArray(data?.proposals) ? data.proposals.flatMap((p) => {
2675
3604
  const x = OrgPackProposal.safeParse(p);
2676
3605
  return x.success ? [x.data] : [];
@@ -2702,7 +3631,7 @@ function uiKnowledgeRoutes(r, ctx, deps = {}) {
2702
3631
  if (date) {
2703
3632
  const file = join6(knowledgeDirOf(root), "snapshots", w.id, date, SNAPSHOT_FILE);
2704
3633
  const text = await readFile4(file, "utf8").catch(() => void 0);
2705
- const m = text === void 0 ? void 0 : SnapshotManifest.safeParse((0, import_yaml3.parse)(text));
3634
+ const m = text === void 0 ? void 0 : SnapshotManifest.safeParse((0, import_yaml4.parse)(text));
2706
3635
  if (m?.success)
2707
3636
  snapshot = {
2708
3637
  date,
@@ -2942,7 +3871,7 @@ function uiKnowledgeRoutes(r, ctx, deps = {}) {
2942
3871
  if (fileExists && !f)
2943
3872
  throw new HttpError(422, "Atticus can't read that file", `${changesRelOf(root)}/${change}/${NEEDS_FILE} isn't a valid needs file.`);
2944
3873
  const k = f ? await readKnowledge(root, { cacheRoot: root }) : void 0;
2945
- const view2 = (n) => ({
3874
+ const view3 = (n) => ({
2946
3875
  ...n,
2947
3876
  ...k?.reviews[n.item] ? { review: k.reviews[n.item] } : {}
2948
3877
  });
@@ -2951,7 +3880,7 @@ function uiKnowledgeRoutes(r, ctx, deps = {}) {
2951
3880
  file: `${changesRelOf(root)}/${change}/${NEEDS_FILE}`,
2952
3881
  present: !!f,
2953
3882
  ...f ? { updatedAt: f.updatedAt } : {},
2954
- needs: (f?.needs ?? []).map(view2),
3883
+ needs: (f?.needs ?? []).map(view3),
2955
3884
  considered: f?.considered ?? [],
2956
3885
  command: `atticus change needs ${change}`,
2957
3886
  slash: `/atticus-needs ${change}`
@@ -3016,7 +3945,8 @@ function uiKnowledgeRoutes(r, ctx, deps = {}) {
3016
3945
  const result = {
3017
3946
  knowledge: await appliesView(change),
3018
3947
  file: before.file,
3019
- commit: `git add ${before.file} && git commit -m "${change}: the knowledge that applies"`
3948
+ commit: `git add ${before.file}
3949
+ git commit -m "${change}: the knowledge that applies"`
3020
3950
  };
3021
3951
  return { status: 200, body: result };
3022
3952
  }).add("GET", "/api/orgpack/draft", async () => ({ status: 200, body: await draftView() })).add("POST", "/api/orgpack/draft", async ({ body }) => {
@@ -3054,7 +3984,7 @@ function uiKnowledgeRoutes(r, ctx, deps = {}) {
3054
3984
  // packages/cli/dist/ui-api-runs.js
3055
3985
  import { readFile as readFile5 } from "node:fs/promises";
3056
3986
  import { join as join7 } from "node:path";
3057
- var import_yaml4 = __toESM(require_dist(), 1);
3987
+ var import_yaml5 = __toESM(require_dist(), 1);
3058
3988
  async function serverModel(root) {
3059
3989
  return (await configuredModel(root))?.type ?? null;
3060
3990
  }
@@ -3064,7 +3994,7 @@ async function configuredModel(root) {
3064
3994
  return null;
3065
3995
  let y;
3066
3996
  try {
3067
- y = (0, import_yaml4.parse)(text);
3997
+ y = (0, import_yaml5.parse)(text);
3068
3998
  } catch {
3069
3999
  return null;
3070
4000
  }
@@ -3080,26 +4010,29 @@ async function configuredModel(root) {
3080
4010
  function uiModelRunRoutes(r, ctx, deps = {}) {
3081
4011
  const root = ctx.root;
3082
4012
  const runStep = deps.runStep ?? (async (args) => {
3083
- const { main } = await import("./chunk-F4D4G2A6.js");
4013
+ const { main } = await import("./chunk-7FHNFCHK.js");
3084
4014
  const out = [];
3085
4015
  const err = [];
3086
4016
  const code = await main([...args, "--root", root], { out: (l) => out.push(l), err: (l) => err.push(l) }, { quiet: true });
3087
4017
  return { code, out, err };
3088
4018
  });
3089
- return r.add("POST", "/api/runs", async ({ body }) => {
3090
- const args = body?.args;
3091
- if (!Array.isArray(args) || args.length < 2 || !args.every((a) => typeof a === "string" && a.trim() !== ""))
3092
- throw badRequest(`args is the step's command words and arguments, for example ["scan", "survey", "forge-ledger"].`);
3093
- const words = args;
3094
- if (!isHandoffStep(words[0], words[1]))
3095
- throw badRequest(`atticus ${words.slice(0, 2).join(" ")} isn't an AI step; the app starts only those.`);
3096
- const option = words.find((a) => a.startsWith("-"));
3097
- if (option)
3098
- throw badRequest(`${option} would be read as an option; give the step's arguments only.`);
4019
+ return r.add("POST", "/api/runs/handoff", async ({ body }) => {
4020
+ const words = stepWords(body);
4021
+ const { prepareStep } = await import("./chunk-J2O4LFVI.js");
4022
+ const { code, prepared } = await prepareStep(root, runStep, words);
4023
+ ctx.events.publish({ type: "file.changed", path: runsRelOf(root) });
4024
+ if (!prepared.runId && code !== 0)
4025
+ throw new HttpError(422, "The step wasn't prepared", prepared.lines.join("\n") || "The step prepared no run.");
4026
+ return { status: 201, body: prepared };
4027
+ }).add("POST", "/api/runs", async ({ body }) => {
4028
+ const words = stepWords(body);
3099
4029
  const configured = await configuredModel(root);
3100
4030
  const model = configured?.type;
3101
4031
  if (!model)
3102
4032
  throw conflict("This server's runtime.yaml configures no model, so it can't run an AI step. Run it in your assistant (its slash command), or from VS Code with Copilot.");
4033
+ const busy = ctx.jobs.runningOf("step");
4034
+ if (busy)
4035
+ throw conflict(`An AI step is running on this server (${busy.id}): wait for it to finish, or cancel it.`);
3103
4036
  const job = ctx.jobs.start("step", async (c) => {
3104
4037
  c.progress(0, 1, `Running atticus ${words.join(" ")} with ${model}`);
3105
4038
  const before = new Set((await listRuns(root, { limit: 1e5 })).map((x) => x.runId));
@@ -3140,6 +4073,900 @@ function uiModelRunRoutes(r, ctx, deps = {}) {
3140
4073
  return { status: 200, body: t };
3141
4074
  });
3142
4075
  }
4076
+ function stepWords(body) {
4077
+ const args = body?.args;
4078
+ if (!Array.isArray(args) || args.length < 2 || !args.every((a) => typeof a === "string" && a.trim() !== ""))
4079
+ throw badRequest(`args is the step's command words and arguments, for example ["scan", "survey", "forge-ledger"].`);
4080
+ const words = args;
4081
+ if (!isHandoffStep(words[0], words[1]))
4082
+ throw badRequest(`atticus ${words.slice(0, 2).join(" ")} isn't an AI step; the app starts only those.`);
4083
+ const option = withoutSource(words).find((a) => a.startsWith("-"));
4084
+ if (option)
4085
+ throw badRequest(`${option} would be read as an option; give the step's arguments only.`);
4086
+ return words;
4087
+ }
4088
+ var SOURCE_STEPS = /* @__PURE__ */ new Set(["knowledge scan", "knowledge find"]);
4089
+ function withoutSource(words) {
4090
+ if (!SOURCE_STEPS.has(`${words[0]} ${words[1]}`))
4091
+ return words;
4092
+ const at = words.indexOf("--source");
4093
+ const value = words[at + 1];
4094
+ if (at < 0 || !value || value.startsWith("-"))
4095
+ return words;
4096
+ return [...words.slice(0, at), ...words.slice(at + 2)];
4097
+ }
4098
+
4099
+ // packages/cli/dist/ui-api-copilot-runs.js
4100
+ import { randomBytes } from "node:crypto";
4101
+
4102
+ // packages/cli/dist/copilot-run.js
4103
+ var TAB_LINES = 5e3;
4104
+ var CopilotRun = class {
4105
+ view;
4106
+ current = "run";
4107
+ constructor(o) {
4108
+ this.view = {
4109
+ runId: o.runId,
4110
+ jobId: o.jobId,
4111
+ kind: o.kind,
4112
+ ...o.repos?.length ? { repos: o.repos } : {},
4113
+ ...o.force ? { force: true } : {},
4114
+ state: "running",
4115
+ ...o.by ? { by: o.by } : {},
4116
+ ...o.model ? { model: o.model } : {},
4117
+ startedAt: o.startedAt,
4118
+ progress: { repos: 0, repoIndex: 0, sessions: 0 },
4119
+ totals: { kept: 0, rejected: 0, tokens: 0 },
4120
+ tabs: [{ id: "run", title: "Run", state: "running", from: 0, lines: [] }]
4121
+ };
4122
+ }
4123
+ tab(id) {
4124
+ return this.view.tabs.find((t) => t.id === id) ?? this.view.tabs[0];
4125
+ }
4126
+ push(id, lines) {
4127
+ const t = this.tab(id);
4128
+ const at = t.from + t.lines.length;
4129
+ t.lines.push(...lines);
4130
+ if (t.lines.length > TAB_LINES) {
4131
+ const drop = t.lines.length - TAB_LINES;
4132
+ t.lines.splice(0, drop);
4133
+ t.from += drop;
4134
+ }
4135
+ return { tab: t.id, title: t.title, at, lines };
4136
+ }
4137
+ /** A line the run printed: into the session under way (or just ended, until the next repo). */
4138
+ line(text) {
4139
+ return this.push(this.current, [text]);
4140
+ }
4141
+ /** A step of the run: progress, and the tabs it opens or closes. Returns the lines it adds. */
4142
+ event(e) {
4143
+ const v = this.view;
4144
+ const out = [];
4145
+ switch (e.type) {
4146
+ case "plan":
4147
+ v.progress.repos = e.repos.length;
4148
+ break;
4149
+ case "repo":
4150
+ v.progress.repoIndex = e.index;
4151
+ v.progress.repos = e.total;
4152
+ v.progress.repo = e.repo;
4153
+ delete v.progress.session;
4154
+ this.current = "run";
4155
+ break;
4156
+ case "session": {
4157
+ const id = `s${v.progress.sessions + 1}`;
4158
+ v.progress.sessions++;
4159
+ const title = `${e.repo}, ${e.label}${e.attempt ? " (again)" : ""}`;
4160
+ v.progress.session = title;
4161
+ v.tabs.push({ id, title, repo: e.repo, state: "running", from: 0, lines: [] });
4162
+ out.push(this.push("run", [`Session ${v.progress.sessions}: ${title}`]));
4163
+ this.current = id;
4164
+ break;
4165
+ }
4166
+ case "session.end": {
4167
+ const t = v.tabs.findLast((x) => x.repo === e.repo && x.state === "running");
4168
+ if (t) {
4169
+ t.state = e.failed ? "failed" : "ended";
4170
+ t.ended = e.failed ?? e.ended;
4171
+ out.push(this.push("run", [`${t.title}: ended (${t.ended})`]));
4172
+ }
4173
+ if (e.tokens)
4174
+ v.totals.tokens += e.tokens;
4175
+ delete v.progress.session;
4176
+ break;
4177
+ }
4178
+ case "verify":
4179
+ v.totals.kept += e.kept;
4180
+ v.totals.rejected += e.rejected;
4181
+ if (this.current !== "run")
4182
+ out.push(this.push("run", [
4183
+ `${e.repo}: ${e.kept} kept, ${e.rejected} rejected, coverage ${Math.round(e.coverage * 100)}%, ${e.open} open`
4184
+ ]));
4185
+ break;
4186
+ }
4187
+ return out;
4188
+ }
4189
+ /** How it ended: done, stopped by a person, or failed with the error said. */
4190
+ finish(state, at, message) {
4191
+ const v = this.view;
4192
+ v.state = state;
4193
+ v.finishedAt = at;
4194
+ if (message)
4195
+ v.message = message;
4196
+ for (const t of v.tabs)
4197
+ if (t.state === "running") {
4198
+ t.state = state === "failed" ? "failed" : "ended";
4199
+ if (t.id !== "run")
4200
+ t.ended ??= state;
4201
+ }
4202
+ this.current = "run";
4203
+ return this.push("run", [
4204
+ state === "done" ? "The run finished." : state === "stopped" ? "Stopped. The next run carries on from here." : `The run stopped: ${message ?? "an error"}`
4205
+ ]);
4206
+ }
4207
+ /** The state without the lines, for the live event. */
4208
+ summary() {
4209
+ const { tabs: _tabs, ...rest } = this.view;
4210
+ return structuredClone(rest);
4211
+ }
4212
+ };
4213
+
4214
+ // packages/cli/dist/ui-api-copilot-runs.js
4215
+ var defaultStarter = (root) => async (o, io, deps) => {
4216
+ const { mapAllCommand } = await import("./chunk-YF5DZCPN.js");
4217
+ return mapAllCommand(root, o, io, {
4218
+ ...deps,
4219
+ makeAcp: async () => (await import("./chunk-ZPDDEVQX.js")).acpStepRunner(root)
4220
+ });
4221
+ };
4222
+ var TAIL = 200;
4223
+ var NOT_ALLOWED = "runtime.yaml doesn't allow copilot-acp here (schemaVersion 3, copilot-acp in modes.allowed and copilot in assistants.allowed), so the app can't start Copilot itself. Read through Copilot Chat instead: atticus map --all in a terminal gives each part to paste.";
4224
+ async function allowedHere(root) {
4225
+ try {
4226
+ const op = await loadOrgPack(orgPackDirOf(root));
4227
+ const c = copilotAcpOf(op.runtime);
4228
+ return { allowed: copilotAcpAllowed(op), ...c.model ? { model: c.model } : {} };
4229
+ } catch {
4230
+ return { allowed: false };
4231
+ }
4232
+ }
4233
+ var optionsOf = (b) => ({
4234
+ mode: "copilot-acp",
4235
+ ...b.kind === "stale" ? { stale: true } : {},
4236
+ ...b.kind === "gaps" ? { gaps: true } : {},
4237
+ ...b.repos?.length ? { repos: b.repos } : {},
4238
+ ...b.force ? { force: true } : {},
4239
+ resumeAs: "the next run (Map from the app, or atticus map --all)"
4240
+ });
4241
+ function uiCopilotRunRoutes(r, ctx, deps = {}) {
4242
+ const start = deps.start ?? defaultStarter(ctx.root);
4243
+ const now = deps.now ?? (() => /* @__PURE__ */ new Date());
4244
+ const flushMs = deps.flushMs ?? 100;
4245
+ let run4;
4246
+ let allowed;
4247
+ let seq = 0;
4248
+ let starting = false;
4249
+ const pending = /* @__PURE__ */ new Map();
4250
+ let timer;
4251
+ const flush = () => {
4252
+ timer = void 0;
4253
+ const r0 = run4;
4254
+ if (!r0)
4255
+ return;
4256
+ for (const n of pending.values())
4257
+ ctx.events.publish({ type: "copilot-run.lines", runId: r0.view.runId, ...n });
4258
+ pending.clear();
4259
+ };
4260
+ const queue = (n) => {
4261
+ const p = pending.get(n.tab);
4262
+ if (p)
4263
+ p.lines.push(...n.lines);
4264
+ else
4265
+ pending.set(n.tab, { ...n, lines: [...n.lines] });
4266
+ timer ??= setTimeout(flush, flushMs);
4267
+ };
4268
+ const state = () => {
4269
+ if (run4)
4270
+ ctx.events.publish({ type: "copilot-run.state", run: run4.summary() });
4271
+ };
4272
+ return r.add("GET", "/api/copilot-runs/current", async ({ query }) => {
4273
+ if (!allowed || Date.now() - allowed.at > 1e4)
4274
+ allowed = { at: Date.now(), v: await allowedHere(ctx.root) };
4275
+ const a = allowed.v;
4276
+ const tail = Math.min(Math.max(Number(query.get("tail") ?? TAIL) || TAIL, 0), TAB_LINES);
4277
+ const view3 = run4 ? {
4278
+ ...run4.summary(),
4279
+ tabs: run4.view.tabs.map((t) => {
4280
+ const keep = tail ? t.lines.slice(-tail) : t.lines;
4281
+ return { ...t, from: t.from + t.lines.length - keep.length, lines: [...keep] };
4282
+ })
4283
+ } : null;
4284
+ return {
4285
+ status: 200,
4286
+ body: {
4287
+ run: view3,
4288
+ allowed: a.allowed,
4289
+ ...a.allowed ? {} : { why: NOT_ALLOWED },
4290
+ ...a.model ? { model: a.model } : {}
4291
+ }
4292
+ };
4293
+ }).add("POST", "/api/copilot-runs", async ({ body }) => {
4294
+ const b = api_exports.StartCopilotRunRequest.safeParse(body ?? {});
4295
+ if (!b.success)
4296
+ throw badRequest("Some fields aren't right.", {
4297
+ errors: b.error.issues.map((i) => ({ path: i.path.join("."), message: i.message }))
4298
+ });
4299
+ if (b.data.kind === "repos" && !b.data.repos?.length)
4300
+ throw badRequest("Name the repos to read (repos), or pick stale, gaps or all.");
4301
+ if (run4?.view.state === "running")
4302
+ throw conflict(`A Copilot run is under way (${run4.view.runId}, started ${run4.view.startedAt}): stop it, or wait for it to finish.`);
4303
+ if (starting)
4304
+ throw conflict("A Copilot run is starting: wait for it, then stop it if need be.");
4305
+ const busy = ctx.jobs.runningOf("step");
4306
+ if (busy?.state === "running")
4307
+ throw conflict(`An AI step is running on this server (${busy.id}): wait for it to finish.`);
4308
+ starting = true;
4309
+ let a;
4310
+ try {
4311
+ a = await allowedHere(ctx.root);
4312
+ } finally {
4313
+ starting = false;
4314
+ }
4315
+ if (!a.allowed)
4316
+ throw conflict(NOT_ALLOWED);
4317
+ if (ctx.jobs.runningOf("step")?.state === "running")
4318
+ throw conflict("An AI step started on this server meanwhile: wait for it to finish.");
4319
+ const runId = `copilot-run-${now().getTime().toString(36)}-${randomBytes(3).toString("hex")}-${++seq}`;
4320
+ const me = new CopilotRun({
4321
+ runId,
4322
+ jobId: "",
4323
+ kind: b.data.kind,
4324
+ ...b.data.repos ? { repos: b.data.repos } : {},
4325
+ ...b.data.force ? { force: true } : {},
4326
+ ...ctx.user ? { by: ctx.user } : {},
4327
+ ...a.model ? { model: a.model } : {},
4328
+ startedAt: now().toISOString()
4329
+ });
4330
+ run4 = me;
4331
+ let lastErr;
4332
+ const job = ctx.jobs.start("step", async (c) => {
4333
+ const io = {
4334
+ out: (l) => queue(me.line(l)),
4335
+ err: (l) => {
4336
+ lastErr = l;
4337
+ queue(me.line(l));
4338
+ }
4339
+ };
4340
+ const onEvent = (e) => {
4341
+ for (const n of me.event(e))
4342
+ queue(n);
4343
+ const p = me.view.progress;
4344
+ c.progress(p.repoIndex, p.repos, p.session ?? p.repo ?? "Planning");
4345
+ state();
4346
+ };
4347
+ const signal = currentSignal();
4348
+ let code;
4349
+ try {
4350
+ code = await start(optionsOf(b.data), io, {
4351
+ ...signal ? { signal } : {},
4352
+ onEvent
4353
+ });
4354
+ } catch (e) {
4355
+ const msg = e instanceof Error ? e.message : String(e);
4356
+ queue(me.finish(signal?.aborted ? "stopped" : "failed", now().toISOString(), msg));
4357
+ flush();
4358
+ state();
4359
+ ctx.events.publish({ type: "file.changed", path: "facts" });
4360
+ return { errors: [msg] };
4361
+ }
4362
+ const stopped = signal?.aborted === true;
4363
+ queue(me.finish(stopped ? "stopped" : code === 0 ? "done" : "failed", now().toISOString(), stopped || code === 0 ? void 0 : lastErr ?? `atticus map --all ended with ${code}`));
4364
+ flush();
4365
+ state();
4366
+ ctx.events.publish({ type: "file.changed", path: "facts" });
4367
+ return { result: { code, runId } };
4368
+ });
4369
+ me.view.jobId = job.id;
4370
+ state();
4371
+ return { status: 202, body: structuredClone(me.view) };
4372
+ }).add("GET", "/api/copilot-runs/{runId}/tabs/{tab}", async ({ params, query }) => {
4373
+ if (!run4 || run4.view.runId !== params.runId)
4374
+ throw notFound(`There is no run ${params.runId}.`);
4375
+ const t = run4.view.tabs.find((x) => x.id === params.tab);
4376
+ if (!t)
4377
+ throw notFound(`${params.runId} has no tab ${params.tab}.`);
4378
+ const from = Math.max(Number(query.get("from") ?? 0) || 0, t.from);
4379
+ return {
4380
+ status: 200,
4381
+ body: { id: t.id, title: t.title, from, lines: t.lines.slice(from - t.from) }
4382
+ };
4383
+ }).add("POST", "/api/copilot-runs/{runId}/stop", async ({ params }) => {
4384
+ if (!run4 || run4.view.runId !== params.runId)
4385
+ throw notFound(`There is no run ${params.runId}.`);
4386
+ if (run4.view.state !== "running")
4387
+ throw conflict(`${params.runId} has already ${run4.view.state === "done" ? "finished" : run4.view.state}.`);
4388
+ ctx.jobs.cancel(run4.view.jobId);
4389
+ queue(run4.line("Stopping: the session under way is cancelled..."));
4390
+ return { status: 200, body: structuredClone(run4.view) };
4391
+ });
4392
+ }
4393
+
4394
+ // packages/cli/dist/ui-api-link-match.js
4395
+ var keyOf2 = (from, to) => `${from}\0${to.repo ?? ""}\0${to.system ?? ""}`;
4396
+ function uiLinkMatchRoutes(r, ctx, deps = {}) {
4397
+ const running = /* @__PURE__ */ new Map();
4398
+ return r.add("GET", "/api/links/match", async ({ query }) => {
4399
+ const { from, to } = boxesOf(query);
4400
+ const { view: view3 } = await candidatesOf(ctx.root, from, to);
4401
+ const run4 = running.get(keyOf2(from, to));
4402
+ const job = run4 ? ctx.jobs.get(run4.jobId) : void 0;
4403
+ if (run4 && job?.state === "running") {
4404
+ const files = await readMatch(ctx.root, view3);
4405
+ return {
4406
+ status: 200,
4407
+ body: {
4408
+ ...files,
4409
+ state: "running",
4410
+ jobId: run4.jobId,
4411
+ proposals: [],
4412
+ none: [],
4413
+ rejected: []
4414
+ }
4415
+ };
4416
+ }
4417
+ const m = await readMatch(ctx.root, view3);
4418
+ if (run4?.error && m.state === "handoff") {
4419
+ m.state = "failed";
4420
+ m.error = run4.error;
4421
+ }
4422
+ return { status: 200, body: m };
4423
+ }).add("POST", "/api/links/match/done", async ({ body }) => {
4424
+ const b = api_exports.MatchLinksRequest.safeParse(body ?? {});
4425
+ if (!b.success)
4426
+ throw badRequest("Say both boxes: { from: <repo>, to: { repo } | { system } }.");
4427
+ const { from, to } = b.data;
4428
+ const { view: view3, facts } = await candidatesOf(ctx.root, from, to);
4429
+ const run4 = running.get(keyOf2(from, to));
4430
+ if (run4 && ctx.jobs.get(run4.jobId)?.state === "running")
4431
+ throw conflict(`A match of ${from} with ${view3.toName} is under way: wait for it.`);
4432
+ const m = await matchDone(ctx.root, { view: view3, facts });
4433
+ if (m.state === "handoff" || m.state === "idle")
4434
+ throw conflict(m.error ?? `No answer is written yet for ${from} and ${view3.toName}.`);
4435
+ if (m.decided?.batch) {
4436
+ ctx.events.publish({ type: "file.changed", path: `facts/${from}.corrections.jsonl` });
4437
+ ctx.events.publish({ type: "file.changed", path: "decisions/" });
4438
+ }
4439
+ return { status: 200, body: m };
4440
+ }).add("POST", "/api/links/match", async ({ body }) => {
4441
+ const b = api_exports.MatchLinksRequest.safeParse(body ?? {});
4442
+ if (!b.success)
4443
+ throw badRequest("Say both boxes: { from: <repo>, to: { repo } | { system } }.");
4444
+ const { from, to } = b.data;
4445
+ const { view: view3, facts } = await candidatesOf(ctx.root, from, to);
4446
+ if (!view3.calls.some((c) => c.status !== "paired"))
4447
+ throw conflict(`${from} has no loose call to match with ${view3.toName}.`);
4448
+ if (!view3.apis.length)
4449
+ throw conflict(`${view3.toName} has no API on record${view3.noApis ? `: ${view3.noApis.command}` : ""}.`);
4450
+ const key = keyOf2(from, to);
4451
+ const op = await loadOrgPack(orgPackDirOf(ctx.root));
4452
+ const had = running.get(key);
4453
+ if (had && ctx.jobs.get(had.jobId)?.state === "running")
4454
+ throw conflict(`A match of ${from} with ${view3.toName} is under way: wait for it.`);
4455
+ const busy = ctx.jobs.runningOf("step");
4456
+ if (busy?.state === "running")
4457
+ throw conflict(`An AI step is running on this server (${busy.id}): wait for it to finish.`);
4458
+ if (!copilotAcpAllowed(op) && !deps.runner) {
4459
+ const m = await runMatch(ctx.root, { view: view3, quotes: quotesOf(facts), mode: "hand-off" });
4460
+ return { status: 202, body: m };
4461
+ }
4462
+ let done;
4463
+ const job = ctx.jobs.start("step", async (c) => {
4464
+ c.progress(0, 1, `Matching ${from} with ${view3.toName}`);
4465
+ const signal = currentSignal();
4466
+ try {
4467
+ done = await runMatch(ctx.root, {
4468
+ view: view3,
4469
+ quotes: quotesOf(facts),
4470
+ facts,
4471
+ mode: "copilot-acp",
4472
+ ...deps.runner ? { runner: deps.runner } : {},
4473
+ ...signal ? { signal } : {},
4474
+ onProgress: (line) => c.progress(0, 1, line.slice(0, 200))
4475
+ });
4476
+ } catch (e) {
4477
+ const msg = e instanceof Error ? e.message : String(e);
4478
+ const me2 = running.get(key);
4479
+ if (me2)
4480
+ me2.error = msg;
4481
+ return { errors: [msg] };
4482
+ }
4483
+ const me = running.get(key);
4484
+ if (me && done.error)
4485
+ me.error = done.error;
4486
+ return done.state === "failed" ? { errors: [done.error ?? "The session ended without an answer."] } : { result: { state: done.state } };
4487
+ });
4488
+ running.set(key, { jobId: job.id });
4489
+ const files = await readMatch(ctx.root, view3);
4490
+ return {
4491
+ status: 202,
4492
+ body: {
4493
+ ...files,
4494
+ state: "running",
4495
+ jobId: job.id,
4496
+ proposals: [],
4497
+ none: [],
4498
+ rejected: []
4499
+ }
4500
+ };
4501
+ });
4502
+ }
4503
+
4504
+ // packages/cli/dist/ui-api-decisions.js
4505
+ var BATCH = /^b-\d{8}-\d{6}-[0-9a-f]{6}$/;
4506
+ function fail(e) {
4507
+ if (e instanceof DecideError) {
4508
+ if (e.code === "not-found")
4509
+ throw notFound(e.message);
4510
+ if (e.code === "conflict")
4511
+ throw conflict(e.message);
4512
+ if (e.code === "refused")
4513
+ throw new HttpError(422, "Atticus can't do that", e.message);
4514
+ throw badRequest(e.message);
4515
+ }
4516
+ throw e;
4517
+ }
4518
+ function uiDecisionRoutes(r, ctx, deps = {}) {
4519
+ const now = () => deps.now?.() ?? /* @__PURE__ */ new Date();
4520
+ return r.add("GET", "/api/decisions", async () => ({
4521
+ status: 200,
4522
+ body: await decisionsView(ctx.root)
4523
+ })).add("POST", "/api/decisions/correct", async ({ body }) => {
4524
+ const b = api_exports.CorrectDecisionRequest.safeParse(body ?? {});
4525
+ if (!b.success)
4526
+ throw badRequest("Say the item and the right answer: { item, option } for a question, { item, api, repo } or { item, none: true } for a pairing.", {
4527
+ errors: b.error.issues.map((i) => ({ path: i.path.join("."), message: i.message }))
4528
+ });
4529
+ let out;
4530
+ try {
4531
+ out = await correctDecision(ctx.root, b.data, { by: ctx.user, now: now() });
4532
+ } catch (e) {
4533
+ fail(e);
4534
+ }
4535
+ if (out.written)
4536
+ for (const path of out.files)
4537
+ ctx.events.publish({ type: "file.changed", path });
4538
+ return { status: 200, body: out };
4539
+ }).add("GET", "/api/propose-answers", async () => {
4540
+ const { openDecideRun, PROPOSE_CHANGE } = await import("./chunk-EFCQSMXB.js");
4541
+ const run4 = await openDecideRun(ctx.root, PROPOSE_CHANGE);
4542
+ const body = run4 ? {
4543
+ state: "waiting",
4544
+ run: run4.runId,
4545
+ line: "/atticus-propose",
4546
+ prompt: `${runsRelOf(ctx.root)}/${PROPOSE_CHANGE}/${run4.runId}/prompt.md`,
4547
+ done: `atticus handoff done ${run4.runId}`
4548
+ } : { state: "none", line: "/atticus-propose" };
4549
+ return { status: 200, body };
4550
+ }).add("POST", "/api/propose-answers", async () => {
4551
+ const op = await loadOrgPack(orgPackDirOf(ctx.root));
4552
+ if (op.runtime.ai === "off")
4553
+ throw conflict("runtime.yaml turns AI off here (ai: off): answer the questions yourself.");
4554
+ const { startProposeHandoff } = await import("./chunk-ZPDDEVQX.js");
4555
+ const h = await startProposeHandoff(ctx.root, { now: now() });
4556
+ const body = {
4557
+ state: "waiting",
4558
+ run: h.runId,
4559
+ line: h.line,
4560
+ prompt: h.promptRel,
4561
+ done: h.done
4562
+ };
4563
+ return { status: 201, body };
4564
+ }).add("POST", "/api/decisions/batches/{batchId}/undo", async ({ params }) => {
4565
+ const id = params.batchId ?? "";
4566
+ if (!BATCH.test(id))
4567
+ throw notFound(`There is no batch ${JSON.stringify(id)}.`);
4568
+ let out;
4569
+ try {
4570
+ out = await undoBatch(ctx.root, id, { by: ctx.user, now: now() });
4571
+ } catch (e) {
4572
+ fail(e);
4573
+ }
4574
+ for (const path of out.files)
4575
+ ctx.events.publish({ type: "file.changed", path });
4576
+ return { status: 200, body: out };
4577
+ });
4578
+ }
4579
+
4580
+ // packages/cli/dist/ui-api-sketch.js
4581
+ import { createHash as createHash3 } from "node:crypto";
4582
+ import { readFile as readFile6 } from "node:fs/promises";
4583
+ import { join as join8 } from "node:path";
4584
+ var CHANGE = /^[a-z0-9][a-z0-9._-]{0,199}$/;
4585
+ var drawnOf = (deltas) => drawnStrokes(deltas);
4586
+ function uiSketchRoutes(r, ctx) {
4587
+ const root = ctx.root;
4588
+ const changeOf = (v) => {
4589
+ if (!CHANGE.test(v))
4590
+ throw badRequest(`${v} isn't a change id.`);
4591
+ return v;
4592
+ };
4593
+ const commitOf = (change) => `git add ${changesRelOf(root)}/${change}
4594
+ git commit -m "${safeMessage(`${change}: drawn on the canvas`)}"`;
4595
+ return r.add("GET", "/api/changes/{changeId}/sketch", async ({ params }) => {
4596
+ const change = changeOf(params.changeId);
4597
+ const m = await changeMapOf(root, change);
4598
+ if (!m)
4599
+ throw notFound(`There is no change ${change}.`);
4600
+ const dir = join8(changesDirOf(root), change);
4601
+ const { deltas } = await readDeltas(dir);
4602
+ return {
4603
+ status: 200,
4604
+ body: {
4605
+ written: false,
4606
+ files: [],
4607
+ map: changeMapBody(change, m),
4608
+ drawn: drawnOf(deltas),
4609
+ commit: commitOf(change),
4610
+ fingerprint: await sketchFingerprint(dir)
4611
+ }
4612
+ };
4613
+ }).add("POST", "/api/changes/{changeId}/sketch", async ({ params, body }) => {
4614
+ const change = changeOf(params.changeId);
4615
+ const b = api_exports.SketchStrokeRequest.safeParse(body ?? {});
4616
+ if (!b.success)
4617
+ throw badRequest("Say the stroke: { stroke: { kind, ... }, preview? }.");
4618
+ const s = Stroke.safeParse(b.data.stroke);
4619
+ if (!s.success)
4620
+ throw badRequest("The stroke isn't one Atticus draws.", {
4621
+ errors: s.error.issues.map((i) => ({ path: i.path.join("."), message: i.message }))
4622
+ });
4623
+ const c = await changeStore(root).read(change).catch(() => void 0);
4624
+ if (!c)
4625
+ throw notFound(`There is no change ${change}.`);
4626
+ const dir = changeFolder(root, change);
4627
+ const t = await targetFlowsOf(root, change);
4628
+ if (!t)
4629
+ throw notFound(`There is no change ${change}.`);
4630
+ const estate = await loadEstate(root, t.records);
4631
+ const by = ctx.user ?? "a person";
4632
+ return withFileLock(join8(dir, TARGET_DIR), async () => {
4633
+ const fingerprint = await sketchFingerprint(dir);
4634
+ const { deltas, problems } = await readDeltas(dir);
4635
+ const answer = (extra) => ({
4636
+ written: false,
4637
+ files: [],
4638
+ drawn: drawnOf(deltas),
4639
+ commit: commitOf(change),
4640
+ fingerprint,
4641
+ ...extra
4642
+ });
4643
+ if (problems.length)
4644
+ return {
4645
+ status: 200,
4646
+ body: answer({
4647
+ problem: `A target flow of ${change} doesn't read, so nothing is drawn until it is fixed: ${problems[0]}`
4648
+ })
4649
+ };
4650
+ const strokeId = createHash3("sha256").update(fingerprint).update(JSON.stringify(s.data)).digest("hex").slice(0, 8);
4651
+ const res = applyStroke(deltas, s.data, estate, by, strokeId);
4652
+ if (res.problem)
4653
+ return {
4654
+ status: 200,
4655
+ body: answer({ problem: res.problem, ...res.pick ? { pick: res.pick } : {} })
4656
+ };
4657
+ const plan = await planSketchWrite(dir, res.deltas, res.changed, estate, by);
4658
+ const files = (await Promise.all(plan.map(async (f) => ({
4659
+ file: `${changesRelOf(root)}/${change}/${f.file}`,
4660
+ changes: changedLines(lfOnly(await readFile6(join8(dir, ...f.file.split("/")), "utf8").catch(() => "")), lfOnly(f.now ?? ""))
4661
+ })))).filter((f) => f.changes.length);
4662
+ if (b.data.preview)
4663
+ return { status: 200, body: answer({ files, drawn: drawnOf(res.deltas) }) };
4664
+ if (b.data.expect !== void 0 && b.data.expect !== fingerprint)
4665
+ throw conflict(`${change}'s target flows changed since the preview: look again, then draw the stroke again.`);
4666
+ await writeSketch(dir, plan);
4667
+ for (const f of files)
4668
+ ctx.events.publish({ type: "file.changed", path: f.file });
4669
+ const m = await changeMapOf(root, change);
4670
+ return {
4671
+ status: 200,
4672
+ body: answer({
4673
+ written: true,
4674
+ files,
4675
+ ...m ? { map: changeMapBody(change, m) } : {},
4676
+ drawn: drawnOf(res.deltas),
4677
+ fingerprint: await sketchFingerprint(dir)
4678
+ })
4679
+ };
4680
+ });
4681
+ });
4682
+ }
4683
+
4684
+ // packages/cli/dist/ui-api-owner.js
4685
+ var REPO = /^[A-Za-z0-9._-]{1,200}$/;
4686
+ var TEAM = /^[a-z0-9][a-z0-9-]{0,62}$/;
4687
+ function uiOwnerRoutes(r, ctx) {
4688
+ const options = async (repo) => {
4689
+ if (!REPO.test(repo))
4690
+ throw badRequest(`${JSON.stringify(repo)} isn't a repo name.`);
4691
+ const op = await loadOrgPack(orgPackDirOf(ctx.root));
4692
+ const entry = estateRepos(op).find((x) => x.id === repo);
4693
+ if (!entry)
4694
+ throw notFound(`No repo of the manifest is ${repo}.`);
4695
+ const teams = (op.roles?.teams ?? []).map((t) => ({ id: t.id, name: t.name ?? t.id }));
4696
+ const seen = /* @__PURE__ */ new Set();
4697
+ const systems = op.profiles.flatMap((p) => p.profile?.systems ?? []).filter((s) => !seen.has(s.id) && !!seen.add(s.id)).map((s) => ({ id: s.id, name: s.name || s.id })).sort((a, b) => a.id < b.id ? -1 : 1);
4698
+ return { entry, teams, systems };
4699
+ };
4700
+ return r.add("GET", "/api/repos/{repo}/owner", async ({ params }) => {
4701
+ const repo = params.repo;
4702
+ const o = await options(repo);
4703
+ const file = (await manifestFiles(ctx.root)).find((f) => f.platform === o.entry.platform);
4704
+ return {
4705
+ status: 200,
4706
+ body: {
4707
+ repo,
4708
+ owner: o.entry.owner,
4709
+ ...o.entry.system ? { system: o.entry.system } : {},
4710
+ file: file?.rel ?? "org-pack/profiles",
4711
+ teams: o.teams,
4712
+ systems: o.systems
4713
+ }
4714
+ };
4715
+ }).add("PUT", "/api/repos/{repo}/owner", async ({ params, body }) => {
4716
+ const repo = params.repo;
4717
+ const b = api_exports.SetRepoOwnerRequest.safeParse(body ?? {});
4718
+ if (!b.success)
4719
+ throw badRequest("Say the owner, the system, or a team to add: { owner, system, addTeam }.", {
4720
+ errors: b.error.issues.map((i) => ({ path: i.path.join("."), message: i.message }))
4721
+ });
4722
+ const o = await options(repo);
4723
+ const { addTeam, preview, system } = b.data;
4724
+ let owner = b.data.owner;
4725
+ if (addTeam) {
4726
+ if (!TEAM.test(addTeam.id))
4727
+ throw badRequest(`${addTeam.id} isn't a team id: lower case, digits and dashes, e.g. team-ledger.`);
4728
+ if (o.teams.some((t) => t.id === addTeam.id))
4729
+ throw badRequest(`${addTeam.id} is a team already: pick it as the owner.`);
4730
+ owner = addTeam.id;
4731
+ } else if (owner && !o.teams.some((t) => t.id === owner) && (o.teams.length || !TEAM.test(owner)))
4732
+ throw badRequest(`No team in roles.yaml is ${owner}: pick one, or add it as a new team.`);
4733
+ if (system && !o.systems.some((s) => s.id === system))
4734
+ throw badRequest(`No system of the profiles is ${system}.`);
4735
+ const files = [];
4736
+ const writtenSoFar = [];
4737
+ try {
4738
+ if (addTeam) {
4739
+ const t = await addRolesTeam(ctx.root, addTeam, { preview: true });
4740
+ files.push({ file: t.rel, changes: t.changes });
4741
+ }
4742
+ if (owner) {
4743
+ const m = await setManifestField(ctx.root, repo, "owner", owner, { preview: true });
4744
+ files.push({ file: m.file.rel, changes: m.changes });
4745
+ }
4746
+ if (system) {
4747
+ const m = await setManifestField(ctx.root, repo, "system", system, { preview: true });
4748
+ const f = files.find((x) => x.file === m.file.rel);
4749
+ if (f)
4750
+ f.changes.push(...m.changes);
4751
+ else
4752
+ files.push({ file: m.file.rel, changes: m.changes });
4753
+ }
4754
+ if (!preview) {
4755
+ if (owner)
4756
+ writtenSoFar.push((await setManifestField(ctx.root, repo, "owner", owner)).file.rel);
4757
+ if (system)
4758
+ writtenSoFar.push((await setManifestField(ctx.root, repo, "system", system)).file.rel);
4759
+ if (addTeam)
4760
+ await addRolesTeam(ctx.root, addTeam);
4761
+ for (const f of files)
4762
+ ctx.events.publish({ type: "file.changed", path: f.file });
4763
+ }
4764
+ } catch (e) {
4765
+ if (e instanceof FileChangedError)
4766
+ throw conflict(writtenSoFar.length ? `${e.message} (${[...new Set(writtenSoFar)].join(", ")} was written already; do the rest again.)` : e.message);
4767
+ if (e instanceof ManifestEditError)
4768
+ throw e.code === "not-found" ? notFound(e.message) : new HttpError(422, "Atticus can't write that", e.message);
4769
+ throw e;
4770
+ }
4771
+ return {
4772
+ status: 200,
4773
+ body: {
4774
+ written: !preview,
4775
+ files,
4776
+ verify: "atticus verify --all --write",
4777
+ commit: `git add ${files.map((f) => f.file).join(" ")}
4778
+ git commit -m "${safeMessage(`${repo}: ${[owner ? `owner ${owner}` : "", system ? `system ${system}` : ""].filter(Boolean).join(", ")}`)}"`
4779
+ }
4780
+ };
4781
+ });
4782
+ }
4783
+
4784
+ // packages/cli/dist/ui-api-declared.js
4785
+ import { readFile as readFile7 } from "node:fs/promises";
4786
+ import { join as join9 } from "node:path";
4787
+ var REPO2 = /^[A-Za-z0-9._-]{1,200}$/;
4788
+ var says = (c) => {
4789
+ const what = `${c.target.kind} ${c.target.value} (${c.target.file}:${c.target.line})`;
4790
+ if (c.action === "wrong")
4791
+ return `${what} is wrong: ${c.reason ?? ""}`.trim();
4792
+ if (c.action === "link")
4793
+ return `${what} goes to ${[c.link?.method, c.link?.path].filter(Boolean).join(" ")} on ${c.link?.repo ?? c.link?.system}${c.ai ? ` (detected by AI, run ${c.ai.run})` : ""}`;
4794
+ if (c.action === "unlink")
4795
+ return `${what} reaches none of these`;
4796
+ const set = Object.entries(c.set ?? {}).map(([k, v]) => `${k} ${v}`).join(", ");
4797
+ return `${what}: ${[set, c.line ? `line ${c.line}` : ""].filter(Boolean).join(", ")}`;
4798
+ };
4799
+ function uiDeclaredRoutes(r, ctx, deps = {}) {
4800
+ const now = deps.now ?? (() => /* @__PURE__ */ new Date());
4801
+ const fail2 = (e) => {
4802
+ if (e instanceof FileChangedError)
4803
+ throw conflict(e.message);
4804
+ if (e instanceof DeclareError) {
4805
+ if (e.code === "not-found")
4806
+ throw notFound(e.message);
4807
+ if (e.code === "exists")
4808
+ throw conflict(e.message);
4809
+ throw new HttpError(422, "Atticus can't write that", e.message);
4810
+ }
4811
+ throw e;
4812
+ };
4813
+ return r.add("GET", "/api/boxes/addable", async () => {
4814
+ const op = await loadOrgPack(orgPackDirOf(ctx.root));
4815
+ const picks = targetPicker(op, await readRepos(ctx.root));
4816
+ return {
4817
+ status: 200,
4818
+ body: {
4819
+ repos: picks.filter((p) => p.kind === "repo").map((p) => ({ id: p.id, name: p.name, read: p.read === true })),
4820
+ systems: picks.filter((p) => p.kind === "system").map((p) => ({
4821
+ id: p.id,
4822
+ name: p.name,
4823
+ ...p.boundary ? { boundary: p.boundary } : {}
4824
+ })),
4825
+ teams: picks.filter((p) => p.kind === "team").map((p) => ({ id: p.id, name: p.name }))
4826
+ }
4827
+ };
4828
+ }).add("POST", "/api/boxes", async ({ body }) => {
4829
+ const b = api_exports.AddSystemRequest.safeParse(body ?? {});
4830
+ if (!b.success)
4831
+ throw badRequest("Say the system's id, name and whether it is a vendor or another team's.", {
4832
+ errors: b.error.issues.map((i) => ({ path: i.path.join("."), message: i.message }))
4833
+ });
4834
+ const op = await loadOrgPack(orgPackDirOf(ctx.root));
4835
+ if (op.profiles.some((p) => (p.profile?.systems ?? []).some((s) => s.id === b.data.id)))
4836
+ throw conflict(`A system of the profiles is ${b.data.id} already: it is on the picture.`);
4837
+ const anyRepo = estateRepos(op)[0];
4838
+ if (!anyRepo)
4839
+ throw conflict("No repo is in a manifest yet, so there is no profile to add it to.");
4840
+ const sys = {
4841
+ id: b.data.id,
4842
+ name: b.data.name,
4843
+ boundary: b.data.boundary,
4844
+ ...b.data.owner ? { owner: b.data.owner } : {}
4845
+ };
4846
+ if (b.data.preview) {
4847
+ const res = await addProfileSystem(ctx.root, anyRepo.id, sys, { preview: true }).catch((e) => {
4848
+ throw new HttpError(422, "Atticus can't write that", e instanceof Error ? e.message : String(e));
4849
+ });
4850
+ return {
4851
+ status: 200,
4852
+ body: {
4853
+ written: false,
4854
+ files: [{ file: res.profile, changes: res.changes ?? [] }],
4855
+ verify: "atticus verify --all --write",
4856
+ commit: ""
4857
+ }
4858
+ };
4859
+ }
4860
+ try {
4861
+ const res = await addProfileSystem(ctx.root, anyRepo.id, sys);
4862
+ ctx.events.publish({ type: "file.changed", path: res.profile });
4863
+ return {
4864
+ status: 200,
4865
+ body: {
4866
+ written: true,
4867
+ files: [{ file: res.profile, changes: res.changes ?? [] }],
4868
+ verify: "atticus verify --all --write",
4869
+ commit: `git add ${res.profile}
4870
+ git commit -m "${safeMessage(`profile: ${b.data.id} added from the canvas`)}"`
4871
+ }
4872
+ };
4873
+ } catch (e) {
4874
+ if (e instanceof FileChangedError)
4875
+ throw conflict(e.message);
4876
+ throw new HttpError(422, "Atticus can't write that", e instanceof Error ? e.message : String(e));
4877
+ }
4878
+ }).add("POST", "/api/declared-links", async ({ body }) => {
4879
+ const b = api_exports.DeclareLinkRequest.safeParse(body ?? {});
4880
+ if (!b.success)
4881
+ throw badRequest("Say the system and the topic with its side (publishes or listens), or the route it calls.", { errors: b.error.issues.map((i) => ({ path: i.path.join("."), message: i.message })) });
4882
+ const op = await loadOrgPack(orgPackDirOf(ctx.root));
4883
+ const own = estateRepos(op).some((x) => x.system === b.data.system);
4884
+ if (own && b.data.kind === "topic")
4885
+ throw conflict(`${b.data.system}'s repos are in the manifest: their code says what it publishes and listens on. Read them (atticus map) rather than declaring it.`);
4886
+ try {
4887
+ const { preview, ...d } = b.data;
4888
+ const res = await declareLink(ctx.root, d, {
4889
+ by: ctx.user,
4890
+ at: now().toISOString(),
4891
+ ...preview ? { preview } : {}
4892
+ });
4893
+ if (res.written)
4894
+ ctx.events.publish({ type: "file.changed", path: res.rel });
4895
+ const what = d.kind === "topic" ? `${d.side} ${d.topic}` : `calls ${d.route}`;
4896
+ return {
4897
+ status: 200,
4898
+ body: {
4899
+ written: res.written,
4900
+ files: [{ file: res.rel, changes: res.changes }],
4901
+ verify: "atticus verify --all --write",
4902
+ commit: `git add ${res.rel}
4903
+ git commit -m "${safeMessage(`profile: ${d.system} ${what}, declared on the canvas`)}"`
4904
+ }
4905
+ };
4906
+ } catch (e) {
4907
+ return fail2(e);
4908
+ }
4909
+ }).add("POST", "/api/declared-links/undo", async ({ body }) => {
4910
+ const b = api_exports.UndeclareLinkRequest.safeParse(body ?? {});
4911
+ if (!b.success)
4912
+ throw badRequest("Say which declaration: { kind, system, value }.");
4913
+ try {
4914
+ const res = await undeclareLink(ctx.root, b.data);
4915
+ ctx.events.publish({ type: "file.changed", path: res.rel });
4916
+ return {
4917
+ status: 200,
4918
+ body: {
4919
+ written: true,
4920
+ files: [{ file: res.rel, changes: res.changes }],
4921
+ verify: "atticus verify --all --write",
4922
+ commit: `git add ${res.rel}
4923
+ git commit -m "${safeMessage(`profile: ${b.data.system}: a declaration undone`)}"`
4924
+ }
4925
+ };
4926
+ } catch (e) {
4927
+ return fail2(e);
4928
+ }
4929
+ }).add("GET", "/api/boxes/corrections", async ({ query }) => {
4930
+ const repo = query.get("repo");
4931
+ const system = query.get("system");
4932
+ if (!repo === !system)
4933
+ throw badRequest("Say repo=<repo> or system=<id>.");
4934
+ const items = [];
4935
+ if (repo) {
4936
+ if (!REPO2.test(repo))
4937
+ throw badRequest(`${JSON.stringify(repo)} isn't a repo name.`);
4938
+ const rel = `${factsRelOf(ctx.root)}/${repo}.corrections.jsonl`;
4939
+ const text = await readFile7(join9(factsDirOf(ctx.root), `${repo}.corrections.jsonl`), "utf8").catch(() => "");
4940
+ for (const line of text.split(/\r?\n/).filter((l) => l.trim())) {
4941
+ const c = parseCorrections(line).corrections[0];
4942
+ if (!c)
4943
+ continue;
4944
+ items.push({
4945
+ kind: c.action,
4946
+ says: says(c),
4947
+ by: c.by,
4948
+ at: c.at,
4949
+ ...c.ai ? { ai: { runtime: c.ai.runtime, run: c.ai.run } } : {},
4950
+ ...c.batch ? { batch: c.batch } : {},
4951
+ file: rel,
4952
+ undo: { repo, line }
4953
+ });
4954
+ }
4955
+ } else {
4956
+ const d = await declaredOf(ctx.root, system);
4957
+ for (const x of d.list.filter((y) => y.by))
4958
+ items.push({
4959
+ kind: x.kind,
4960
+ says: x.kind === "topic" ? `${x.side ? `${x.side} ` : ""}${x.value}` : `calls ${x.value}`,
4961
+ ...x.by ? { by: x.by } : {},
4962
+ ...x.at ? { at: x.at } : {},
4963
+ file: d.rel,
4964
+ undo: { kind: x.kind, system: x.system, value: x.value }
4965
+ });
4966
+ }
4967
+ return { status: 200, body: { items } };
4968
+ });
4969
+ }
3143
4970
 
3144
4971
  // packages/cli/dist/ui-api-history.js
3145
4972
  function uiHistoryRoutes(r, ctx) {
@@ -3172,18 +4999,18 @@ function uiHistoryRoutes(r, ctx) {
3172
4999
  }
3173
5000
 
3174
5001
  // packages/cli/dist/ui-api-ai.js
3175
- var import_yaml5 = __toESM(require_dist(), 1);
3176
- import { existsSync } from "node:fs";
3177
- import { readFile as readFile6 } from "node:fs/promises";
3178
- import { join as join8, relative, sep } from "node:path";
5002
+ var import_yaml6 = __toESM(require_dist(), 1);
5003
+ import { existsSync as existsSync3 } from "node:fs";
5004
+ import { readFile as readFile8 } from "node:fs/promises";
5005
+ import { join as join10, relative, sep } from "node:path";
3179
5006
  var unprocessable = (detail) => new HttpError(422, "The settings aren't valid", detail);
3180
5007
  var ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,127}$/;
3181
- var FIELDS = ["ai", "modes", "assistants", "models", "budgets"];
5008
+ var FIELDS = ["ai", "modes", "assistants", "models", "budgets", "decide"];
3182
5009
  function uiAiRoutes(r, ctx, deps = {}) {
3183
5010
  const root = ctx.root;
3184
5011
  const env = deps.env ?? process.env;
3185
5012
  const keys = () => deps.keys ?? defaultKeyStore();
3186
- const file = () => join8(orgPackDirOf(root), "runtime.yaml");
5013
+ const file = () => join10(orgPackDirOf(root), "runtime.yaml");
3187
5014
  const rel = () => relative(root, file()).split(sep).join("/");
3188
5015
  const build = async (op, type, settings) => {
3189
5016
  if (deps.provider)
@@ -3231,13 +5058,16 @@ function uiAiRoutes(r, ctx, deps = {}) {
3231
5058
  keySet: keyEnv ? !!env[keyEnv]?.trim() || !!keys().get(keyEnv) : false,
3232
5059
  models: rt.models,
3233
5060
  budgets: rt.budgets,
5061
+ // Ninth UX walk (ADR 0181): whether the AI decides first, and the bar that pauses it.
5062
+ decide: decideOf(rt),
5063
+ schemaVersion: rt.schemaVersion,
3234
5064
  path: rel()
3235
5065
  }
3236
5066
  };
3237
5067
  }).add("PUT", "/api/settings/ai", async ({ body }) => {
3238
5068
  const b = body ?? {};
3239
- const text = existsSync(file()) ? await readFile6(file(), "utf8") : void 0;
3240
- const raw = text !== void 0 ? (0, import_yaml5.parse)(text) ?? {} : structuredClone(DEFAULT_RUNTIME);
5069
+ const text = existsSync3(file()) ? await readFile8(file(), "utf8") : void 0;
5070
+ const raw = text !== void 0 ? (0, import_yaml6.parse)(text) ?? {} : structuredClone(DEFAULT_RUNTIME);
3241
5071
  for (const f of FIELDS)
3242
5072
  if (b[f] !== void 0)
3243
5073
  raw[f] = b[f];
@@ -3258,18 +5088,39 @@ function uiAiRoutes(r, ctx, deps = {}) {
3258
5088
  }
3259
5089
  raw.adapters = adapters;
3260
5090
  }
5091
+ const names = (m) => {
5092
+ const x = m;
5093
+ return [...x?.allowed ?? [], x?.default].includes("copilot-acp");
5094
+ };
5095
+ const lifted = names(b.modes) && raw.schemaVersion === 2;
5096
+ if (lifted)
5097
+ raw.schemaVersion = 3;
5098
+ const liftedDecide = b.decide !== void 0 && raw.schemaVersion === 3;
5099
+ if (liftedDecide)
5100
+ raw.schemaVersion = 4;
3261
5101
  const checked = Runtime.safeParse(raw);
3262
5102
  if (!checked.success)
3263
5103
  throw unprocessable(checked.error.issues.map((i) => `${i.path.join(".") || "runtime.yaml"}: ${i.message}`).join("; "));
3264
- const header = (text ?? "").split(/\r?\n/).filter((l, i, a) => a.slice(0, i + 1).every((x) => x.startsWith("#")));
3265
- await writeFileAtomic(file(), `${header.length ? `${header.join("\n")}
3266
- ` : ""}${(0, import_yaml5.stringify)(raw, { lineWidth: 0 })}`);
5104
+ const out = (() => {
5105
+ if (text === void 0)
5106
+ return (0, import_yaml6.stringify)(raw, { lineWidth: 0 });
5107
+ const doc = (0, import_yaml6.parseDocument)(text);
5108
+ for (const f of FIELDS)
5109
+ if (b[f] !== void 0)
5110
+ doc.set(f, raw[f]);
5111
+ if (b.model !== void 0)
5112
+ doc.set("adapters", raw.adapters);
5113
+ if (lifted || liftedDecide)
5114
+ doc.set("schemaVersion", raw.schemaVersion);
5115
+ return doc.toString({ lineWidth: 0 });
5116
+ })();
5117
+ await writeFileIfUnchanged(file(), text, inEol(out, eolOf(text ?? "")));
3267
5118
  return {
3268
5119
  status: 200,
3269
5120
  body: {
3270
5121
  ok: true,
3271
5122
  path: rel(),
3272
- note: `Saved ${rel()}. Commit it in a pull request: the org pack is reviewed like code.`
5123
+ note: `Saved ${rel()}. Propose it for review: the org pack is reviewed like code.`
3273
5124
  }
3274
5125
  };
3275
5126
  }).add("POST", "/api/settings/ai/key", async ({ body }) => {
@@ -3316,7 +5167,7 @@ function uiDocumentRoutes(r, ctx) {
3316
5167
  const root = ctx.root;
3317
5168
  const changed = (path) => ctx.events.publish({ type: "file.changed", path });
3318
5169
  return r.add("GET", "/api/knowledge/documents", async () => {
3319
- const { listDocuments } = await import("./chunk-IHRP4OYV.js");
5170
+ const { listDocuments } = await import("./chunk-URIKUID7.js");
3320
5171
  return { status: 200, body: api_exports.DocumentList.parse(await listDocuments(root)) };
3321
5172
  }).add("POST", "/api/knowledge/documents", async ({ body }) => {
3322
5173
  const b = api_exports.AttachFileRequest.safeParse(body ?? {});
@@ -3325,7 +5176,7 @@ function uiDocumentRoutes(r, ctx) {
3325
5176
  const bytes = new Uint8Array(Buffer.from(b.data.content, "base64"));
3326
5177
  if (bytes.length > MAX_FILE)
3327
5178
  throw new HttpError(413, "The file is too large", "Add a file of 15 MB or less.");
3328
- const { addToInbox } = await import("./chunk-3GLDB4IT.js");
5179
+ const { addToInbox } = await import("./chunk-ZKYRDV7A.js");
3329
5180
  const res = await addToInbox(root, [{ name: b.data.name, bytes }]);
3330
5181
  if (!res.added.length)
3331
5182
  throw new HttpError(422, "Atticus can't add that", res.refused[0]?.why ?? "", {
@@ -3335,7 +5186,7 @@ function uiDocumentRoutes(r, ctx) {
3335
5186
  changed(a.path);
3336
5187
  return { status: 201, body: api_exports.AddDocumentResult.parse(res) };
3337
5188
  }, { maxBody: Math.ceil(MAX_FILE * 4 / 3) + 64 * 1024 }).add("POST", "/api/changes/{changeId}/inputs/{name}/promote", async ({ params }) => {
3338
- const { AttachError, promoteChangeInput } = await import("./chunk-GXDATVSY.js");
5189
+ const { AttachError, promoteChangeInput } = await import("./chunk-XQ3YZ4QW.js");
3339
5190
  try {
3340
5191
  const res = await promoteChangeInput(root, params.changeId, params.name);
3341
5192
  changed(res.path);
@@ -3352,13 +5203,13 @@ function uiDocumentRoutes(r, ctx) {
3352
5203
  function uiRepoRoutes(r, ctx) {
3353
5204
  const root = ctx.root;
3354
5205
  return r.add("GET", "/api/repos/branches", async () => {
3355
- const { repoBranches } = await import("./chunk-U6TD7UOH.js");
5206
+ const { repoBranches } = await import("./chunk-YBOVNMHC.js");
3356
5207
  return { status: 200, body: api_exports.RepoBranchesList.parse(await repoBranches(root)) };
3357
5208
  }).add("GET", "/api/repos/compare", async ({ query }) => {
3358
5209
  const branch = query.get("branch")?.trim() ?? "";
3359
5210
  if (!branch)
3360
5211
  throw badRequest("Say which branch: ?branch=<name>.");
3361
- const { compareBranch } = await import("./chunk-U6TD7UOH.js");
5212
+ const { compareBranch } = await import("./chunk-YBOVNMHC.js");
3362
5213
  const c = await compareBranch(root, branch);
3363
5214
  if (!c)
3364
5215
  throw notFound(`${branch} hasn't been scanned beside the baseline yet.`);
@@ -3367,7 +5218,7 @@ function uiRepoRoutes(r, ctx) {
3367
5218
  const b = api_exports.EditRepoBranchesRequest.safeParse(body ?? {});
3368
5219
  if (!b.success)
3369
5220
  throw badRequest("Send defaultBranch and branches as branch names.");
3370
- const { editRepoBranches, RepoEditError } = await import("./chunk-U6TD7UOH.js");
5221
+ const { editRepoBranches, RepoEditError } = await import("./chunk-YBOVNMHC.js");
3371
5222
  try {
3372
5223
  const file = await editRepoBranches(root, params.repoId, b.data);
3373
5224
  ctx.events.publish({ type: "file.changed", path: file });
@@ -3380,14 +5231,56 @@ function uiRepoRoutes(r, ctx) {
3380
5231
  };
3381
5232
  } catch (e) {
3382
5233
  if (e instanceof RepoEditError)
3383
- throw e.code === "not-found" ? notFound(e.message) : badRequest(e.message);
5234
+ throw e.code === "not-found" ? notFound(e.message) : e.code === "conflict" ? conflict(e.message) : badRequest(e.message);
5235
+ throw e;
5236
+ }
5237
+ }).add("PUT", "/api/repos/{repoId}/build", async ({ params, body }) => {
5238
+ const b = api_exports.EditRepoBuildRequest.safeParse(body ?? {});
5239
+ if (!b.success)
5240
+ throw badRequest('Send the build command as build ("" clears it), up to 500 characters.');
5241
+ const { editRepoBuild, RepoEditError } = await import("./chunk-YBOVNMHC.js");
5242
+ try {
5243
+ const r2 = await editRepoBuild(root, params.repoId, b.data);
5244
+ ctx.events.publish({ type: "file.changed", path: r2.file });
5245
+ return {
5246
+ status: 200,
5247
+ body: {
5248
+ file: r2.file,
5249
+ ...r2.build ? { build: r2.build } : {},
5250
+ note: r2.build ? `${r2.file} changed: propose it for review. A task in ${params.repoId} is now built and tested with it before its PR is raised; run the task again.` : `${r2.file} changed: propose it for review. ${params.repoId} has no build command now, so its tasks aren't built or tested.`
5251
+ }
5252
+ };
5253
+ } catch (e) {
5254
+ if (e instanceof RepoEditError)
5255
+ throw e.code === "not-found" ? notFound(e.message) : e.code === "conflict" ? conflict(e.message) : badRequest(e.message);
5256
+ throw e;
5257
+ }
5258
+ }).add("PUT", "/api/orgpack/owner", async ({ body }) => {
5259
+ const b = api_exports.SetOrgPackOwnerRequest.safeParse(body ?? {});
5260
+ if (!b.success)
5261
+ throw badRequest("Send the owner as one handle: { owner }.");
5262
+ const { setOrgPackOwner, OwnerEditError } = await import("./chunk-PQH56YF6.js");
5263
+ try {
5264
+ const r2 = await setOrgPackOwner(root, b.data.owner, b.data.was);
5265
+ ctx.events.publish({ type: "file.changed", path: r2.file });
5266
+ return {
5267
+ status: 200,
5268
+ body: {
5269
+ file: r2.file,
5270
+ owner: r2.owner,
5271
+ note: `${r2.owner} now looks after the org pack. ${r2.file} changed: propose it for review; its owner approves changes to the org pack.`
5272
+ }
5273
+ };
5274
+ } catch (e) {
5275
+ if (e instanceof OwnerEditError)
5276
+ throw e.code === "not-found" ? notFound(e.message) : e.code === "conflict" ? conflict(e.message) : badRequest(e.message);
3384
5277
  throw e;
3385
5278
  }
3386
5279
  }).add("PUT", "/api/repos/{repoId}/description", async ({ params, body }) => {
3387
5280
  const b = api_exports.EditRepoDescriptionRequest.safeParse(body ?? {});
3388
5281
  if (!b.success)
3389
5282
  throw badRequest("Send what the repo is as text, or accept: true to confirm the draft, or reject: true to drop it.");
3390
- const { editRepoDescription, RepoEditError } = await import("./chunk-U6TD7UOH.js");
5283
+ const { editRepoDescription, RepoEditError } = await import("./chunk-YBOVNMHC.js");
3391
5284
  try {
3392
5285
  const r2 = await editRepoDescription(root, params.repoId, b.data);
3393
5286
  ctx.events.publish({ type: "file.changed", path: r2.file });
@@ -3401,7 +5294,7 @@ function uiRepoRoutes(r, ctx) {
3401
5294
  };
3402
5295
  } catch (e) {
3403
5296
  if (e instanceof RepoEditError)
3404
- throw e.code === "not-found" ? notFound(e.message) : badRequest(e.message);
5297
+ throw e.code === "not-found" ? notFound(e.message) : e.code === "conflict" ? conflict(e.message) : badRequest(e.message);
3405
5298
  throw e;
3406
5299
  }
3407
5300
  });
@@ -3410,11 +5303,11 @@ function uiRepoRoutes(r, ctx) {
3410
5303
  // packages/cli/dist/ui-api-cockpit.js
3411
5304
  function uiCockpitRoutes(r, ctx) {
3412
5305
  return r.add("GET", "/api/changes/{changeId}/cockpit", async ({ params }) => {
3413
- const { cockpit } = await import("./chunk-RONDYDMQ.js");
5306
+ const { cockpit } = await import("./chunk-6EXDI5GI.js");
3414
5307
  try {
3415
5308
  return {
3416
5309
  status: 200,
3417
- body: api_exports.ChangeCockpit.parse(await cockpit(ctx.root, params.changeId))
5310
+ body: api_exports.ChangeCockpit.parse(await cockpit(ctx.root, params.changeId, { user: ctx.user }))
3418
5311
  };
3419
5312
  } catch (e) {
3420
5313
  if (e instanceof Error && e.name === "ChangeFileError")
@@ -3422,7 +5315,7 @@ function uiCockpitRoutes(r, ctx) {
3422
5315
  throw e;
3423
5316
  }
3424
5317
  }).add("GET", "/api/past-changes", async ({ query }) => {
3425
- const { changeAboutText, similarChanges } = await import("./chunk-63565QTF.js");
5318
+ const { changeAboutText, similarChanges } = await import("./chunk-BC6KKZ67.js");
3426
5319
  const change = query.get("change");
3427
5320
  const text = change ? await changeAboutText(ctx.root, change) : query.get("text")?.trim();
3428
5321
  if (change && !text)
@@ -3440,11 +5333,11 @@ function uiCockpitRoutes(r, ctx) {
3440
5333
  const b = api_exports.AttachPastChangeRequest.safeParse(body ?? {});
3441
5334
  if (!b.success)
3442
5335
  throw badRequest("Say which archived change: { like: <its folder or id> }.");
3443
- const { attachPastChange, findPastChange } = await import("./chunk-63565QTF.js");
5336
+ const { attachPastChange, findPastChange } = await import("./chunk-BC6KKZ67.js");
3444
5337
  const folder = await findPastChange(ctx.root, b.data.like);
3445
5338
  if (!folder)
3446
5339
  throw notFound(`There is no archived change ${b.data.like}.`);
3447
- const { AttachError } = await import("./chunk-GXDATVSY.js");
5340
+ const { AttachError } = await import("./chunk-XQ3YZ4QW.js");
3448
5341
  try {
3449
5342
  const name = await attachPastChange(ctx.root, params.changeId, folder, ctx.user);
3450
5343
  ctx.events.publish({ type: "file.changed", path: `inputs/${name}` });
@@ -3461,9 +5354,9 @@ function uiCockpitRoutes(r, ctx) {
3461
5354
  }
3462
5355
 
3463
5356
  // packages/cli/dist/ui-api-audit.js
3464
- var REPO = /^[a-z0-9][a-z0-9._-]{0,99}$/i;
5357
+ var REPO3 = /^[a-z0-9][a-z0-9._-]{0,99}$/i;
3465
5358
  var repoOf = (v) => {
3466
- if (!REPO.test(v))
5359
+ if (!REPO3.test(v))
3467
5360
  throw badRequest(`${JSON.stringify(v)} isn't a repo name.`);
3468
5361
  return v;
3469
5362
  };
@@ -3557,15 +5450,16 @@ function uiAuditRoutes(r, ctx, deps = {}) {
3557
5450
  open: p.audit.items.filter((i) => !i.answer).length,
3558
5451
  items: await itemsWithLines(ctx.root, repo, ofDraw(p.audit, p.drawn)),
3559
5452
  precision: { facts: precision.facts, links: precision.links },
3560
- commit: `git add ${p.fileRel} && git commit -m "${repo}: a person's sample audit answers"`
5453
+ commit: `git add ${p.fileRel}
5454
+ git commit -m "${repo}: a person's sample audit answers"`
3561
5455
  };
3562
5456
  return { status: 200, body: result };
3563
5457
  });
3564
5458
  }
3565
5459
 
3566
5460
  // packages/cli/dist/ui-api-outside.js
3567
- import { readFile as readFile7 } from "node:fs/promises";
3568
- import { join as join9 } from "node:path";
5461
+ import { readFile as readFile9 } from "node:fs/promises";
5462
+ import { join as join11 } from "node:path";
3569
5463
  var SPECS = /* @__PURE__ */ new Set(["openapi", "swagger"]);
3570
5464
  var cmp = (a, b) => a < b ? -1 : a > b ? 1 : 0;
3571
5465
  var OUTSIDE_ADD_SPEC = (id) => [
@@ -3577,12 +5471,13 @@ async function outsideView(root) {
3577
5471
  const quiet = { out: () => void 0, err: () => void 0 };
3578
5472
  const { op, records, inputs } = await intakeContext(root, quiet);
3579
5473
  const systems = outsideProfileSystems(op);
5474
+ const stamp2 = await stampOf(root);
3580
5475
  const estate = await estateFacts(root, op, "");
3581
5476
  const facts = /* @__PURE__ */ new Map();
3582
5477
  for (const r of estate.repos)
3583
5478
  for (const f of r.facts)
3584
5479
  facts.set(`${r.repo}\0${f.id}`, f);
3585
- const joined = await estateJoinOnFile(root, op, estate);
5480
+ const joined = await estateJoinOnFile(root, op, estate, stamp2);
3586
5481
  const repos = joinReposOf(op, estate.repos, componentRenames(estate.repos, op));
3587
5482
  const { maps: sharedMaps } = await readSharedMaps(root);
3588
5483
  const sharedJoin = sharedMaps.length ? joinSharedMaps({ outside: joined.outside ?? [], repos, maps: sharedMaps, systems }) : { matches: [], gaps: [], asks: [] };
@@ -3593,7 +5488,7 @@ async function outsideView(root) {
3593
5488
  const system = systems.find((s) => s.id === d.systems[0]);
3594
5489
  if (!system || specs.has(system.id))
3595
5490
  continue;
3596
- const body = await readFile7(join9(inputsDirOf(root), ...d.path.split("/").slice(1)), "utf8");
5491
+ const body = await readFile9(join11(inputsDirOf(root), ...d.path.split("/").slice(1)), "utf8");
3597
5492
  const spec = readVendorSpec(d.path, body, system.id);
3598
5493
  if (spec)
3599
5494
  specs.set(system.id, spec);
@@ -3625,7 +5520,7 @@ async function outsideView(root) {
3625
5520
  ...joinConventions(op)
3626
5521
  }) : { matches: [], gaps: [], asks: [] };
3627
5522
  const contracts = await contractCheckOf(root).catch(() => void 0);
3628
- const catalog = records.length ? records : (await readCatalog(join9(catalogDirOf(root), "generated"), { cacheRoot: root })).records;
5523
+ const catalog = records.length ? records : (await readCatalog(join11(catalogDirOf(root), "generated"), { cacheRoot: root })).records;
3629
5524
  const ifaces = catalog.filter((r) => r.kind === "Interface");
3630
5525
  const componentsOf = (id) => /* @__PURE__ */ new Set([
3631
5526
  id,
@@ -3783,9 +5678,196 @@ function uiContractsRoutes(r, ctx, o = {}) {
3783
5678
  }
3784
5679
 
3785
5680
  // packages/cli/dist/ui-api-stages.js
3786
- import { stat as stat4 } from "node:fs/promises";
3787
- import { join as join10 } from "node:path";
3788
- var ID2 = /^[a-z0-9][a-z0-9._-]{0,99}$/;
5681
+ var import_yaml7 = __toESM(require_dist(), 1);
5682
+ import { createHash as createHash4 } from "node:crypto";
5683
+ import { readFile as readFile11, stat as stat6 } from "node:fs/promises";
5684
+ import { join as join13 } from "node:path";
5685
+
5686
+ // packages/cli/dist/ui-api-drafts.js
5687
+ import { readdir as readdir4, readFile as readFile10, stat as stat5 } from "node:fs/promises";
5688
+ import { join as join12 } from "node:path";
5689
+ var ID3 = /^[a-z0-9][a-z0-9._-]{0,99}$/;
5690
+ var CORRECTION_ID = /^c-[0-9a-f]{10}$/;
5691
+ var exists2 = (p) => stat5(p).then(() => true, () => false);
5692
+ var PAGE_STAGE = {
5693
+ feature: "feature",
5694
+ architecture: "architecture",
5695
+ stories: "stories",
5696
+ tasks: "stories"
5697
+ };
5698
+ var ON_PAGE = {
5699
+ feature: (f) => f === "feature.md" || f === "proposal.md",
5700
+ architecture: (f) => /^target-flows\/[^/]+\.ya?ml$/.test(f) || f === "design.md" || f === "data.md",
5701
+ stories: (f) => f === "stories.md" || /^stories\/[^/]+\.md$/.test(f) || /^specs\/.+\/spec\.md$/.test(f),
5702
+ tasks: (f) => f === "tasks.md"
5703
+ };
5704
+ var pageOfFile = (f) => Object.keys(ON_PAGE).find((p) => ON_PAGE[p](f));
5705
+ async function walk(dir, sub = "") {
5706
+ const out = [];
5707
+ for (const e of await readdir4(join12(dir, sub), { withFileTypes: true }).catch(() => [])) {
5708
+ if (e.name.startsWith("."))
5709
+ continue;
5710
+ const rel = sub ? `${sub}/${e.name}` : e.name;
5711
+ if (e.isDirectory()) {
5712
+ if (["stage", "review", "tasks", "inputs"].includes(rel))
5713
+ continue;
5714
+ out.push(...await walk(dir, rel));
5715
+ } else
5716
+ out.push(rel);
5717
+ }
5718
+ return out;
5719
+ }
5720
+ var slashOf = (stage, change) => `/atticus-${stage === "small" ? "small" : stage} ${change}`;
5721
+ var nextOf = (stage, change) => ({
5722
+ stage,
5723
+ command: rerunOf(stage, change),
5724
+ slash: slashOf(stage, change)
5725
+ });
5726
+ var view = (c) => ({ ...c });
5727
+ async function writtenByStage(dir, change) {
5728
+ const out = [];
5729
+ for (const s of STAGES) {
5730
+ const l = await readStageLoop(dir, change, s).catch(() => void 0);
5731
+ if (l?.present && l.loop.written)
5732
+ out.push({ stage: s, hashes: l.loop.written });
5733
+ }
5734
+ return out;
5735
+ }
5736
+ var provenanceView = (p) => ({ ...p });
5737
+ async function stageDraftsView(root, change, page) {
5738
+ const dir = changeFolder(root, change);
5739
+ const rel = `${changesRelOf(root)}/${change}`;
5740
+ const problems = [];
5741
+ const candidates = (await walk(dir)).filter(ON_PAGE[page]).sort();
5742
+ const prov = await draftProvenance(dir, change, candidates, await writtenByStage(dir, change)).catch((e) => {
5743
+ problems.push({ file: `${rel}/stage/drafted.yaml`, message: String(e.message) });
5744
+ return {};
5745
+ });
5746
+ const files = [];
5747
+ for (const f of candidates) {
5748
+ const p = prov[f];
5749
+ if (!p)
5750
+ continue;
5751
+ const text = await readFile10(join12(dir, ...f.split("/")), "utf8").catch(() => "") ?? "";
5752
+ files.push({
5753
+ file: f,
5754
+ path: `${rel}/${f}`,
5755
+ stage: p.by === "ai" ? p.stage : PAGE_STAGE[page],
5756
+ drafted: provenanceView(p),
5757
+ items: draftItems(f, text)
5758
+ });
5759
+ }
5760
+ let corrections = [];
5761
+ try {
5762
+ corrections = await readCorrections(dir, change);
5763
+ } catch (e) {
5764
+ problems.push({ file: `${rel}/${CORRECTIONS_FILE}`, message: String(e.message) });
5765
+ }
5766
+ const mine = corrections.filter((c) => ON_PAGE[page](c.file)).sort((a, b) => a.at < b.at ? -1 : a.at > b.at ? 1 : 0);
5767
+ const waiting = mine.find((c) => c.state === "waiting");
5768
+ return {
5769
+ change,
5770
+ page,
5771
+ files,
5772
+ corrections: mine.map(view),
5773
+ next: nextOf(waiting?.stage ?? PAGE_STAGE[page], change),
5774
+ problems
5775
+ };
5776
+ }
5777
+ function pageOf(v) {
5778
+ const r = api_exports.DraftPage.safeParse(v);
5779
+ if (!r.success)
5780
+ throw badRequest(`${JSON.stringify(v)} isn't a page of drafts: feature, architecture, stories or tasks.`);
5781
+ return r.data;
5782
+ }
5783
+ var refusal = (e) => {
5784
+ if (e instanceof PlannerError && e.code === "refused")
5785
+ throw conflict(e.message);
5786
+ if (e instanceof PlannerError)
5787
+ throw new HttpError(422, "Atticus refused", e.message);
5788
+ throw e;
5789
+ };
5790
+ function uiDraftRoutes(r, ctx, deps = {}) {
5791
+ const now = deps.now ?? (() => /* @__PURE__ */ new Date());
5792
+ const changeOf = async (v) => {
5793
+ if (!ID3.test(v))
5794
+ throw badRequest(`${JSON.stringify(v)} isn't a change id.`);
5795
+ if (!await exists2(join12(changeFolder(ctx.root, v), "change.yaml")))
5796
+ throw notFound(`There is no change ${v}.`);
5797
+ return v;
5798
+ };
5799
+ const list = async (change) => ({
5800
+ change,
5801
+ corrections: (await readCorrections(changeFolder(ctx.root, change), change).catch(refusal)).map(view)
5802
+ });
5803
+ return r.add("GET", "/api/changes/{changeId}/drafts/{page}", async ({ params }) => {
5804
+ const page = pageOf(params.page);
5805
+ const change = await changeOf(params.changeId);
5806
+ return { status: 200, body: await stageDraftsView(ctx.root, change, page) };
5807
+ }).add("GET", "/api/changes/{changeId}/corrections", async ({ params }) => {
5808
+ const change = await changeOf(params.changeId);
5809
+ return { status: 200, body: await list(change) };
5810
+ }).add("POST", "/api/changes/{changeId}/corrections", async ({ params, body }) => {
5811
+ const change = await changeOf(params.changeId);
5812
+ const b = api_exports.AddCorrectionRequest.safeParse(body ?? {});
5813
+ if (!b.success)
5814
+ throw badRequest("Say which draft (file, and item) and write the correction: { file, item, text }.", {
5815
+ errors: b.error.issues.map((i) => ({ path: i.path.join("."), message: i.message }))
5816
+ });
5817
+ const page = pageOfFile(b.data.file);
5818
+ if (!page)
5819
+ throw notFound(`${b.data.file} isn't a stage draft of ${change}.`);
5820
+ const v = await stageDraftsView(ctx.root, change, page);
5821
+ const f = v.files.find((x) => x.file === b.data.file);
5822
+ if (!f)
5823
+ throw notFound(`${b.data.file} isn't a stage draft of ${change}.`);
5824
+ const item = b.data.item ? f.items.find((i) => i.id === b.data.item) : void 0;
5825
+ if (b.data.item && !item)
5826
+ throw notFound(`${b.data.file} has no item ${b.data.item} now: reload the page, then correct it again.`);
5827
+ const stage = f.stage;
5828
+ const dir = changeFolder(ctx.root, change);
5829
+ const { present } = await readStageLoop(dir, change, stage);
5830
+ if (!present)
5831
+ throw conflict(`The ${stage} stage of ${change} hasn't run here yet, so nothing would read a correction: run ${rerunOf(stage, change)} first.`);
5832
+ const c = await addCorrection(dir, change, {
5833
+ stage,
5834
+ file: f.file,
5835
+ ...item ? { item: item.id } : {},
5836
+ label: item?.label ?? f.file,
5837
+ text: b.data.text,
5838
+ similar: b.data.similar,
5839
+ ...b.data.like ? { like: b.data.like } : item?.like?.[0] ? { like: item.like[0] } : {},
5840
+ by: ctx.user,
5841
+ at: now().toISOString()
5842
+ }).catch(refusal);
5843
+ const written = `${changesRelOf(ctx.root)}/${change}/${CORRECTIONS_FILE}`;
5844
+ ctx.events.publish({ type: "file.changed", path: written });
5845
+ const result = {
5846
+ correction: view(c),
5847
+ files: [written],
5848
+ commit: `git add ${written}
5849
+ git commit -m "${change}: a correction of ${f.file}"`,
5850
+ next: nextOf(stage, change)
5851
+ };
5852
+ return { status: 200, body: result };
5853
+ }).add("POST", "/api/changes/{changeId}/corrections/{correctionId}/remove", async ({ params }) => {
5854
+ const change = await changeOf(params.changeId);
5855
+ const id = params.correctionId;
5856
+ if (!CORRECTION_ID.test(id))
5857
+ throw badRequest(`${JSON.stringify(id)} isn't a correction id.`);
5858
+ const gone = await removeCorrection(changeFolder(ctx.root, change), change, id).catch(refusal);
5859
+ if (!gone)
5860
+ throw notFound(`Change ${change} has no correction ${id}.`);
5861
+ ctx.events.publish({
5862
+ type: "file.changed",
5863
+ path: `${changesRelOf(ctx.root)}/${change}/${CORRECTIONS_FILE}`
5864
+ });
5865
+ return { status: 200, body: await list(change) };
5866
+ });
5867
+ }
5868
+
5869
+ // packages/cli/dist/ui-api-stages.js
5870
+ var ID4 = /^[a-z0-9][a-z0-9._-]{0,99}$/;
3789
5871
  function findingOptions(f) {
3790
5872
  const open = f.state === "open";
3791
5873
  const issue = f.kind === "issue";
@@ -3797,8 +5879,8 @@ function findingOptions(f) {
3797
5879
  ...open && f.question ? { ask: { text: f.question.text, owner: f.question.owner } } : {}
3798
5880
  };
3799
5881
  }
3800
- var exists2 = (p) => stat4(p).then(() => true, () => false);
3801
- var slashOf = (stage, change) => `/atticus-${stage} ${change}`;
5882
+ var exists3 = (p) => stat6(p).then(() => true, () => false);
5883
+ var slashOf2 = (stage, change) => `/atticus-${stage} ${change}`;
3802
5884
  async function gateOf(root, change, stage) {
3803
5885
  const problems = [];
3804
5886
  let gate;
@@ -3824,7 +5906,7 @@ async function latest(root, change, gate) {
3824
5906
  const r = await readRound(root, change, gate, round);
3825
5907
  return { round, findings: r.findings, bad: r.bad };
3826
5908
  }
3827
- var view = (c) => ({
5909
+ var view2 = (c) => ({
3828
5910
  question: c.question,
3829
5911
  text: c.text,
3830
5912
  by: c.by,
@@ -3848,7 +5930,7 @@ async function stageReviewView(root, change, stage) {
3848
5930
  finding: s.finding,
3849
5931
  file: fileRel,
3850
5932
  options: findingOptions(s.finding),
3851
- comments: comments.comments.filter((c) => c.about === fileRel).map(view),
5933
+ comments: comments.comments.filter((c) => c.about === fileRel).map(view2),
3852
5934
  ...p ? {
3853
5935
  proposal: {
3854
5936
  claim: p.claim,
@@ -3881,10 +5963,37 @@ async function stageReviewView(root, change, stage) {
3881
5963
  },
3882
5964
  findings,
3883
5965
  questions,
3884
- next: { command: rerunOf(stage, change), slash: slashOf(stage, change) },
5966
+ next: { command: rerunOf(stage, change), slash: slashOf2(stage, change) },
3885
5967
  problems
3886
5968
  };
3887
5969
  }
5970
+ var PLACED_HEAD = "# The feature placed against the estate (ADR 0087).";
5971
+ var EDITED = /^# The feature placed against the estate \(ADR 0087\)\. Edited by (.+) on (\S+); /;
5972
+ var sha3 = (s) => createHash4("sha256").update(s).digest("hex");
5973
+ function draftedOf(text) {
5974
+ const head = text.split("\n", 1)[0] ?? "";
5975
+ if (head.startsWith(PLACED_HEAD) && / Drafted by /.test(head))
5976
+ return { by: "ai" };
5977
+ const m = EDITED.exec(head);
5978
+ return m ? { by: "person", who: m[1], at: m[2] } : { by: "person" };
5979
+ }
5980
+ async function placementView(root, change) {
5981
+ const rel = `${changesRelOf(root)}/${change}`;
5982
+ const dir = changeFolder(root, change);
5983
+ const read = (f) => readFile11(join13(dir, f), "utf8").catch(() => void 0);
5984
+ const source = await read(FEATURE_FILE);
5985
+ const content = await read("feature.md") ?? "";
5986
+ return {
5987
+ change,
5988
+ present: source !== void 0,
5989
+ file: `${rel}/feature.md`,
5990
+ content,
5991
+ source: `${rel}/${FEATURE_FILE}`,
5992
+ sourceContent: source ?? "",
5993
+ sha256: sha3(source ?? ""),
5994
+ ...source !== void 0 ? { drafted: draftedOf(source) } : {}
5995
+ };
5996
+ }
3888
5997
  function tabOf(v) {
3889
5998
  const r = api_exports.StageTab.safeParse(v);
3890
5999
  if (!r.success)
@@ -3894,13 +6003,57 @@ function tabOf(v) {
3894
6003
  function uiStageRoutes(r, ctx, deps = {}) {
3895
6004
  const now = deps.now ?? (() => /* @__PURE__ */ new Date());
3896
6005
  const changeOf = async (v) => {
3897
- if (!ID2.test(v))
6006
+ if (!ID4.test(v))
3898
6007
  throw badRequest(`${JSON.stringify(v)} isn't a change id.`);
3899
- if (!await exists2(join10(changeFolder(ctx.root, v), "change.yaml")))
6008
+ if (!await exists3(join13(changeFolder(ctx.root, v), "change.yaml")))
3900
6009
  throw notFound(`There is no change ${v}.`);
3901
6010
  return v;
3902
6011
  };
3903
- return r.add("GET", "/api/changes/{changeId}/stages/{stage}", async ({ params }) => {
6012
+ return uiDraftRoutes(r, ctx, deps).add("GET", "/api/changes/{changeId}/placement", async ({ params }) => {
6013
+ const change = await changeOf(params.changeId);
6014
+ return { status: 200, body: await placementView(ctx.root, change) };
6015
+ }).add("PUT", "/api/changes/{changeId}/placement", async ({ params, body }) => {
6016
+ const change = await changeOf(params.changeId);
6017
+ const b = api_exports.SavePlacementRequest.safeParse(body ?? {});
6018
+ if (!b.success)
6019
+ throw badRequest("Send feature.yaml's text and the sha256 it started from.", {
6020
+ errors: b.error.issues.map((i) => ({ path: i.path.join("."), message: i.message }))
6021
+ });
6022
+ const dir = changeFolder(ctx.root, change);
6023
+ const file = join13(dir, FEATURE_FILE);
6024
+ const was = await readFile11(file, "utf8").catch(() => void 0);
6025
+ if (sha3(was ?? "") !== b.data.baseSha256)
6026
+ throw conflict(`${FEATURE_FILE} changed since you opened it. Reload it, then edit again.`);
6027
+ const lines = b.data.content.replace(/\r\n/g, "\n").split("\n");
6028
+ const own = lines[0]?.startsWith(PLACED_HEAD) ? lines.slice(1) : lines;
6029
+ let parsed;
6030
+ try {
6031
+ parsed = (0, import_yaml7.parse)(own.join("\n"));
6032
+ } catch (e) {
6033
+ throw new HttpError(422, "Not a placement", `${FEATURE_FILE} doesn't read as YAML: ${String(e.message ?? e).split("\n")[0]}`);
6034
+ }
6035
+ const p = FeaturePlacement.safeParse(parsed);
6036
+ if (!p.success)
6037
+ throw new HttpError(422, "Not a placement", `${FEATURE_FILE} doesn't read as a placement: ${p.error.issues.map((i) => `${i.path.join(".") || "(top)"}: ${i.message}`).join("; ")}`);
6038
+ if (p.data.change !== change)
6039
+ throw new HttpError(422, "Not a placement", `${FEATURE_FILE} names change ${p.data.change}, not ${change}.`);
6040
+ const at = now().toISOString();
6041
+ const head = `${PLACED_HEAD} Edited by ${ctx.user} on ${at}; review it in the feature PR.`;
6042
+ const saved = `${head}
6043
+ ${own.join("\n")}`;
6044
+ await writeFileIfUnchanged(file, was, saved);
6045
+ const md = renderFeature(p.data);
6046
+ await writeFileAtomic(join13(dir, "feature.md"), md);
6047
+ for (const [f, content] of [
6048
+ [FEATURE_FILE, saved],
6049
+ ["feature.md", md]
6050
+ ])
6051
+ await recordPersonSave(dir, change, { file: f, who: ctx.user, at, content }).catch(() => void 0);
6052
+ const rel = `${changesRelOf(ctx.root)}/${change}`;
6053
+ for (const f of [FEATURE_FILE, "feature.md"])
6054
+ ctx.events.publish({ type: "file.changed", path: `${rel}/${f}` });
6055
+ return { status: 200, body: await placementView(ctx.root, change) };
6056
+ }).add("GET", "/api/changes/{changeId}/stages/{stage}", async ({ params }) => {
3904
6057
  const stage = tabOf(params.stage);
3905
6058
  const change = await changeOf(params.changeId);
3906
6059
  return { status: 200, body: await stageReviewView(ctx.root, change, stage) };
@@ -3948,12 +6101,13 @@ function uiStageRoutes(r, ctx, deps = {}) {
3948
6101
  comment: {
3949
6102
  question: q.id,
3950
6103
  text: q.answer.text,
3951
- by: q.answer.by,
6104
+ by: answerByWords(q.answer.by),
3952
6105
  at: q.answer.at,
3953
6106
  read: false
3954
6107
  },
3955
6108
  files: written.files,
3956
- commit: `git add ${written.files.join(" ")} && git commit -m "${change}: a comment on ${f.finding.id}"`,
6109
+ commit: `git add ${written.files.join(" ")}
6110
+ git commit -m "${change}: a comment on ${f.finding.id}"`,
3957
6111
  next: v.next
3958
6112
  };
3959
6113
  return { status: 200, body: result };
@@ -3971,6 +6125,16 @@ function catalogMayHaveChanged(root, path) {
3971
6125
  };
3972
6126
  return near(catalogDirOf(root)) || near(orgPackDirOf(root));
3973
6127
  }
6128
+ function extendUiRoutes(r, ctx, deps = {}, o = {}) {
6129
+ const known = uiKnowRoutes(uiRunRoutes(uiRoutes(r, ctx, deps), ctx, deps), ctx, {
6130
+ warm: o.warm === true
6131
+ });
6132
+ const base = uiInitiativeRoutes(known, ctx);
6133
+ const views = uiGraphRoutes(uiAcrossRoutes(uiMeRoutes(base, ctx), ctx), ctx);
6134
+ const specs = uiSpecRoutes(uiOnboardingRoutes(uiQuestionRoutes(views, ctx), ctx), ctx);
6135
+ const all = uiComponentTypeRoutes(uiLinkMatchRoutes(uiSketchRoutes(uiDeclaredRoutes(uiOwnerRoutes(uiLinkRoutes(uiCopilotRunRoutes(uiStageRoutes(uiContractsRoutes(uiOutsideRoutes(uiAuditRoutes(uiFactsRoutes(uiTrustRoutes(uiCockpitRoutes(uiRepoRoutes(uiDocumentRoutes(uiAiRoutes(uiHistoryRoutes(uiModelRunRoutes(uiKnowledgeRoutes(uiCiteRoutes(specs, ctx), ctx), ctx), ctx), ctx), ctx), ctx), ctx), ctx), ctx), ctx), ctx), ctx), ctx), ctx), ctx), ctx), ctx), ctx), ctx), ctx);
6136
+ return uiDecisionRoutes(all, ctx);
6137
+ }
3974
6138
  async function startUi(root, o = {}, deps = {}) {
3975
6139
  const srv = await startServer({
3976
6140
  root,
@@ -3993,17 +6157,10 @@ async function startUi(root, o = {}, deps = {}) {
3993
6157
  log.debug("request", fields);
3994
6158
  };
3995
6159
  })(),
3996
- extend: (r, ctx) => {
3997
- const base = uiKnowRoutes(uiRunRoutes(uiRoutes(r, ctx, deps), ctx, deps), ctx, {
3998
- warm: o.warm === true
3999
- });
4000
- const views = uiGraphRoutes(uiAcrossRoutes(uiMeRoutes(base, ctx), ctx), ctx);
4001
- const specs = uiSpecRoutes(uiOnboardingRoutes(uiQuestionRoutes(views, ctx), ctx), ctx);
4002
- return uiStageRoutes(uiContractsRoutes(uiOutsideRoutes(uiAuditRoutes(uiFactsRoutes(uiTrustRoutes(uiCockpitRoutes(uiRepoRoutes(uiDocumentRoutes(uiAiRoutes(uiHistoryRoutes(uiModelRunRoutes(uiKnowledgeRoutes(uiCiteRoutes(specs, ctx), ctx), ctx), ctx), ctx), ctx), ctx), ctx), ctx), ctx), ctx), ctx), ctx), ctx);
4003
- }
6160
+ extend: (r, ctx) => extendUiRoutes(r, ctx, deps, { warm: o.warm === true })
4004
6161
  });
4005
6162
  keepCatalogsInMemory(true);
4006
- void readCatalog(join11(catalogDirOf(root), "generated"), { cacheRoot: root }).catch(() => void 0);
6163
+ void readCatalog(join14(catalogDirOf(root), "generated"), { cacheRoot: root }).catch(() => void 0);
4007
6164
  const stop = srv.events.listen((e) => {
4008
6165
  const changed = e.type === "file.changed" && (typeof e.path !== "string" || catalogMayHaveChanged(root, e.path));
4009
6166
  if (changed || e.type === "index.rebuilt")
@@ -4096,12 +6253,14 @@ async function uiCommand(root, o, io) {
4096
6253
  if (o.open)
4097
6254
  openBrowser(srv.url);
4098
6255
  await untilStopped(o.embed === "vscode", process.stdin, o.parent !== void 0 ? { parent: o.parent } : {});
6256
+ stopAllBuilds();
4099
6257
  await srv.close();
4100
6258
  io.out("Stopped.");
4101
6259
  return 0;
4102
6260
  }
4103
6261
  export {
4104
6262
  catalogMayHaveChanged,
6263
+ extendUiRoutes,
4105
6264
  startUi,
4106
6265
  startedLines,
4107
6266
  uiCommand,