@maycombhq/atticus 2.0.0-beta.2 → 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 (273) hide show
  1. package/dist/bin.js +8 -4
  2. package/dist/chunk-265LWMOA.js +91 -0
  3. package/dist/chunk-26WBY34Y.js +37 -0
  4. package/dist/{chunk-UBBC2XSG.js → chunk-26Z7OGQW.js} +518 -93
  5. package/dist/chunk-2D7PWSOK.js +172 -0
  6. package/dist/{chunk-JCZIKA4C.js → chunk-2HH3UCZM.js} +4 -4
  7. package/dist/{chunk-GDLULHTC.js → chunk-2HTEMYTB.js} +12 -26
  8. package/dist/chunk-2UCRFXFZ.js +130 -0
  9. package/dist/{chunk-MVZVGYXH.js → chunk-2V2ZPQQU.js} +81 -13
  10. package/dist/{chunk-BOHJZTEQ.js → chunk-36RY7ZXM.js} +130 -71
  11. package/dist/{chunk-I3E7CX4H.js → chunk-37WE3LS2.js} +3 -3
  12. package/dist/{chunk-IPKHE5D6.js → chunk-3AQ2R5NL.js} +1520 -871
  13. package/dist/chunk-3G6S7LAA.js +43 -0
  14. package/dist/chunk-3J5I7ZOU.js +58 -0
  15. package/dist/{chunk-65NYPGRC.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-3C7EYRFZ.js → chunk-3UTYPHN3.js} +1 -1
  19. package/dist/{chunk-G2B4ZQGH.js → chunk-3WON4G3M.js} +2 -2
  20. package/dist/{chunk-S3QNRWYR.js → chunk-42EPP7OB.js} +12 -12
  21. package/dist/chunk-455SYIIE.js +113 -0
  22. package/dist/{chunk-YISXLPHN.js → chunk-4B6BT75O.js} +166 -23
  23. package/dist/{chunk-C6EKFH7J.js → chunk-4CLK2RJO.js} +7 -7
  24. package/dist/chunk-4CYTJ5TZ.js +38 -0
  25. package/dist/{chunk-EYWIDJFU.js → chunk-4D62IZII.js} +5 -5
  26. package/dist/chunk-4FOAYLI4.js +328 -0
  27. package/dist/{chunk-7JUKDRSZ.js → chunk-4KNOJC6J.js} +294 -215
  28. package/dist/{chunk-YMZ3TNYB.js → chunk-4PYC2UGC.js} +2 -2
  29. package/dist/{chunk-27NKUS3X.js → chunk-4SLBCEPD.js} +2104 -726
  30. package/dist/chunk-4TKVU2I2.js +122 -0
  31. package/dist/{chunk-RUVFXE2L.js → chunk-4WXRPQDE.js} +448 -47
  32. package/dist/{chunk-7ZUIMTOU.js → chunk-5CMPVC3N.js} +24 -24
  33. package/dist/chunk-5G7ILBUG.js +66 -0
  34. package/dist/{chunk-HFQCFSJR.js → chunk-5PV7S2ZP.js} +3 -3
  35. package/dist/{chunk-FITWGOJ2.js → chunk-5QUOPNLN.js} +4 -4
  36. package/dist/{chunk-Z6LF7HQN.js → chunk-5TDRBOAV.js} +5 -5
  37. package/dist/{chunk-T6AIP7H5.js → chunk-5WERBKLE.js} +199 -63
  38. package/dist/{chunk-XPCM366L.js → chunk-62DLJSLV.js} +8 -8
  39. package/dist/{chunk-L24C55VN.js → chunk-64AADMTW.js} +99 -25
  40. package/dist/{chunk-K65BJIMT.js → chunk-67BP7HXL.js} +17 -17
  41. package/dist/chunk-6DQYZU2W.js +379 -0
  42. package/dist/{chunk-EDS2L2LD.js → chunk-6EXDI5GI.js} +52 -26
  43. package/dist/chunk-6FNAKCET.js +38 -0
  44. package/dist/{chunk-6IU67AEK.js → chunk-75ASLHBT.js} +21 -15
  45. package/dist/{chunk-A3GY6SOA.js → chunk-75ZOZFGH.js} +18 -18
  46. package/dist/{chunk-MGYZM27B.js → chunk-77O633L5.js} +49 -23
  47. package/dist/{chunk-GQ4N4WKF.js → chunk-7AB6OWTV.js} +3 -3
  48. package/dist/{chunk-M766ZJ6W.js → chunk-7CZHUZBL.js} +25 -25
  49. package/dist/{chunk-A7UPUQFY.js → chunk-7FHNFCHK.js} +643 -124
  50. package/dist/chunk-7FUIVIBG.js +415 -0
  51. package/dist/{chunk-6QSERWXH.js → chunk-7QPCGTPS.js} +21 -21
  52. package/dist/{chunk-LR3R6Z2A.js → chunk-A2G72UI4.js} +7 -7
  53. package/dist/{chunk-I4JRCD6M.js → chunk-AE4P57KK.js} +23 -17
  54. package/dist/{chunk-AVJDSJ2F.js → chunk-AHW3RY3X.js} +7 -7
  55. package/dist/chunk-AM6FP6JW.js +43 -0
  56. package/dist/{chunk-CSZ4C5N6.js → chunk-AOCWFA7T.js} +93 -10
  57. package/dist/{chunk-EPTBZ3GP.js → chunk-API5TOUB.js} +1 -1
  58. package/dist/{chunk-652QWTLZ.js → chunk-AZDITBCT.js} +3 -3
  59. package/dist/{chunk-DPUYDDQG.js → chunk-BA2HUMDN.js} +25 -3
  60. package/dist/chunk-BA3U7ALS.js +99 -0
  61. package/dist/{chunk-HVUPKCGJ.js → chunk-BC6KKZ67.js} +5 -5
  62. package/dist/{chunk-CKSITVS4.js → chunk-BEPCKDCP.js} +2554 -722
  63. package/dist/{chunk-3ALD2W2A.js → chunk-BPPU3PW7.js} +4 -4
  64. package/dist/{chunk-QRAOANID.js → chunk-BVWWPBKD.js} +21 -21
  65. package/dist/{chunk-ZDPAS56H.js → chunk-BXBCMGIQ.js} +7 -1
  66. package/dist/chunk-BXZ4PKIT.js +233 -0
  67. package/dist/chunk-C7CEK3KW.js +247 -0
  68. package/dist/chunk-CFH3QH26.js +29 -0
  69. package/dist/chunk-CG6CULP6.js +170 -0
  70. package/dist/chunk-CLD4RVTA.js +45 -0
  71. package/dist/{chunk-DKJA76CJ.js → chunk-CNPY4NTZ.js} +13 -13
  72. package/dist/chunk-CNU5Q2D5.js +575 -0
  73. package/dist/{chunk-T2UYZSCQ.js → chunk-COYN3UAN.js} +126 -23
  74. package/dist/{chunk-23W7VWKR.js → chunk-CPK7QSBG.js} +1360 -113
  75. package/dist/{chunk-4AK7IBVP.js → chunk-D2WSW4GU.js} +1 -1
  76. package/dist/{chunk-ABWHYWNI.js → chunk-D57MFJPP.js} +3 -3
  77. package/dist/chunk-D5KV5PSK.js +45 -0
  78. package/dist/chunk-D5MASS4I.js +48 -0
  79. package/dist/{chunk-7EOBHUMC.js → chunk-DCWN3HFV.js} +2 -2
  80. package/dist/{chunk-QSXK2RGH.js → chunk-DDJPM5YX.js} +2 -2
  81. package/dist/chunk-DN4A2Q2O.js +57 -0
  82. package/dist/{chunk-IHMLFRVV.js → chunk-DUPZPDCJ.js} +58 -16
  83. package/dist/{chunk-PRPPGYZG.js → chunk-E2VXBSIR.js} +4 -4
  84. package/dist/chunk-E57AOFYN.js +32 -0
  85. package/dist/{chunk-UGLN7QHV.js → chunk-ECLXZWAG.js} +12 -12
  86. package/dist/{chunk-5WG5YP4T.js → chunk-EDZ45WIR.js} +14 -14
  87. package/dist/chunk-EFCQSMXB.js +37 -0
  88. package/dist/{chunk-2ANOROX3.js → chunk-EZIN5E5B.js} +8 -8
  89. package/dist/{chunk-BWZ77RQT.js → chunk-F6H2RJ3N.js} +1 -1
  90. package/dist/{chunk-OLQJTJJU.js → chunk-FAJDKNSD.js} +7 -3
  91. package/dist/chunk-FGAGWYZB.js +47 -0
  92. package/dist/{chunk-4WBVUHXN.js → chunk-FLVZFTLQ.js} +18 -18
  93. package/dist/chunk-FQ6WH6PB.js +140 -0
  94. package/dist/{chunk-T6XQL73F.js → chunk-FRHD22WM.js} +61 -23
  95. package/dist/{chunk-XAKWMCXI.js → chunk-FVMPFCDS.js} +4 -4
  96. package/dist/{chunk-B2WLAWNJ.js → chunk-FY3QIRTL.js} +3 -3
  97. package/dist/{chunk-CPA6HPVR.js → chunk-FYBF5MB2.js} +15 -11
  98. package/dist/{chunk-V2YTAIQO.js → chunk-G5YJBNDZ.js} +21 -6
  99. package/dist/chunk-G6IQYM25.js +339 -0
  100. package/dist/chunk-G6X4YL4D.js +161 -0
  101. package/dist/{chunk-SJRGMG6A.js → chunk-GC2VK3JT.js} +25 -25
  102. package/dist/{chunk-SP2I63OO.js → chunk-GCS3FZOB.js} +77 -3
  103. package/dist/{chunk-6TACQN3N.js → chunk-GDZB57IU.js} +5 -5
  104. package/dist/chunk-GIFMBCTW.js +221 -0
  105. package/dist/{chunk-YY6ZQQBP.js → chunk-GK4PYVEC.js} +18 -8
  106. package/dist/{chunk-TDD55CJH.js → chunk-GNWAOVFQ.js} +6 -5
  107. package/dist/chunk-GS76RXQE.js +226 -0
  108. package/dist/{chunk-YYQN46FY.js → chunk-GSGIZ2OO.js} +23 -23
  109. package/dist/chunk-GUYL7RMK.js +60 -0
  110. package/dist/chunk-GX74YWR7.js +321 -0
  111. package/dist/chunk-HE3HRDNZ.js +122 -0
  112. package/dist/{chunk-AIOQPLA5.js → chunk-HGHRSF3E.js} +30 -12
  113. package/dist/{chunk-2RMYG36Q.js → chunk-HK3VOHIE.js} +198 -336
  114. package/dist/{chunk-QZSX3F7C.js → chunk-HS4BGB4G.js} +24 -22
  115. package/dist/{chunk-XY3P7JW7.js → chunk-HSZMPH5T.js} +834 -149
  116. package/dist/{chunk-L6SGQIR3.js → chunk-HWD4JPU7.js} +4 -2
  117. package/dist/{chunk-PCVDV3UZ.js → chunk-HXXP2NTE.js} +6 -6
  118. package/dist/{chunk-AA6YULYB.js → chunk-I2A3Z5AK.js} +7 -7
  119. package/dist/{chunk-N7IXEHK3.js → chunk-ICHDSWEY.js} +7 -7
  120. package/dist/chunk-IFFRGC3N.js +31 -0
  121. package/dist/{chunk-4BN53TG3.js → chunk-IFHDV6KQ.js} +31 -23
  122. package/dist/chunk-J2O4LFVI.js +80 -0
  123. package/dist/chunk-J5AAZ6OJ.js +161 -0
  124. package/dist/{chunk-ZMFPW66A.js → chunk-JAABT3DA.js} +3 -3
  125. package/dist/chunk-JITJP64K.js +34 -0
  126. package/dist/chunk-JKIGL3XQ.js +162 -0
  127. package/dist/chunk-JRVNXLZP.js +615 -0
  128. package/dist/chunk-JRXHON4E.js +41 -0
  129. package/dist/{chunk-DHPACDG7.js → chunk-JS6WMC3J.js} +8 -8
  130. package/dist/chunk-JU4QNJSR.js +62 -0
  131. package/dist/{chunk-PAMPPUR6.js → chunk-JYMIB5XY.js} +1697 -172
  132. package/dist/{chunk-RRTKKSLQ.js → chunk-JYTIHTIK.js} +8 -6
  133. package/dist/{chunk-3HI6Z55Y.js → chunk-K332XW7K.js} +324 -39
  134. package/dist/{chunk-GLUX4EEP.js → chunk-K6JB6XTX.js} +9 -9
  135. package/dist/chunk-KDAHXQIE.js +294 -0
  136. package/dist/chunk-KYZMRSPS.js +93 -0
  137. package/dist/chunk-L4CESDPB.js +22 -0
  138. package/dist/{chunk-NXFPS2LR.js → chunk-LEBR5NHW.js} +14 -14
  139. package/dist/chunk-LIJJCBQI.js +223 -0
  140. package/dist/{chunk-DE76JROX.js → chunk-LIRVU3HW.js} +16 -16
  141. package/dist/{chunk-JMSCMUCA.js → chunk-LN77DXBM.js} +145 -21
  142. package/dist/{chunk-JIITQXFB.js → chunk-LNWEWYCM.js} +2969 -788
  143. package/dist/{chunk-TANFV3MZ.js → chunk-LO3BLMVG.js} +3 -3
  144. package/dist/chunk-LPV6T362.js +250 -0
  145. package/dist/{chunk-2VHF7OXM.js → chunk-LQH4P45K.js} +1 -1
  146. package/dist/chunk-LU4MQVAL.js +41 -0
  147. package/dist/chunk-LWDNKE4X.js +273 -0
  148. package/dist/chunk-M4H2UG4R.js +218 -0
  149. package/dist/{chunk-6BVONWJ7.js → chunk-M6D2U3YM.js} +3 -3
  150. package/dist/chunk-MD7KUEJ3.js +514 -0
  151. package/dist/{chunk-66YVAGGM.js → chunk-MMZUN7ZO.js} +20 -20
  152. package/dist/chunk-MZGRDONP.js +916 -0
  153. package/dist/chunk-N3RUGTGV.js +39 -0
  154. package/dist/{chunk-JGZCG5VM.js → chunk-NCB6656Z.js} +7 -7
  155. package/dist/{chunk-4NDAAFQT.js → chunk-NRQ65LTQ.js} +7 -5
  156. package/dist/{chunk-UNM6ZOKP.js → chunk-NTAVOWYD.js} +145 -18
  157. package/dist/{chunk-3USYZGRF.js → chunk-NYOABJNK.js} +36 -27
  158. package/dist/{chunk-WVPJSRRJ.js → chunk-O4IKAIZ2.js} +24 -20
  159. package/dist/{chunk-D3PO4DOZ.js → chunk-ODMBPDDV.js} +51 -14
  160. package/dist/chunk-OKUB6RJA.js +533 -0
  161. package/dist/chunk-OMPENNWY.js +39 -0
  162. package/dist/chunk-OPQZISG3.js +25 -0
  163. package/dist/{chunk-5GGOLAKC.js → chunk-OV57AHCG.js} +26 -26
  164. package/dist/chunk-OZJ3JLYI.js +58 -0
  165. package/dist/{chunk-KEM4SDDP.js → chunk-P2VBXX7N.js} +11 -10
  166. package/dist/{chunk-S2A7YC3Q.js → chunk-PAINMSID.js} +10150 -1720
  167. package/dist/chunk-PKJAV25S.js +43 -0
  168. package/dist/{chunk-EBEW3XXQ.js → chunk-PNQLCRSS.js} +4 -4
  169. package/dist/{chunk-WY6PP6AA.js → chunk-PQH56YF6.js} +37 -5
  170. package/dist/chunk-PRTBPXYA.js +50 -0
  171. package/dist/{chunk-YEDDOYSC.js → chunk-PRW3HXM3.js} +2 -2
  172. package/dist/chunk-PRZ7KMWL.js +42 -0
  173. package/dist/{chunk-6M5NK3K3.js → chunk-PTQ3ZALZ.js} +42 -4
  174. package/dist/{chunk-5GDRCDQB.js → chunk-PUEWDYGP.js} +2 -2
  175. package/dist/chunk-Q26LDIUV.js +236 -0
  176. package/dist/{chunk-CR3MTH32.js → chunk-Q46QDDMF.js} +77 -7
  177. package/dist/chunk-QBNWF5ZU.js +19 -0
  178. package/dist/chunk-QE527XRY.js +342 -0
  179. package/dist/{chunk-CNUKCS2T.js → chunk-QHJJMZLX.js} +1518 -54
  180. package/dist/chunk-QJCJOMW3.js +425 -0
  181. package/dist/{chunk-VYDBJ73M.js → chunk-QKMRFNMA.js} +15 -15
  182. package/dist/chunk-QOOEYDNJ.js +317 -0
  183. package/dist/chunk-QSIDVFB7.js +45 -0
  184. package/dist/{chunk-R77KLRB3.js → chunk-RA33EYS4.js} +19 -18
  185. package/dist/{chunk-DFBCSHVT.js → chunk-RALY3E5U.js} +4 -4
  186. package/dist/{chunk-ZCJDOK2S.js → chunk-S2RY3BOD.js} +35 -21
  187. package/dist/chunk-S5LSDXV4.js +40 -0
  188. package/dist/chunk-SB5GRUFM.js +54 -0
  189. package/dist/chunk-SH27FYPF.js +150 -0
  190. package/dist/chunk-T74WQQGS.js +40 -0
  191. package/dist/{chunk-O3WI5KWW.js → chunk-TGVVUKTG.js} +2 -2
  192. package/dist/chunk-THN26IEF.js +48 -0
  193. package/dist/chunk-TJH2EUAZ.js +47 -0
  194. package/dist/{chunk-ZNCEKR4J.js → chunk-TQ4TG57V.js} +12 -8
  195. package/dist/{chunk-7FBGJFEE.js → chunk-TQTRWENN.js} +12 -12
  196. package/dist/{chunk-TYAID7B7.js → chunk-TSM6T6GQ.js} +23 -23
  197. package/dist/{chunk-DPZHZQKE.js → chunk-TW3KOSQK.js} +43 -10
  198. package/dist/{chunk-MC3H7736.js → chunk-TWQRPM76.js} +37 -4
  199. package/dist/chunk-TYD6I4CZ.js +109 -0
  200. package/dist/chunk-U3NBK4EU.js +420 -0
  201. package/dist/{chunk-6JCPI55B.js → chunk-UDXWIPCB.js} +47 -7
  202. package/dist/{chunk-MMOSZPWJ.js → chunk-UEVZZZU2.js} +4 -4
  203. package/dist/{chunk-JBTK5OP2.js → chunk-UF4UHCIG.js} +2 -2
  204. package/dist/{chunk-6JUBZBMW.js → chunk-UFE36WJH.js} +4 -4
  205. package/dist/{chunk-ONLS5QA5.js → chunk-URIKUID7.js} +12 -12
  206. package/dist/chunk-UYLBRRFK.js +1106 -0
  207. package/dist/chunk-UZACEG2C.js +515 -0
  208. package/dist/{chunk-7SW2QWLU.js → chunk-VAMHOHUU.js} +12 -12
  209. package/dist/{chunk-RZPYKDWS.js → chunk-VUVGX5L2.js} +1 -1
  210. package/dist/{chunk-Q4D5X7QB.js → chunk-VYOCPTUB.js} +44 -24
  211. package/dist/{chunk-CAQZUNOH.js → chunk-WAZEXEJH.js} +567 -3
  212. package/dist/chunk-WCEF7H6L.js +221 -0
  213. package/dist/{chunk-2DIQQJCB.js → chunk-WEOMJHPV.js} +12 -12
  214. package/dist/{chunk-FHH4I4ZV.js → chunk-WFU77T3A.js} +12 -6
  215. package/dist/chunk-WIWTJMMR.js +292 -0
  216. package/dist/{chunk-2BFCZ3RK.js → chunk-WNTIOBOG.js} +18 -18
  217. package/dist/{chunk-7WD63HU2.js → chunk-X2CUXTWU.js} +6 -6
  218. package/dist/chunk-X7EMBM5X.js +34 -0
  219. package/dist/chunk-XEQCQO66.js +33 -0
  220. package/dist/{chunk-F7HYOUFF.js → chunk-XG4WJHLQ.js} +20 -4
  221. package/dist/{chunk-EFHQ3TWP.js → chunk-XOL3GFW2.js} +171 -31
  222. package/dist/{chunk-NE2W3OO7.js → chunk-XQ3YZ4QW.js} +22 -22
  223. package/dist/{chunk-CEXSU4S6.js → chunk-Y6K3CJBU.js} +723 -85
  224. package/dist/{chunk-4RS5KZQD.js → chunk-YAH5IQAL.js} +5 -5
  225. package/dist/{chunk-64EQYWCA.js → chunk-YBOVNMHC.js} +51 -14
  226. package/dist/chunk-YF5DZCPN.js +1212 -0
  227. package/dist/chunk-YIGBJNH4.js +37 -0
  228. package/dist/{chunk-BKVWTZ6A.js → chunk-YJ3TBOTK.js} +25 -25
  229. package/dist/{chunk-Q5AMFEWD.js → chunk-YKTE2RU3.js} +2 -2
  230. package/dist/{chunk-7EKXJZ2Y.js → chunk-YO2PKTXT.js} +296 -37
  231. package/dist/{chunk-6J5OKA4Y.js → chunk-YTTNTQOX.js} +15 -15
  232. package/dist/{chunk-OT2O76OS.js → chunk-YWAKDNMH.js} +183 -33
  233. package/dist/{chunk-R6XF6UR3.js → chunk-YZDZOEV4.js} +4 -4
  234. package/dist/chunk-Z3QFH7YG.js +17 -0
  235. package/dist/{chunk-W5BVQF44.js → chunk-Z6VLC5X4.js} +17 -17
  236. package/dist/{chunk-HLJAXMM4.js → chunk-ZFQMMU5Y.js} +2 -2
  237. package/dist/{chunk-W3BQAZ3Q.js → chunk-ZKYRDV7A.js} +12 -12
  238. package/dist/{chunk-ZMAINWBW.js → chunk-ZLS6PU2O.js} +3 -3
  239. package/dist/{chunk-ZVM6AKKM.js → chunk-ZOSD4FXV.js} +3 -3
  240. package/dist/chunk-ZPDDEVQX.js +47 -0
  241. package/dist/{chunk-TXLH6WHQ.js → chunk-ZQLRCA2V.js} +11 -11
  242. package/dist/{chunk-HITVBIJS.js → chunk-ZX7235KR.js} +128 -54
  243. package/dist/{chunk-GT4I4ZWM.js → chunk-ZZFHLRQW.js} +18 -16
  244. package/dist/files.json +636 -364
  245. package/dist/packages/naysayer/constitution/constitution.yaml +19 -0
  246. package/dist/packages/sdk/npm/index.js +5614 -2343
  247. package/dist/packages/ui/dist/assets/{elk-api-tma0MCuh.js → elk-api-CowTnhEJ.js} +2 -2
  248. package/dist/packages/ui/dist/assets/index-Dw20IRhQ.css +1 -0
  249. package/dist/packages/ui/dist/assets/index-z2wv0d5d.js +47 -0
  250. package/dist/packages/ui/dist/index.html +2 -2
  251. package/package.json +4 -4
  252. package/dist/chunk-2BF2EYJV.js +0 -174
  253. package/dist/chunk-42EVSKME.js +0 -37
  254. package/dist/chunk-4BKGMUIK.js +0 -53
  255. package/dist/chunk-5KLGBXG5.js +0 -48
  256. package/dist/chunk-6BNC6EQ4.js +0 -41
  257. package/dist/chunk-6D2CQ5II.js +0 -41
  258. package/dist/chunk-7AVHLZGD.js +0 -45
  259. package/dist/chunk-D46WUV3T.js +0 -33
  260. package/dist/chunk-E36QOJMD.js +0 -47
  261. package/dist/chunk-FQXYPF7E.js +0 -147
  262. package/dist/chunk-GBRFVT63.js +0 -45
  263. package/dist/chunk-ICY36XCH.js +0 -34
  264. package/dist/chunk-J4K3KVLD.js +0 -131
  265. package/dist/chunk-JZU3H6UQ.js +0 -54
  266. package/dist/chunk-NN2ZQEB4.js +0 -38
  267. package/dist/chunk-PCOCACY3.js +0 -385
  268. package/dist/chunk-RTEYOSJY.js +0 -29
  269. package/dist/chunk-RVH5SUW5.js +0 -69
  270. package/dist/chunk-VN3DP4LQ.js +0 -43
  271. package/dist/chunk-XPTOBS2D.js +0 -48
  272. package/dist/packages/ui/dist/assets/index-B54fm9FQ.js +0 -29
  273. package/dist/packages/ui/dist/assets/index-DD_hufQz.css +0 -1
@@ -3,52 +3,48 @@ 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-DPUYDDQG.js";
10
6
  import {
11
7
  historyEntry,
12
8
  listHistory
13
- } from "./chunk-G2B4ZQGH.js";
9
+ } from "./chunk-3WON4G3M.js";
10
+ import "./chunk-JYTIHTIK.js";
14
11
  import {
15
- estimateOf
16
- } from "./chunk-FHH4I4ZV.js";
12
+ uiComponentTypeRoutes
13
+ } from "./chunk-CG6CULP6.js";
17
14
  import {
18
- changeArchive,
19
- changeHandoff,
20
- changeNew,
21
- changeSync
22
- } from "./chunk-T2UYZSCQ.js";
15
+ answerAudit,
16
+ auditLines,
17
+ defaultAuditSeed,
18
+ precisionOf,
19
+ prepareAudit,
20
+ writeAudit
21
+ } from "./chunk-7FUIVIBG.js";
22
+ import {
23
+ listRuns,
24
+ partStates,
25
+ runTranscript
26
+ } from "./chunk-BA2HUMDN.js";
23
27
  import {
24
- HttpError,
25
- badRequest,
26
- conflict,
27
- notFound,
28
- startServer,
29
- strParam,
30
28
  uiCiteRoutes
31
- } from "./chunk-2RMYG36Q.js";
32
- import "./chunk-RRTKKSLQ.js";
29
+ } from "./chunk-WCEF7H6L.js";
33
30
  import {
34
31
  isHandoffStep
35
32
  } from "./chunk-4Q3NN7VF.js";
36
33
  import {
37
- conformanceCommand
38
- } from "./chunk-V2YTAIQO.js";
39
- import {
40
- outcomeList,
41
- outcomeRecord,
42
- releaseEvidenceCommand,
43
- releaseForecastCommand,
44
- releaseNotesCommand,
45
- releasePlanCommand,
46
- releaseSecurityCommand
47
- } from "./chunk-652QWTLZ.js";
34
+ estimateOf
35
+ } from "./chunk-WFU77T3A.js";
48
36
  import {
49
37
  changeMapBody,
50
- changeMapOf
51
- } from "./chunk-TDD55CJH.js";
38
+ changeMapOf,
39
+ targetFlowsOf
40
+ } from "./chunk-GNWAOVFQ.js";
41
+ import {
42
+ changeArchive,
43
+ changeHandoff,
44
+ changeNew,
45
+ changeReleasePrs,
46
+ changeSync
47
+ } from "./chunk-COYN3UAN.js";
52
48
  import {
53
49
  isSandbox,
54
50
  learnCheck,
@@ -56,141 +52,253 @@ import {
56
52
  lessonsFor,
57
53
  readProgress,
58
54
  sandboxCreate
59
- } from "./chunk-TXLH6WHQ.js";
55
+ } from "./chunk-ZQLRCA2V.js";
60
56
  import {
61
57
  defencesScan,
62
58
  evalRun
63
- } from "./chunk-XAKWMCXI.js";
59
+ } from "./chunk-FVMPFCDS.js";
64
60
  import {
65
61
  listReports,
66
62
  readQuality
67
- } from "./chunk-CPA6HPVR.js";
63
+ } from "./chunk-FYBF5MB2.js";
68
64
  import {
69
- readServiceRequests
70
- } from "./chunk-BOHJZTEQ.js";
65
+ conformanceCommand
66
+ } from "./chunk-G5YJBNDZ.js";
71
67
  import {
72
- initiativeStatus,
73
- readView
74
- } from "./chunk-MVZVGYXH.js";
68
+ outcomeList,
69
+ outcomeRecord,
70
+ releaseEvidenceCommand,
71
+ releaseForecastCommand,
72
+ releaseNotesCommand,
73
+ releasePlanCommand,
74
+ releaseSecurityCommand
75
+ } from "./chunk-AZDITBCT.js";
76
+ import {
77
+ readServiceRequests
78
+ } from "./chunk-36RY7ZXM.js";
75
79
  import {
76
- taskRun
77
- } from "./chunk-CSZ4C5N6.js";
78
- import "./chunk-CEXSU4S6.js";
79
- import "./chunk-FITWGOJ2.js";
80
+ runDone,
81
+ taskRun,
82
+ taskRunNotWaiting
83
+ } from "./chunk-AOCWFA7T.js";
80
84
  import {
81
85
  executorsCheck,
82
86
  todoDone,
83
87
  todoRun,
84
88
  todoShow
85
- } from "./chunk-HLJAXMM4.js";
89
+ } from "./chunk-ZFQMMU5Y.js";
86
90
  import {
87
91
  inputsSha256,
88
92
  loadExecutors
89
- } from "./chunk-QSXK2RGH.js";
93
+ } from "./chunk-DDJPM5YX.js";
90
94
  import {
91
- uiOnboardingRoutes
92
- } from "./chunk-EBEW3XXQ.js";
95
+ initiativeStatus,
96
+ readView
97
+ } from "./chunk-2V2ZPQQU.js";
93
98
  import {
94
- readCorrections
95
- } from "./chunk-CR3MTH32.js";
96
- import "./chunk-O3WI5KWW.js";
99
+ DecideError,
100
+ correctDecision,
101
+ decisionsView,
102
+ undoBatch
103
+ } from "./chunk-UYLBRRFK.js";
97
104
  import {
98
- readMe,
99
- writeMe
100
- } from "./chunk-JBTK5OP2.js";
105
+ boxesOf,
106
+ candidatesOf,
107
+ uiLinkRoutes
108
+ } from "./chunk-6DQYZU2W.js";
101
109
  import {
102
- digestCommand
103
- } from "./chunk-7WD63HU2.js";
110
+ matchDone,
111
+ quotesOf,
112
+ readMatch,
113
+ runMatch
114
+ } from "./chunk-QJCJOMW3.js";
115
+ import "./chunk-3SSTNB4E.js";
116
+ import "./chunk-JRVNXLZP.js";
117
+ import {
118
+ copilotAcpAllowed
119
+ } from "./chunk-LIJJCBQI.js";
120
+ import "./chunk-26Z7OGQW.js";
121
+ import "./chunk-OKUB6RJA.js";
122
+ import {
123
+ filesUnder,
124
+ memoByFiles,
125
+ recheckNow,
126
+ uiFactsRoutes
127
+ } from "./chunk-QOOEYDNJ.js";
128
+ import {
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";
104
144
  import {
105
145
  reconcileCommand
106
- } from "./chunk-R6XF6UR3.js";
146
+ } from "./chunk-YZDZOEV4.js";
107
147
  import {
108
148
  sourcesAdd,
109
149
  sourcesSync
110
- } from "./chunk-PCVDV3UZ.js";
150
+ } from "./chunk-HXXP2NTE.js";
111
151
  import {
112
152
  INPUTS_SOURCE_ID
113
- } from "./chunk-ABWHYWNI.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";
114
175
  import {
115
176
  baselineCommand,
116
177
  setupSteps
117
- } from "./chunk-YY6ZQQBP.js";
118
- import {
119
- catalogRecords,
120
- openChanges,
121
- systemGuide
122
- } from "./chunk-B2WLAWNJ.js";
178
+ } from "./chunk-GK4PYVEC.js";
123
179
  import {
124
180
  doctorChecks
125
- } from "./chunk-AIOQPLA5.js";
126
- import "./chunk-3G4YI4ZU.js";
127
- import "./chunk-YISXLPHN.js";
128
- import "./chunk-GDLULHTC.js";
181
+ } from "./chunk-HGHRSF3E.js";
182
+ import "./chunk-4B6BT75O.js";
129
183
  import {
130
- handoffDone,
131
- workspaceOpen,
132
- workspaceSync
133
- } from "./chunk-4BN53TG3.js";
134
- import "./chunk-2VHF7OXM.js";
135
- import "./chunk-KEM4SDDP.js";
184
+ readMe,
185
+ writeMe
186
+ } from "./chunk-UF4UHCIG.js";
136
187
  import {
137
- critic,
138
- criticStats,
139
- findingAction
140
- } from "./chunk-6M5NK3K3.js";
141
- import "./chunk-LR3R6Z2A.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";
142
202
  import {
143
- planStories,
144
- snowDraft,
145
- snowSubmit
146
- } from "./chunk-AA6YULYB.js";
147
- import "./chunk-BWZ77RQT.js";
203
+ checkoutOf
204
+ } from "./chunk-Q46QDDMF.js";
205
+ import "./chunk-TGVVUKTG.js";
206
+ import {
207
+ intakeContext
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";
148
215
  import {
149
216
  archiveGate,
150
217
  ciArchiveGate
151
- } from "./chunk-HITVBIJS.js";
218
+ } from "./chunk-ZX7235KR.js";
152
219
  import {
153
220
  traceMatrix
154
- } from "./chunk-2ANOROX3.js";
155
- import "./chunk-65NYPGRC.js";
156
- import "./chunk-VOGCSYOI.js";
221
+ } from "./chunk-EZIN5E5B.js";
222
+ import "./chunk-3LZ6P5EP.js";
157
223
  import {
158
224
  scanCommand
159
- } from "./chunk-JGZCG5VM.js";
160
- import "./chunk-RVH5SUW5.js";
225
+ } from "./chunk-NCB6656Z.js";
226
+ import "./chunk-TYD6I4CZ.js";
161
227
  import {
162
228
  readChangeTasks,
163
- readStory
164
- } from "./chunk-OT2O76OS.js";
229
+ readStory,
230
+ setTaskDone
231
+ } from "./chunk-YWAKDNMH.js";
232
+ import {
233
+ catalogRecords,
234
+ openChanges,
235
+ systemGuide
236
+ } from "./chunk-FY3QIRTL.js";
237
+ import "./chunk-3G4YI4ZU.js";
238
+ import "./chunk-2HTEMYTB.js";
165
239
  import {
166
240
  ASSISTANT_FORMATS,
167
241
  readProbes,
168
242
  whatRunsWhere
169
- } from "./chunk-IPKHE5D6.js";
243
+ } from "./chunk-3AQ2R5NL.js";
244
+ import {
245
+ critic,
246
+ criticStats,
247
+ findingAction
248
+ } from "./chunk-PTQ3ZALZ.js";
249
+ import "./chunk-A2G72UI4.js";
250
+ import {
251
+ planStories,
252
+ snowDraft,
253
+ snowSubmit
254
+ } from "./chunk-I2A3Z5AK.js";
255
+ import "./chunk-F6H2RJ3N.js";
170
256
  import {
257
+ CORRECTIONS_FILE,
258
+ FEATURE_FILE,
171
259
  NEEDS_FILE,
172
260
  PlannerError,
173
261
  SYNC_FILE,
262
+ SnowFillError,
263
+ Stroke,
174
264
  TARGET_DIR,
265
+ addCorrection,
266
+ applyStroke,
175
267
  codeFactRecords,
176
268
  commentOnFinding,
177
269
  conflictTouches,
270
+ draftItems,
271
+ draftProvenance,
272
+ drawnStrokes,
273
+ fillSnowFields,
274
+ loadEstate,
178
275
  markNeed,
179
276
  mergeDelta,
180
277
  plainWhy,
278
+ planSketchWrite,
279
+ readCorrections,
181
280
  readDeltas,
182
281
  readKnowledgeApplies,
183
282
  readNeeds,
184
283
  readStageLoop,
284
+ recordPersonSave,
285
+ removeCorrection,
286
+ renderFeature,
185
287
  setKnowledgeStatus,
288
+ sketchFingerprint,
186
289
  stageComments,
187
- stageGate
188
- } from "./chunk-CKSITVS4.js";
290
+ stageGate,
291
+ writeSketch
292
+ } from "./chunk-BEPCKDCP.js";
189
293
  import {
294
+ FeaturePlacement,
295
+ STAGES,
190
296
  STAGE_PHASE,
191
297
  fieldNames,
192
298
  rerunOf
193
- } from "./chunk-L6SGQIR3.js";
299
+ } from "./chunk-HWD4JPU7.js";
300
+ import "./chunk-5WERBKLE.js";
301
+ import "./chunk-VOGCSYOI.js";
194
302
  import {
195
303
  layerRules,
196
304
  loadConstitution,
@@ -203,8 +311,9 @@ import {
203
311
  readStoryIndex,
204
312
  reviewedGates,
205
313
  roundsOf
206
- } from "./chunk-3HI6Z55Y.js";
314
+ } from "./chunk-K332XW7K.js";
207
315
  import {
316
+ ChangeOwnersError,
208
317
  artifactFiles,
209
318
  artifactPhase,
210
319
  changeFolder,
@@ -214,19 +323,27 @@ import {
214
323
  detectConflicts,
215
324
  liveHandoff,
216
325
  loadChangeContext,
217
- requiredArtifacts
218
- } from "./chunk-XY3P7JW7.js";
219
- import "./chunk-4AK7IBVP.js";
326
+ requiredArtifacts,
327
+ setChangeOwners
328
+ } from "./chunk-HSZMPH5T.js";
329
+ import "./chunk-D2WSW4GU.js";
220
330
  import {
221
331
  fileStepStore,
222
332
  manualAdapter,
223
333
  stepOfProvisional
224
- } from "./chunk-7JUKDRSZ.js";
334
+ } from "./chunk-4KNOJC6J.js";
225
335
  import {
336
+ componentRenames,
337
+ estateFacts,
338
+ estateRepos,
226
339
  forgetCatalogs,
340
+ hostAliases,
341
+ joinReposOf,
227
342
  keepCatalogsInMemory,
228
- readCatalog
229
- } from "./chunk-27NKUS3X.js";
343
+ outsideProfileSystems,
344
+ readCatalog,
345
+ targetPicker
346
+ } from "./chunk-4SLBCEPD.js";
230
347
  import {
231
348
  OrgPackProposal,
232
349
  SNAPSHOT_FILE,
@@ -250,106 +367,128 @@ import {
250
367
  snapshotDates,
251
368
  snapshotSources,
252
369
  writeReview
253
- } from "./chunk-L24C55VN.js";
370
+ } from "./chunk-64AADMTW.js";
254
371
  import {
255
372
  NoSuchNode,
256
- applyCorrections,
257
373
  buildEstate,
258
- correctedFact,
259
- correctionFor,
260
374
  focusView,
261
- frameworkFacts,
262
- frameworkGaps,
263
375
  nodeView,
264
376
  viewKey
265
- } from "./chunk-PAMPPUR6.js";
377
+ } from "./chunk-JYMIB5XY.js";
266
378
  import {
267
379
  readServiceRequestExports,
380
+ readVendorSpec,
268
381
  readWorkItemExports
269
- } from "./chunk-23W7VWKR.js";
270
- import "./chunk-7EKXJZ2Y.js";
271
- import "./chunk-MC3H7736.js";
272
- import "./chunk-T6AIP7H5.js";
273
- import "./chunk-RZPYKDWS.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";
274
387
  import {
275
388
  baseClonePath,
276
389
  loadRepos,
277
390
  readChangeRepos
278
- } from "./chunk-EFHQ3TWP.js";
279
- import "./chunk-GQ4N4WKF.js";
391
+ } from "./chunk-XOL3GFW2.js";
280
392
  import {
281
393
  COMMON_SETTINGS,
282
394
  PROVIDER_SETTINGS,
283
395
  defaultKeyStore,
284
396
  looksLikeKey
285
- } from "./chunk-YEDDOYSC.js";
397
+ } from "./chunk-PRW3HXM3.js";
286
398
  import {
287
399
  providerFactoryFor
288
- } from "./chunk-CNUKCS2T.js";
400
+ } from "./chunk-QHJJMZLX.js";
401
+ import "./chunk-7AB6OWTV.js";
289
402
  import {
290
403
  ASSISTANTS,
291
404
  ChangeArtifact,
292
405
  DEFAULT_PORT,
293
406
  DEFAULT_RUNTIME,
407
+ DeclareError,
294
408
  DiagramLayout,
295
- FACT_FIXED_FIELDS,
296
- FactCorrection,
297
409
  KNOWLEDGE_KINDS,
298
410
  KNOWLEDGE_RECORD_KIND,
299
411
  LAYOUT_SCHEMA_VERSION,
300
412
  MANUAL_SYSTEMS,
413
+ ManifestEditError,
301
414
  QuestionError,
415
+ QuestionRole,
302
416
  QuestionState,
303
417
  RUN_SCHEMA_VERSION,
304
418
  RecordId,
305
419
  Runtime,
306
420
  SOURCE_KIND_SUGGESTIONS,
421
+ SPEC_HEADER_FILE,
422
+ TARGETS_STEP_ID,
423
+ TOPICS_STEP_ID,
307
424
  ViewKey,
425
+ acceptProposal,
426
+ addProfileSystem,
427
+ addRolesTeam,
428
+ answerByWords,
308
429
  answerQuestion,
309
430
  api_exports,
310
431
  catalogDirOf,
432
+ changedLines,
311
433
  changesDirOf,
312
434
  changesRelOf,
313
- correctionLine,
435
+ copilotAcpOf,
436
+ currentSignal,
437
+ decideOf,
438
+ declareLink,
439
+ declaredOf,
440
+ diagramsRelOf,
441
+ eolOf,
314
442
  external_exports,
443
+ factIdPart,
315
444
  factsDirOf,
316
- factsFileOf,
317
445
  factsRelOf,
318
446
  gateRoundLimit,
447
+ inEol,
319
448
  inputsDirOf,
320
449
  isAdopted,
450
+ joinConventions,
451
+ joinSharedMaps,
452
+ joinVendorOperations,
321
453
  knowledgeDirOf,
322
454
  knowledgeKindOf,
323
455
  knowledgeRelOf,
324
456
  layoutPath,
457
+ lfOnly,
325
458
  listQuestions,
326
459
  loadOrgPack,
460
+ manifestFiles,
327
461
  orgPackDirOf,
328
462
  pageStanding,
329
- parseFacts,
330
- parseFactsLines,
463
+ parseCorrections,
331
464
  proposalsDirOf,
332
465
  proposalsRelOf,
333
466
  readEstateJoins,
334
467
  readQuestion,
335
468
  readRepoSpec,
469
+ readSharedMaps,
336
470
  require_dist,
337
471
  runsDirOf,
338
472
  runsRelOf,
473
+ setManifestField,
474
+ specDirOf,
339
475
  specRelOf,
340
- specRepos
341
- } from "./chunk-S2A7YC3Q.js";
476
+ specRepos,
477
+ undeclareLink
478
+ } from "./chunk-PAINMSID.js";
342
479
  import {
480
+ FileChangedError,
343
481
  createLog,
344
- writeFileAtomic
345
- } from "./chunk-D3PO4DOZ.js";
482
+ writeFileAtomic,
483
+ writeFileIfUnchanged
484
+ } from "./chunk-ODMBPDDV.js";
346
485
  import {
347
486
  __toESM
348
487
  } from "./chunk-TV45EUGM.js";
349
488
 
350
489
  // packages/cli/dist/ui-commands.js
351
490
  import { spawn } from "node:child_process";
352
- import { isAbsolute, join as join12, relative as relative3, sep as sep3 } from "node:path";
491
+ import { isAbsolute, join as join14, relative as relative2, sep as sep2 } from "node:path";
353
492
 
354
493
  // packages/ui/dist-lib/index.js
355
494
  import { dirname, join } from "node:path";
@@ -361,8 +500,45 @@ function uiDistDir() {
361
500
 
362
501
  // packages/cli/dist/ui-api.js
363
502
  import { createHash } from "node:crypto";
364
- import { readdir, readFile, writeFile } from "node:fs/promises";
365
- 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
+ ]);
366
542
  async function run(f) {
367
543
  const out = [];
368
544
  const err = [];
@@ -413,6 +589,7 @@ async function changeDetail(root, id) {
413
589
  const current = report.phase ? phases.indexOf(report.phase) : phases.length;
414
590
  const artifacts = [];
415
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));
416
593
  const cctx = await loadChangeContext(root);
417
594
  const onStages = (await Promise.all(cctx.policy.stages.map((s) => readFile(join2(dir, ...s.loop.split("/"))).then(() => true, () => false)))).some(Boolean);
418
595
  const type = report.type.artifacts;
@@ -432,6 +609,8 @@ async function changeDetail(root, id) {
432
609
  artifact: a,
433
610
  path: `${changesRelOf(root)}/${id}/${files[0] ?? `${a}.md`}`,
434
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),
435
614
  openFindings: findings.filter((f) => f.state === "open" && f.artifact.startsWith(a)).length
436
615
  });
437
616
  }
@@ -446,6 +625,8 @@ async function changeDetail(root, id) {
446
625
  openFindings: open,
447
626
  updatedAt: c.history.at(-1)?.at ?? c.createdAt,
448
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,
449
630
  artifacts,
450
631
  ...h ? {
451
632
  handoff: {
@@ -456,13 +637,82 @@ async function changeDetail(root, id) {
456
637
  } : {}
457
638
  };
458
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
+ }
459
708
  async function runView(root, change, runId) {
460
709
  const dir = join2(runsDirOf(root), change, runId);
461
- const text2 = await readFile(join2(dir, "run.json"), "utf8").catch(() => void 0);
462
- if (!text2)
710
+ const text = await readFile(join2(dir, "run.json"), "utf8").catch(() => void 0);
711
+ if (!text)
463
712
  return void 0;
464
- const r = JSON.parse(text2);
713
+ const r = JSON.parse(text);
465
714
  const manifest = await readFile(join2(dir, "context-manifest.json"), "utf8").catch(() => "");
715
+ const tr = await taskRunOfRun(root, change, runId).catch(() => void 0);
466
716
  return {
467
717
  schemaVersion: RUN_SCHEMA_VERSION,
468
718
  runId: r.runId,
@@ -479,7 +729,22 @@ async function runView(root, change, runId) {
479
729
  attempt: r.attempt ?? 1,
480
730
  ...r.stepClass ? { stepClass: r.stepClass } : {},
481
731
  ...r.model ? { model: r.model } : {},
482
- 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
+ } : {}
483
748
  };
484
749
  }
485
750
  async function findRun(root, runId) {
@@ -492,23 +757,137 @@ async function findRun(root, runId) {
492
757
  }
493
758
  throw notFound(`There is no run ${runId}.`);
494
759
  }
495
- async function artifactPath(root, id, artifact) {
760
+ async function artifactPath(root, id, artifact, o = {}) {
496
761
  const a = ChangeArtifact.safeParse(artifact);
497
762
  if (!a.success)
498
763
  throw badRequest(`${artifact} is not an artifact. Use one of ${ChangeArtifact.options.join(", ")}.`);
499
764
  const dir = join2(changesDirOf(root), id);
500
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`;
501
773
  if (files.length === 0)
502
774
  throw notFound(`Change ${id} has no ${artifact} yet.`);
503
775
  return files[0];
504
776
  }
505
777
  function uiRoutes(r, ctx, deps = {}) {
506
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();
507
796
  const changeDeps = {
508
797
  ...deps.gitHost ? { gitHost: deps.gitHost } : {},
509
798
  ...deps.now ? { now: deps.now } : {}
510
799
  };
511
- 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 }) => {
512
891
  const b = parseBody(NewChange, body);
513
892
  const owners = Object.entries(b.owners ?? {}).map(([role, who]) => `${role}=${who.join(",")}`);
514
893
  const res = await run((io) => changeNew(root, {
@@ -530,7 +909,22 @@ function uiRoutes(r, ctx, deps = {}) {
530
909
  }).add("GET", "/api/changes/{changeId}", async ({ params }) => ({
531
910
  status: 200,
532
911
  body: await changeDetail(root, params.changeId)
533
- })).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 }) => {
534
928
  await readChange(root, params.changeId);
535
929
  const rep = await changeReport(root, params.changeId);
536
930
  return {
@@ -554,10 +948,13 @@ function uiRoutes(r, ctx, deps = {}) {
554
948
  reasons: rep.reasons
555
949
  }
556
950
  };
557
- }).add("GET", "/api/changes/{changeId}/artifacts/{artifact}", async ({ params }) => {
951
+ }).add("GET", "/api/changes/{changeId}/artifacts/{artifact}", async ({ params, query }) => {
558
952
  await readChange(root, params.changeId);
559
- const rel = await artifactPath(root, params.changeId, params.artifact);
560
- 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(() => "");
561
958
  return {
562
959
  status: 200,
563
960
  body: {
@@ -567,15 +964,27 @@ function uiRoutes(r, ctx, deps = {}) {
567
964
  sha256: sha(content)
568
965
  }
569
966
  };
570
- }).add("PUT", "/api/changes/{changeId}/artifacts/{artifact}", async ({ params, body }) => {
967
+ }).add("PUT", "/api/changes/{changeId}/artifacts/{artifact}", async ({ params, body, query }) => {
571
968
  await readChange(root, params.changeId);
572
969
  const b = parseBody(api_exports.SaveArtifactRequest, body);
573
- 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
+ });
574
974
  const file = join2(changesDirOf(root), params.changeId, rel);
575
- const now = await readFile(file, "utf8");
975
+ const was = await readFile(file, "utf8").catch(() => void 0);
976
+ const now = was ?? "";
576
977
  if (sha(now) !== b.baseSha256)
577
978
  throw conflict(`${rel} changed since you opened it. Reload it, then make your edit again.`);
578
- 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);
579
988
  ctx.events.publish({
580
989
  type: "file.changed",
581
990
  path: `${changesRelOf(root)}/${params.changeId}/${rel}`
@@ -591,17 +1000,142 @@ function uiRoutes(r, ctx, deps = {}) {
591
1000
  };
592
1001
  }).add("GET", "/api/changes/{changeId}/tasks", async ({ params }) => {
593
1002
  await readChange(root, params.changeId);
594
- const files = await readChangeFiles(join2(changesDirOf(root), params.changeId));
1003
+ const change = params.changeId;
595
1004
  return {
596
1005
  status: 200,
597
- body: readChangeTasks(files).map((t) => ({
598
- id: t.id,
599
- repo: t.repo,
600
- description: t.title,
601
- done: t.done,
602
- ...t.stories[0] ? { story: t.stories[0] } : {},
603
- branch: `task/${params.changeId}/${t.id}`
604
- }))
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)
605
1139
  };
606
1140
  }).add("POST", "/api/changes/{changeId}/workspace", async ({ params, body }) => {
607
1141
  await readChange(root, params.changeId);
@@ -617,41 +1151,69 @@ function uiRoutes(r, ctx, deps = {}) {
617
1151
  const res = await run((io) => changeHandoff(root, params.changeId, { by: user, approve: true, ...b.repo ? { repo: b.repo } : {} }, io, changeDeps));
618
1152
  if (res.code !== 0)
619
1153
  throw refused(res);
620
- 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 }) };
621
1167
  }).add("POST", "/api/changes/{changeId}/sync", async ({ params, body }) => {
622
1168
  await readChange(root, params.changeId);
623
1169
  const b = parseBody(api_exports.SyncRequest, body);
624
1170
  const res = await run((io) => changeSync(root, params.changeId, { by: user, approve: true, ...b.repo ? { repo: b.repo } : {} }, io, changeDeps));
625
1171
  if (res.code === 2)
626
1172
  throw refused(res);
1173
+ const { appLines } = await import("./chunk-J2O4LFVI.js");
627
1174
  return {
628
1175
  status: 200,
629
1176
  body: {
630
1177
  ...await changeDetail(root, params.changeId),
631
- lines: [...res.out, ...res.err]
1178
+ lines: appLines([...res.out, ...res.err])
632
1179
  }
633
1180
  };
634
1181
  }).add("GET", "/api/changes/{changeId}/inputs", async ({ params }) => {
635
1182
  const c = await readChange(root, params.changeId);
636
- const { readChangeInputs } = await import("./chunk-6JCPI55B.js");
1183
+ const { readChangeInputs } = await import("./chunk-UDXWIPCB.js");
637
1184
  return {
638
1185
  status: 200,
639
1186
  body: api_exports.ChangeInputList.parse(await readChangeInputs(join2(changesDirOf(root), c.id)))
640
1187
  };
641
1188
  }).add("GET", "/api/changes/{changeId}/prototypes", async ({ params }) => {
642
1189
  const c = await readChange(root, params.changeId);
643
- const { changePrototypes } = await import("./chunk-Q4D5X7QB.js");
1190
+ const { changePrototypes } = await import("./chunk-VYOCPTUB.js");
644
1191
  return {
645
1192
  status: 200,
646
1193
  body: api_exports.ChangePrototypeList.parse(await changePrototypes(root, c.id))
647
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
+ };
648
1210
  }).add("POST", "/api/changes/{changeId}/inputs", async ({ params, body }) => {
649
1211
  const c = await readChange(root, params.changeId);
650
1212
  const b = parseBody(api_exports.AttachFileRequest, body);
651
1213
  const bytes = new Uint8Array(Buffer.from(b.content, "base64"));
652
1214
  if (bytes.length > MAX_ATTACH)
653
1215
  throw new HttpError(413, "The file is too large", "Attach a file of 15 MB or less.");
654
- const { attachFiles } = await import("./chunk-NE2W3OO7.js");
1216
+ const { attachFiles } = await import("./chunk-XQ3YZ4QW.js");
655
1217
  const r2 = await attachFiles(root, c.id, [{ name: b.name, bytes }], user);
656
1218
  if (!r2.added.length)
657
1219
  throw new HttpError(422, "Atticus can't attach that", r2.refused[0]?.why ?? "", {
@@ -673,15 +1235,42 @@ function uiRoutes(r, ctx, deps = {}) {
673
1235
  const b = parseBody(api_exports.StartRunRequest, body);
674
1236
  const before = new Set(await readdir(join2(runsDirOf(root), params.changeId)).catch(() => []));
675
1237
  let res;
676
- if (b.task)
677
- res = await run((io) => taskRun(root, params.changeId, b.task, {
678
- mode: b.runtime,
679
- by: user,
680
- ...b.autonomy ? {
681
- level: b.autonomy === "draft-and-pr" ? "pr" : b.autonomy === "plan-only" ? "plan" : "draft"
682
- } : {}
683
- }, io));
684
- 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")
685
1274
  res = await run((io) => planStories(root, params.changeId, { mode: b.runtime }, io));
686
1275
  else if (b.agent.endsWith("-critic"))
687
1276
  res = await run((io) => critic(root, params.changeId, { mode: b.runtime }, io));
@@ -732,32 +1321,110 @@ function uiRoutes(r, ctx, deps = {}) {
732
1321
  const before = await findRun(root, params.runId);
733
1322
  if (before.state === "validated" || before.state === "cancelled")
734
1323
  throw conflict(`Run ${params.runId} is already ${before.state}.`);
735
- 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
+ }
736
1333
  const after = await findRun(root, params.runId);
737
1334
  ctx.events.publish({ type: "run.changed", runId: after.runId, state: after.state });
738
- 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
+ };
739
1361
  }).add("POST", "/api/runs/{runId}/retry", async ({ params }) => {
740
1362
  const v = await findRun(root, params.runId);
741
1363
  if (v.state !== "failed")
742
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));
743
1368
  const file = join2(runsDirOf(root), v.change, v.runId, "run.json");
744
- const rec = JSON.parse(await readFile(file, "utf8"));
1369
+ const was = await readFile(file, "utf8");
1370
+ const rec = JSON.parse(was);
745
1371
  rec.state = "prepared";
746
1372
  rec.attempt = (rec.attempt ?? 1) + 1;
747
1373
  rec.errors = [];
748
- await writeFile(file, JSON.stringify(rec, null, 2) + "\n");
1374
+ await writeFileIfUnchanged(file, was, JSON.stringify(rec, null, 2) + "\n");
749
1375
  const after = await findRun(root, params.runId);
750
1376
  ctx.events.publish({ type: "run.changed", runId: after.runId, state: after.state });
751
1377
  return { status: 200, body: after };
752
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
+ }
753
1408
  const v = await findRun(root, params.runId);
754
1409
  if (["validated", "cancelled"].includes(v.state))
755
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.`);
756
1413
  const file = join2(runsDirOf(root), v.change, v.runId, "run.json");
757
- const rec = JSON.parse(await readFile(file, "utf8"));
758
- rec.state = "cancelled";
759
- rec.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
760
- 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
+ }
761
1428
  const after = await findRun(root, params.runId);
762
1429
  ctx.events.publish({ type: "run.changed", runId: after.runId, state: after.state });
763
1430
  return { status: 200, body: after };
@@ -840,8 +1507,10 @@ function uiRoutes(r, ctx, deps = {}) {
840
1507
  }
841
1508
 
842
1509
  // packages/cli/dist/ui-api-run.js
843
- var import_yaml = __toESM(require_dist(), 1);
844
- 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";
845
1514
  import { join as join3, resolve } from "node:path";
846
1515
  async function run2(f) {
847
1516
  const out = [];
@@ -862,11 +1531,11 @@ function parseBody2(schema, body) {
862
1531
  return r.data;
863
1532
  }
864
1533
  function jsonOf(r) {
865
- const text2 = r.out.join("\n").trim();
866
- if (!text2.startsWith("{") && !text2.startsWith("["))
1534
+ const text = r.out.join("\n").trim();
1535
+ if (!text.startsWith("{") && !text.startsWith("["))
867
1536
  return void 0;
868
1537
  try {
869
- return JSON.parse(text2);
1538
+ return JSON.parse(text);
870
1539
  } catch {
871
1540
  return void 0;
872
1541
  }
@@ -898,7 +1567,7 @@ async function latestFindings2(root, change) {
898
1567
  }
899
1568
  return out;
900
1569
  }
901
- async function filesUnder(dir, prefix = "") {
1570
+ async function filesUnder2(dir, prefix = "") {
902
1571
  const out = [];
903
1572
  let entries;
904
1573
  try {
@@ -909,9 +1578,9 @@ async function filesUnder(dir, prefix = "") {
909
1578
  for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
910
1579
  const rel = prefix ? `${prefix}/${e.name}` : e.name;
911
1580
  if (e.isDirectory())
912
- out.push(...await filesUnder(join3(dir, e.name), rel));
1581
+ out.push(...await filesUnder2(join3(dir, e.name), rel));
913
1582
  else
914
- 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 });
915
1584
  }
916
1585
  return out;
917
1586
  }
@@ -971,6 +1640,8 @@ async function findStep(root, id, change) {
971
1640
  throw notFound(`There is no step ${stepRef(id, change)}.`);
972
1641
  return s;
973
1642
  }
1643
+ var sha2 = (s) => createHash2("sha256").update(s).digest("hex");
1644
+ var snowText = (dir) => readFile2(join3(dir, "snow.md"), "utf8").catch(() => "");
974
1645
  var SyncEntries = external_exports.object({
975
1646
  requests: external_exports.array(external_exports.object({ key: external_exports.string(), id: external_exports.string() })).default([])
976
1647
  });
@@ -979,9 +1650,13 @@ async function changeRequests(root, change, templates, steps) {
979
1650
  const drafts = readSnowDrafts(await readChangeFiles(dir));
980
1651
  if (drafts.length === 0)
981
1652
  return [];
1653
+ const snow = {
1654
+ file: `${changesRelOf(root)}/${change}/snow.md`,
1655
+ sha256: sha2(await snowText(dir))
1656
+ };
982
1657
  let synced = [];
983
1658
  try {
984
- 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;
985
1660
  } catch {
986
1661
  }
987
1662
  return drafts.map((d) => {
@@ -1001,7 +1676,8 @@ async function changeRequests(root, change, templates, steps) {
1001
1676
  record,
1002
1677
  approvers: t?.approvers ?? [],
1003
1678
  fields,
1004
- 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
1005
1681
  };
1006
1682
  const entry = t ? synced.find((e) => e.key === `${t.id}|${record}`) : void 0;
1007
1683
  if (!entry)
@@ -1052,6 +1728,23 @@ function uiRunRoutes(r, ctx, deps = {}) {
1052
1728
  const nowDate = deps.now ? () => new Date(deps.now) : void 0;
1053
1729
  const pack = () => loadOrgPack(orgPackDirOf(root));
1054
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
+ };
1055
1748
  return r.add("GET", "/api/todo", async ({ query }) => {
1056
1749
  const change = strParam(query, "change");
1057
1750
  const op = await pack();
@@ -1126,7 +1819,8 @@ function uiRunRoutes(r, ctx, deps = {}) {
1126
1819
  }
1127
1820
  ctx.events.publish({ type: "file.changed", path: `steps/${params.stepId}` });
1128
1821
  const s = await findStep(root, params.stepId, b.change);
1129
- 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 } : {} } };
1130
1824
  }).add("GET", "/api/todo/{stepId}/run", async ({ params, query }) => {
1131
1825
  const s = await findStep(root, params.stepId, strParam(query, "change"));
1132
1826
  const op = await pack();
@@ -1199,12 +1893,44 @@ function uiRunRoutes(r, ctx, deps = {}) {
1199
1893
  throw refused2(res);
1200
1894
  ctx.events.publish({ type: "file.changed", path: `${params.changeId}/snow.md` });
1201
1895
  return { status: 200, body: { lines: res.out, toFill: res.code === 1 } };
1202
- }).add("POST", "/api/service-desk/{changeId}/submit", async ({ params }) => {
1203
- await readChange2(root, params.changeId);
1204
- const op = await pack();
1205
- const desk = deps.serviceDesk ?? manualAdapter("serviceDesk", {
1206
- store: fileStepStore(root),
1207
- root,
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
+ };
1928
+ }).add("POST", "/api/service-desk/{changeId}/submit", async ({ params }) => {
1929
+ await readChange2(root, params.changeId);
1930
+ const op = await pack();
1931
+ const desk = deps.serviceDesk ?? manualAdapter("serviceDesk", {
1932
+ store: fileStepStore(root),
1933
+ root,
1208
1934
  change: params.changeId,
1209
1935
  procedures: op.procedures,
1210
1936
  ...deps.now ? { now: () => deps.now } : {}
@@ -1293,7 +2019,7 @@ function uiRunRoutes(r, ctx, deps = {}) {
1293
2019
  } : {},
1294
2020
  conflicts,
1295
2021
  archive: { pass: archive.pass, blockers: archive.blockers, notes: archive.notes },
1296
- evidence: await filesUnder(changeFolder(root, id))
2022
+ evidence: await filesUnder2(changeFolder(root, id))
1297
2023
  }
1298
2024
  };
1299
2025
  }).add("POST", "/api/release/{changeId}/archive", async ({ params, body }) => {
@@ -1470,7 +2196,7 @@ function uiRunRoutes(r, ctx, deps = {}) {
1470
2196
  const { lessons, problems } = await lessonsFor(root);
1471
2197
  const sandbox = await isSandbox(root);
1472
2198
  const progress = sandbox ? await readProgress(root) : void 0;
1473
- const view2 = lessons.map((l) => {
2199
+ const view3 = lessons.map((l) => {
1474
2200
  const p = progress?.lessons[l.id];
1475
2201
  const done = p ? l.steps.filter((s) => p.steps[s.id]).length : 0;
1476
2202
  return {
@@ -1486,8 +2212,8 @@ function uiRunRoutes(r, ctx, deps = {}) {
1486
2212
  });
1487
2213
  const roles = [...new Set(lessons.map((l) => l.role))].map((role) => ({
1488
2214
  role,
1489
- lessons: view2.filter((v) => v.role === role).length,
1490
- 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
1491
2217
  }));
1492
2218
  const cur = progress?.current ? lessons.find((l) => l.id === progress.current) : void 0;
1493
2219
  const lp = cur ? progress.lessons[cur.id] : void 0;
@@ -1497,7 +2223,7 @@ function uiRunRoutes(r, ctx, deps = {}) {
1497
2223
  body: {
1498
2224
  sandbox,
1499
2225
  roles,
1500
- lessons: view2,
2226
+ lessons: view3,
1501
2227
  ...cur ? {
1502
2228
  current: {
1503
2229
  lesson: cur.id,
@@ -1537,7 +2263,8 @@ function uiRunRoutes(r, ctx, deps = {}) {
1537
2263
  return { status: 200, body: { ...out, ...hint ? { hint } : {} } };
1538
2264
  }).add("POST", "/api/learning/sandbox", async ({ body }) => {
1539
2265
  const b = parseBody2(api_exports.SandboxRequest, body);
1540
- 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() } : {}));
1541
2268
  const out = jsonOf(res);
1542
2269
  if (res.code !== 0 || !out)
1543
2270
  throw refused2(res);
@@ -1709,10 +2436,10 @@ function uiKnowRoutes(r, ctx, o = {}) {
1709
2436
  let checked;
1710
2437
  const machine = (fresh = false) => {
1711
2438
  if (fresh || !checked || Date.now() - checked.at > 3e5) {
1712
- const run3 = { at: Date.now(), checks: doctor(ctx.root) };
1713
- checked = run3;
1714
- run3.checks.catch(() => {
1715
- if (checked === run3)
2439
+ const run4 = { at: Date.now(), checks: doctor(ctx.root) };
2440
+ checked = run4;
2441
+ run4.checks.catch(() => {
2442
+ if (checked === run4)
1716
2443
  checked = void 0;
1717
2444
  });
1718
2445
  }
@@ -1720,7 +2447,7 @@ function uiKnowRoutes(r, ctx, o = {}) {
1720
2447
  };
1721
2448
  if (o.warm)
1722
2449
  void machine().catch(() => void 0);
1723
- const view2 = async (name, arg) => {
2450
+ const view3 = async (name, arg) => {
1724
2451
  const build = VIEWS[name];
1725
2452
  if (!build)
1726
2453
  throw notFound(`There is no view ${name}.`);
@@ -1729,11 +2456,11 @@ function uiKnowRoutes(r, ctx, o = {}) {
1729
2456
  const args = build(arg);
1730
2457
  if (!args)
1731
2458
  throw badRequest(`The ${name} view needs an id: /api/view/${name}/<id>.`);
1732
- const { main } = await import("./chunk-A7UPUQFY.js");
2459
+ const { main } = await import("./chunk-7FHNFCHK.js");
1733
2460
  const out = [];
1734
2461
  const err = [];
1735
2462
  const code = await main([...args, "--json", "--root", ctx.root], { out: (l) => out.push(l), err: (l) => err.push(l) }, { quiet: true });
1736
- const text3 = out.join("\n").trim();
2463
+ const text2 = out.join("\n").trim();
1737
2464
  if (code === 2) {
1738
2465
  const words = err.join("\n") || "The command refused the request.";
1739
2466
  if (scanFirst(words))
@@ -1742,18 +2469,24 @@ function uiKnowRoutes(r, ctx, o = {}) {
1742
2469
  }
1743
2470
  let body;
1744
2471
  try {
1745
- body = text3 ? JSON.parse(text3) : null;
2472
+ body = text2 ? JSON.parse(text2) : null;
1746
2473
  } catch {
1747
2474
  body = void 0;
1748
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
+ }
1749
2482
  if (body === void 0) {
1750
2483
  const words = [...err, ...out].join("\n") || "No output.";
1751
2484
  throw new HttpError(422, "Atticus can't show that yet", words, scanFirst(words) ? { notYet: true } : void 0);
1752
2485
  }
1753
2486
  return { status: 200, body: { code, data: body, problems: err } };
1754
2487
  };
1755
- const text2 = async (args) => {
1756
- const { main } = await import("./chunk-A7UPUQFY.js");
2488
+ const text = async (args) => {
2489
+ const { main } = await import("./chunk-7FHNFCHK.js");
1757
2490
  const out = [];
1758
2491
  const err = [];
1759
2492
  const code = await main([...args, "--root", ctx.root], { out: (l) => out.push(l), err: (l) => err.push(l) }, { quiet: true });
@@ -1771,7 +2504,7 @@ function uiKnowRoutes(r, ctx, o = {}) {
1771
2504
  throw badRequest("Some fields aren't right.", {
1772
2505
  errors: b.error.issues.map((i) => ({ path: i.path.join("."), message: i.message }))
1773
2506
  });
1774
- const { triageDecide } = await import("./chunk-W3BQAZ3Q.js");
2507
+ const { triageDecide } = await import("./chunk-ZKYRDV7A.js");
1775
2508
  const out = [];
1776
2509
  const err = [];
1777
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) });
@@ -1832,7 +2565,7 @@ function uiKnowRoutes(r, ctx, o = {}) {
1832
2565
  throw badRequest(`${focus} isn't an id.`);
1833
2566
  return {
1834
2567
  status: 200,
1835
- body: await text2(["diagram", "render", name, ...focus ? ["--focus", focus] : []])
2568
+ body: await text(["diagram", "render", name, ...focus ? ["--focus", focus] : []])
1836
2569
  };
1837
2570
  }).add("GET", "/api/diagram/{view}/graph", async ({ params, query }) => {
1838
2571
  const name = params.view;
@@ -1841,7 +2574,7 @@ function uiKnowRoutes(r, ctx, o = {}) {
1841
2574
  const focus = query.get("focus") ?? void 0;
1842
2575
  if (focus !== void 0 && !ID.test(focus))
1843
2576
  throw badRequest(`${focus} isn't an id.`);
1844
- const r2 = await text2([
2577
+ const r2 = await text([
1845
2578
  "diagram",
1846
2579
  "render",
1847
2580
  name,
@@ -1855,7 +2588,96 @@ function uiKnowRoutes(r, ctx, o = {}) {
1855
2588
  throw new HttpError(422, "Atticus can't show that yet", words, scanFirst(words) ? { notYet: true } : void 0);
1856
2589
  }
1857
2590
  return { status: 200, body: { view: JSON.parse(out) } };
1858
- }).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
+ });
1859
2681
  }
1860
2682
 
1861
2683
  // packages/cli/dist/ui-api-questions.js
@@ -1865,8 +2687,82 @@ function uiQuestionRoutes(r, ctx, deps = {}) {
1865
2687
  const state = strParam(query, "state");
1866
2688
  if (state !== void 0 && !QuestionState.safeParse(state).success)
1867
2689
  throw badRequest("state is open, answered or retired.");
1868
- const body = await listQuestions(ctx.root, state ? { state } : {});
2690
+ const mine = strParam(query, "mine");
2691
+ if (mine !== void 0 && mine !== "true")
2692
+ throw badRequest("mine is true, or left out.");
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;
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
+ },
2740
+ questions: routed.questions,
2741
+ problems: [...listed.problems, ...routed.problems],
2742
+ about: routed.about,
2743
+ ...routed.me ? { me: routed.me } : {},
2744
+ ...change ? { yourRoles: await personsRoles(ctx.root, change, ctx.user) } : {}
2745
+ };
1869
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
+ }
1870
2766
  }).add("GET", "/api/questions/{id}", async ({ params }) => {
1871
2767
  const q = await readQuestion(ctx.root, params.id).catch(() => {
1872
2768
  throw new HttpError(422, "Atticus can't read that question", `questions/${params.id}.yaml isn't a valid question.`);
@@ -1874,11 +2770,19 @@ function uiQuestionRoutes(r, ctx, deps = {}) {
1874
2770
  if (!q)
1875
2771
  throw notFound(`There is no question ${params.id}.`);
1876
2772
  return { status: 200, body: q };
2773
+ }).add("GET", "/api/questions/{id}/targets", async ({ params }) => {
2774
+ const q = await readQuestion(ctx.root, params.id).catch(() => void 0);
2775
+ if (!q)
2776
+ throw notFound(`There is no question ${params.id}.`);
2777
+ if (q.askedBy.step !== TARGETS_STEP_ID && q.askedBy.step !== TOPICS_STEP_ID)
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.`);
2779
+ const { pickerOf } = await import("./chunk-IFFRGC3N.js");
2780
+ return { status: 200, body: { picks: await pickerOf(ctx.root) } };
1877
2781
  }).add("GET", "/api/questions/{id}/evidence", async ({ params }) => {
1878
2782
  const q = await readQuestion(ctx.root, params.id).catch(() => void 0);
1879
2783
  if (!q)
1880
2784
  throw notFound(`There is no question ${params.id}.`);
1881
- const { evidenceExcerpts } = await import("./chunk-6JUBZBMW.js");
2785
+ const { evidenceExcerpts } = await import("./chunk-UFE36WJH.js");
1882
2786
  return {
1883
2787
  status: 200,
1884
2788
  body: {
@@ -1892,8 +2796,39 @@ function uiQuestionRoutes(r, ctx, deps = {}) {
1892
2796
  errors: b.error.issues.map((i) => ({ path: i.path.join("."), message: i.message }))
1893
2797
  });
1894
2798
  try {
1895
- const r2 = await answerQuestion(ctx.root, params.id, {
1896
- ...b.data,
2799
+ const { acceptProposal: acceptProposal2, target, addSystem, name, ...given } = b.data;
2800
+ let writeSystem;
2801
+ let system;
2802
+ if (target) {
2803
+ if (acceptProposal2 || given.option !== void 0 || given.text !== void 0)
2804
+ throw badRequest("Give target alone, not with option, text or acceptProposal.");
2805
+ const q0 = await readQuestion(ctx.root, params.id).catch(() => void 0);
2806
+ if (!q0)
2807
+ throw notFound(`There is no question ${params.id}.`);
2808
+ const { targetAnswerOf, offerLine } = await import("./chunk-IFFRGC3N.js");
2809
+ const t = await targetAnswerOf(ctx.root, q0, target, {
2810
+ ...addSystem ? { addSystem } : {},
2811
+ ...name ? { name } : {}
2812
+ });
2813
+ if ("problem" in t)
2814
+ throw badRequest([t.problem, ...t.offer ? [offerLine(q0.id, target, t.offer)] : []].join(" "), t.offer ? { offer: t.offer } : void 0);
2815
+ given.text = t.text;
2816
+ writeSystem = t.write;
2817
+ }
2818
+ const r2 = acceptProposal2 ? await acceptProposal(ctx.root, params.id, {
2819
+ ...given.scope ? { scope: given.scope } : {},
2820
+ ...given.repoType ? { repoType: given.repoType } : {},
2821
+ ...given.match ? { match: given.match } : {},
2822
+ by: ctx.user,
2823
+ at: now().toISOString()
2824
+ }) : await answerQuestion(ctx.root, params.id, {
2825
+ ...given,
2826
+ // Review item 14: the profile is written only once the answer is accepted.
2827
+ ...writeSystem ? {
2828
+ beforeWrite: async () => {
2829
+ system = await writeSystem();
2830
+ }
2831
+ } : {},
1897
2832
  by: ctx.user,
1898
2833
  at: now().toISOString()
1899
2834
  });
@@ -1902,7 +2837,8 @@ function uiQuestionRoutes(r, ctx, deps = {}) {
1902
2837
  body: {
1903
2838
  question: r2.question,
1904
2839
  rerun: r2.question.rerun,
1905
- ...r2.skill ? { skill: r2.skill } : {}
2840
+ ...r2.skill ? { skill: r2.skill } : {},
2841
+ ...system ? { system } : {}
1906
2842
  }
1907
2843
  };
1908
2844
  } catch (e) {
@@ -1940,15 +2876,15 @@ async function latestFindings3(root, change) {
1940
2876
  }
1941
2877
  var SEVERITY_ORDER = ["blocking", "gap", "risk", "idea"];
1942
2878
  var AT_RELEASE = /* @__PURE__ */ new Set(["release-ready", "released", "post-release-review"]);
1943
- async function acrossRows(root, view2) {
2879
+ async function acrossRows(root, view3) {
1944
2880
  const changes = await openChanges3(root);
1945
2881
  const rows = [];
1946
- const op = view2 === "requests" ? await loadOrgPack(orgPackDirOf(root)) : void 0;
1947
- 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(() => []) : [];
1948
2884
  for (const c of changes) {
1949
2885
  const base = { change: c.id, changeTitle: c.title, changeState: c.state };
1950
2886
  const dir = changeFolder(root, c.id);
1951
- switch (view2) {
2887
+ switch (view3) {
1952
2888
  case "findings":
1953
2889
  for (const f of await latestFindings3(root, c.id))
1954
2890
  rows.push({
@@ -2035,9 +2971,9 @@ async function acrossRows(root, view2) {
2035
2971
  }
2036
2972
  }
2037
2973
  }
2038
- if (view2 === "findings")
2974
+ if (view3 === "findings")
2039
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));
2040
- if (view2 === "releases")
2976
+ if (view3 === "releases")
2041
2977
  rows.sort((a, b) => Number(b.atRelease) - Number(a.atRelease));
2042
2978
  return rows;
2043
2979
  }
@@ -2127,13 +3063,13 @@ async function hopsFromTargetFlows(dir, mappings, interfaces) {
2127
3063
  function uiAcrossRoutes(r, ctx) {
2128
3064
  const { root } = ctx;
2129
3065
  return r.add("GET", "/api/across", async ({ query }) => {
2130
- const view2 = strParam(query, "view") ?? "";
2131
- if (!ACROSS_VIEWS.includes(view2))
3066
+ const view3 = strParam(query, "view") ?? "";
3067
+ if (!ACROSS_VIEWS.includes(view3))
2132
3068
  throw badRequest(`Say which view: ?view= one of ${ACROSS_VIEWS.join(", ")}.`);
2133
- const rows = await acrossRows(root, view2);
3069
+ const rows = await acrossRows(root, view3);
2134
3070
  return {
2135
3071
  status: 200,
2136
- body: { view: view2, changes: (await openChanges3(root)).length, rows }
3072
+ body: { view: view3, changes: (await openChanges3(root)).length, rows }
2137
3073
  };
2138
3074
  }).add("GET", "/api/changes/{changeId}/mappings", async ({ params }) => {
2139
3075
  try {
@@ -2146,8 +3082,9 @@ function uiAcrossRoutes(r, ctx) {
2146
3082
  }
2147
3083
 
2148
3084
  // packages/cli/dist/ui-api-graph.js
2149
- var import_yaml2 = __toESM(require_dist(), 1);
2150
- 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";
2151
3088
  import { join as join4 } from "node:path";
2152
3089
  var lensOf = (v, fallback) => {
2153
3090
  if (v === null)
@@ -2167,13 +3104,27 @@ var keyOf = (k) => {
2167
3104
  throw badRequest(`${k} isn't a view key. Use <lens>.<node>, e.g. architecture.estate.`);
2168
3105
  return k;
2169
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
+ }
2170
3112
  function uiGraphRoutes(r, ctx) {
2171
- 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 () => {
2172
3122
  const records = await catalogRecords(ctx.root);
2173
3123
  if (!records.length)
2174
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 });
2175
3125
  return buildEstate(records);
2176
3126
  };
3127
+ setTimeout(() => void estate().catch(() => void 0), 0).unref?.();
2177
3128
  const guard = (f) => {
2178
3129
  try {
2179
3130
  return f();
@@ -2183,11 +3134,13 @@ function uiGraphRoutes(r, ctx) {
2183
3134
  throw e;
2184
3135
  }
2185
3136
  };
2186
- 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) => {
2187
3140
  try {
2188
- 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")));
2189
3142
  if (!f.success)
2190
- 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.`);
2191
3144
  return f.data;
2192
3145
  } catch (e) {
2193
3146
  if (e.code === "ENOENT")
@@ -2211,46 +3164,100 @@ function uiGraphRoutes(r, ctx) {
2211
3164
  const lens = lensOf(query.get("lens"), "data");
2212
3165
  const est = await estate();
2213
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
+ };
2214
3221
  }).add("GET", "/api/graph/layout/{viewKey}", async ({ params }) => {
2215
- const view2 = keyOf(params.viewKey);
2216
- const f = await readLayout(view2);
3222
+ const view3 = keyOf(params.viewKey);
3223
+ const f = await readLayout(view3);
2217
3224
  return {
2218
3225
  status: 200,
2219
- body: { view: view2, saved: !!f, path: layoutPath(view2), positions: f?.positions ?? {} }
3226
+ body: { view: view3, saved: !!f, path: layoutRel(view3), positions: f?.positions ?? {} }
2220
3227
  };
2221
3228
  }).add("PUT", "/api/graph/layout/{viewKey}", async ({ params, body }) => {
2222
- const view2 = keyOf(params.viewKey);
3229
+ const view3 = keyOf(params.viewKey);
2223
3230
  const b = api_exports.PutLayoutRequest.safeParse(body ?? {});
2224
3231
  if (!b.success)
2225
3232
  throw badRequest("Some fields aren't right.", {
2226
3233
  errors: b.error.issues.map((i) => ({ path: i.path.join("."), message: i.message }))
2227
3234
  });
2228
3235
  const { lens, node, positions } = b.data;
2229
- if (view2 !== viewKey(lens, node) && view2 !== viewKey(lens, node, "focus"))
2230
- 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.`);
2231
3238
  if (Object.keys(positions).length > 5e3)
2232
3239
  throw badRequest("At most 5000 boxes per view.");
2233
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) }]));
2234
3241
  const file = DiagramLayout.parse({
2235
3242
  schemaVersion: LAYOUT_SCHEMA_VERSION,
2236
- view: view2,
3243
+ view: view3,
2237
3244
  lens,
2238
3245
  node,
2239
3246
  by: ctx.user,
2240
3247
  at: (/* @__PURE__ */ new Date()).toISOString(),
2241
3248
  positions: sorted
2242
3249
  });
2243
- await writeFileAtomic(join4(ctx.root, layoutPath(view2)), (0, import_yaml2.stringify)(file));
2244
- 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) });
2245
3252
  return {
2246
3253
  status: 200,
2247
- body: { view: view2, saved: true, path: layoutPath(view2), positions: file.positions }
3254
+ body: { view: view3, saved: true, path: layoutRel(view3), positions: file.positions }
2248
3255
  };
2249
3256
  });
2250
3257
  }
2251
3258
 
2252
3259
  // packages/cli/dist/ui-api-specs.js
2253
- import { stat as stat2 } from "node:fs/promises";
3260
+ import { stat as stat3 } from "node:fs/promises";
2254
3261
  import { join as join5 } from "node:path";
2255
3262
  var idOf2 = (v, what) => {
2256
3263
  if (!RecordId.safeParse(v).success)
@@ -2357,7 +3364,7 @@ function uiSpecRoutes(r, ctx) {
2357
3364
  });
2358
3365
  const deltasOf = async (change) => {
2359
3366
  const dir = join5(changesDirOf(root), change);
2360
- if (!await stat2(join5(dir, "change.yaml")).catch(() => void 0))
3367
+ if (!await stat3(join5(dir, "change.yaml")).catch(() => void 0))
2361
3368
  throw notFound(`There is no change ${change}.`);
2362
3369
  return readDeltas(dir);
2363
3370
  };
@@ -2381,7 +3388,7 @@ function uiSpecRoutes(r, ctx) {
2381
3388
  });
2382
3389
  r.add("GET", "/api/changes/{changeId}/conformance", async ({ params }) => {
2383
3390
  const change = idOf2(params.changeId, "change");
2384
- const { conformanceOf } = await import("./chunk-T6XQL73F.js");
3391
+ const { conformanceOf } = await import("./chunk-FRHD22WM.js");
2385
3392
  const c = await conformanceOf(root, change);
2386
3393
  if (!c)
2387
3394
  throw notFound(`There is no change ${change}.`);
@@ -2394,6 +3401,14 @@ function uiSpecRoutes(r, ctx) {
2394
3401
  throw notFound(`There is no change ${change}.`);
2395
3402
  return { status: 200, body: e };
2396
3403
  });
3404
+ r.add("GET", "/api/changes/{changeId}/impact", async ({ params }) => {
3405
+ const change = idOf2(params.changeId, "change");
3406
+ const { changeImpactOf } = await import("./chunk-2D7PWSOK.js");
3407
+ const i = await changeImpactOf(root, change);
3408
+ if (!i)
3409
+ throw notFound(`There is no change ${change}.`);
3410
+ return { status: 200, body: i };
3411
+ });
2397
3412
  r.add("GET", "/api/changes/{changeId}/map", async ({ params }) => {
2398
3413
  const change = idOf2(params.changeId, "change");
2399
3414
  const m = await changeMapOf(root, change);
@@ -2422,8 +3437,8 @@ function uiSpecRoutes(r, ctx) {
2422
3437
  }
2423
3438
 
2424
3439
  // packages/cli/dist/ui-api-knowledge.js
2425
- var import_yaml3 = __toESM(require_dist(), 1);
2426
- 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";
2427
3442
  import { join as join6 } from "node:path";
2428
3443
  var STALE_DAYS = 14;
2429
3444
  var SLUG = /^[a-z0-9][a-z0-9-]*$/;
@@ -2493,7 +3508,7 @@ function candidateView(c) {
2493
3508
  keepAs: keepAs(r.kind)
2494
3509
  };
2495
3510
  }
2496
- var exists = (p) => stat3(p).then(() => true, () => false);
3511
+ var exists = (p) => stat4(p).then(() => true, () => false);
2497
3512
  function capture() {
2498
3513
  const lines = [];
2499
3514
  const problems = [];
@@ -2583,8 +3598,8 @@ function uiKnowledgeRoutes(r, ctx, deps = {}) {
2583
3598
  const op = await loadOrgPack(orgPackDirOf(root));
2584
3599
  const platforms = [];
2585
3600
  for (const { platform } of op.profiles) {
2586
- const text2 = await readFile4(join6(proposalsDirOf(root), "orgpack", `${platform}.yaml`), "utf8").catch(() => void 0);
2587
- const data = text2 === void 0 ? void 0 : (0, import_yaml3.parse)(text2);
3601
+ const text = await readFile4(join6(proposalsDirOf(root), "orgpack", `${platform}.yaml`), "utf8").catch(() => void 0);
3602
+ const data = text === void 0 ? void 0 : (0, import_yaml4.parse)(text);
2588
3603
  const proposals = Array.isArray(data?.proposals) ? data.proposals.flatMap((p) => {
2589
3604
  const x = OrgPackProposal.safeParse(p);
2590
3605
  return x.success ? [x.data] : [];
@@ -2592,7 +3607,7 @@ function uiKnowledgeRoutes(r, ctx, deps = {}) {
2592
3607
  platforms.push({
2593
3608
  platform,
2594
3609
  file: `${proposalsRelOf(root)}/orgpack/${platform}.yaml`,
2595
- present: text2 !== void 0,
3610
+ present: text !== void 0,
2596
3611
  ...typeof data?.at === "string" ? { at: data.at } : {},
2597
3612
  proposals,
2598
3613
  command: `atticus orgpack draft --platform ${platform}`
@@ -2615,8 +3630,8 @@ function uiKnowledgeRoutes(r, ctx, deps = {}) {
2615
3630
  let snapshot;
2616
3631
  if (date) {
2617
3632
  const file = join6(knowledgeDirOf(root), "snapshots", w.id, date, SNAPSHOT_FILE);
2618
- const text2 = await readFile4(file, "utf8").catch(() => void 0);
2619
- const m = text2 === void 0 ? void 0 : SnapshotManifest.safeParse((0, import_yaml3.parse)(text2));
3633
+ const text = await readFile4(file, "utf8").catch(() => void 0);
3634
+ const m = text === void 0 ? void 0 : SnapshotManifest.safeParse((0, import_yaml4.parse)(text));
2620
3635
  if (m?.success)
2621
3636
  snapshot = {
2622
3637
  date,
@@ -2856,7 +3871,7 @@ function uiKnowledgeRoutes(r, ctx, deps = {}) {
2856
3871
  if (fileExists && !f)
2857
3872
  throw new HttpError(422, "Atticus can't read that file", `${changesRelOf(root)}/${change}/${NEEDS_FILE} isn't a valid needs file.`);
2858
3873
  const k = f ? await readKnowledge(root, { cacheRoot: root }) : void 0;
2859
- const view2 = (n) => ({
3874
+ const view3 = (n) => ({
2860
3875
  ...n,
2861
3876
  ...k?.reviews[n.item] ? { review: k.reviews[n.item] } : {}
2862
3877
  });
@@ -2865,7 +3880,7 @@ function uiKnowledgeRoutes(r, ctx, deps = {}) {
2865
3880
  file: `${changesRelOf(root)}/${change}/${NEEDS_FILE}`,
2866
3881
  present: !!f,
2867
3882
  ...f ? { updatedAt: f.updatedAt } : {},
2868
- needs: (f?.needs ?? []).map(view2),
3883
+ needs: (f?.needs ?? []).map(view3),
2869
3884
  considered: f?.considered ?? [],
2870
3885
  command: `atticus change needs ${change}`,
2871
3886
  slash: `/atticus-needs ${change}`
@@ -2930,7 +3945,8 @@ function uiKnowledgeRoutes(r, ctx, deps = {}) {
2930
3945
  const result = {
2931
3946
  knowledge: await appliesView(change),
2932
3947
  file: before.file,
2933
- 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"`
2934
3950
  };
2935
3951
  return { status: 200, body: result };
2936
3952
  }).add("GET", "/api/orgpack/draft", async () => ({ status: 200, body: await draftView() })).add("POST", "/api/orgpack/draft", async ({ body }) => {
@@ -2968,17 +3984,17 @@ function uiKnowledgeRoutes(r, ctx, deps = {}) {
2968
3984
  // packages/cli/dist/ui-api-runs.js
2969
3985
  import { readFile as readFile5 } from "node:fs/promises";
2970
3986
  import { join as join7 } from "node:path";
2971
- var import_yaml4 = __toESM(require_dist(), 1);
3987
+ var import_yaml5 = __toESM(require_dist(), 1);
2972
3988
  async function serverModel(root) {
2973
3989
  return (await configuredModel(root))?.type ?? null;
2974
3990
  }
2975
3991
  async function configuredModel(root) {
2976
- const text2 = await readFile5(join7(orgPackDirOf(root), "runtime.yaml"), "utf8").catch(() => void 0);
2977
- if (!text2)
3992
+ const text = await readFile5(join7(orgPackDirOf(root), "runtime.yaml"), "utf8").catch(() => void 0);
3993
+ if (!text)
2978
3994
  return null;
2979
3995
  let y;
2980
3996
  try {
2981
- y = (0, import_yaml4.parse)(text2);
3997
+ y = (0, import_yaml5.parse)(text);
2982
3998
  } catch {
2983
3999
  return null;
2984
4000
  }
@@ -2994,26 +4010,29 @@ async function configuredModel(root) {
2994
4010
  function uiModelRunRoutes(r, ctx, deps = {}) {
2995
4011
  const root = ctx.root;
2996
4012
  const runStep = deps.runStep ?? (async (args) => {
2997
- const { main } = await import("./chunk-A7UPUQFY.js");
4013
+ const { main } = await import("./chunk-7FHNFCHK.js");
2998
4014
  const out = [];
2999
4015
  const err = [];
3000
4016
  const code = await main([...args, "--root", root], { out: (l) => out.push(l), err: (l) => err.push(l) }, { quiet: true });
3001
4017
  return { code, out, err };
3002
4018
  });
3003
- return r.add("POST", "/api/runs", async ({ body }) => {
3004
- const args = body?.args;
3005
- if (!Array.isArray(args) || args.length < 2 || !args.every((a) => typeof a === "string" && a.trim() !== ""))
3006
- throw badRequest(`args is the step's command words and arguments, for example ["scan", "survey", "forge-ledger"].`);
3007
- const words = args;
3008
- if (!isHandoffStep(words[0], words[1]))
3009
- throw badRequest(`atticus ${words.slice(0, 2).join(" ")} isn't an AI step; the app starts only those.`);
3010
- const option = words.find((a) => a.startsWith("-"));
3011
- if (option)
3012
- 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);
3013
4029
  const configured = await configuredModel(root);
3014
4030
  const model = configured?.type;
3015
4031
  if (!model)
3016
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.`);
3017
4036
  const job = ctx.jobs.start("step", async (c) => {
3018
4037
  c.progress(0, 1, `Running atticus ${words.join(" ")} with ${model}`);
3019
4038
  const before = new Set((await listRuns(root, { limit: 1e5 })).map((x) => x.runId));
@@ -3054,136 +4073,1054 @@ function uiModelRunRoutes(r, ctx, deps = {}) {
3054
4073
  return { status: 200, body: t };
3055
4074
  });
3056
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
+ }
3057
4098
 
3058
- // packages/cli/dist/ui-api-history.js
3059
- function uiHistoryRoutes(r, ctx) {
3060
- const root = ctx.root;
3061
- return r.add("GET", "/api/history", async ({ query }) => {
3062
- const limitText = query.get("limit");
3063
- const limit = limitText === null ? 200 : Number(limitText);
3064
- if (!Number.isInteger(limit) || limit < 1)
3065
- throw badRequest("limit is a whole number of 1 or more.");
3066
- const opt = (k) => {
3067
- const v = query.get(k)?.trim();
3068
- return v ? v : void 0;
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: [] }]
3069
4121
  };
3070
- const change = opt("change");
3071
- const session = opt("session");
3072
- const filter = opt("q");
3073
- const body = await listHistory(root, {
3074
- ...change ? { change } : {},
3075
- ...session ? { session } : {},
3076
- ...filter ? { filter } : {},
3077
- limit
3078
- });
3079
- return { status: 200, body };
3080
- }).add("GET", "/api/history/{historyId}", async ({ params }) => {
3081
- const e = await historyEntry(root, params.historyId);
3082
- if (!e)
3083
- throw notFound(`There is no record ${params.historyId}.`);
3084
- return { status: 200, body: e };
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)
3085
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
+ }
3086
4232
  }
3087
-
3088
- // packages/cli/dist/ui-api-ai.js
3089
- var import_yaml5 = __toESM(require_dist(), 1);
3090
- import { existsSync } from "node:fs";
3091
- import { readFile as readFile6 } from "node:fs/promises";
3092
- import { join as join8, relative, sep } from "node:path";
3093
- var unprocessable = (detail) => new HttpError(422, "The settings aren't valid", detail);
3094
- var ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,127}$/;
3095
- var FIELDS = ["ai", "modes", "assistants", "models", "budgets"];
3096
- function uiAiRoutes(r, ctx, deps = {}) {
3097
- const root = ctx.root;
3098
- const env = deps.env ?? process.env;
3099
- const keys = () => deps.keys ?? defaultKeyStore();
3100
- const file = () => join8(orgPackDirOf(root), "runtime.yaml");
3101
- const rel = () => relative(root, file()).split(sep).join("/");
3102
- const build = async (op, type, settings) => {
3103
- if (deps.provider)
3104
- return deps.provider(type, settings);
3105
- const make = await providerFactoryFor(op, type);
3106
- return make?.(settings);
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();
3107
4259
  };
3108
- return r.add("GET", "/api/settings/ai", async () => {
3109
- const op = await loadOrgPack(orgPackDirOf(root));
3110
- const rt = op.runtime;
3111
- const model = rt.adapters.model;
3112
- const keyEnv = typeof model?.settings.keyEnv === "string" ? model.settings.keyEnv : void 0;
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;
3113
4284
  return {
3114
4285
  status: 200,
3115
4286
  body: {
3116
- // The assistant atticus probe measured here, when one was (ADR 0124 addendum A-probe).
3117
- runs: whatRunsWhere(rt, { probes: (await readProbes(root)).results }),
3118
- ai: rt.ai,
3119
- modes: rt.modes,
3120
- assistants: ASSISTANTS.map((id) => ({
3121
- id,
3122
- name: ASSISTANT_FORMATS[id].name,
3123
- allowed: rt.assistants.allowed.includes(id),
3124
- default: rt.assistants.default === id
3125
- })),
3126
- providers: [
3127
- ...Object.entries(PROVIDER_SETTINGS).map(([type, settings]) => ({
3128
- type,
3129
- settings: [...settings]
3130
- })),
3131
- // The org pack's model-provider plugins, with what each takes (ADR 0121 item 4).
3132
- ...op.modelProviders.map(({ manifest: m }) => ({
3133
- type: m.id,
3134
- settings: [
3135
- ...COMMON_SETTINGS,
3136
- ...m.auth === "none" ? [] : ["keyEnv"],
3137
- ...m.settings
3138
- ],
3139
- plugin: true,
3140
- pinned: !!m.sha256
3141
- }))
3142
- ],
3143
- model: model ? { type: model.type, settings: model.settings } : null,
3144
- // Whether the key's variable is set here, by name: its value is never read out.
3145
- keySet: keyEnv ? !!env[keyEnv]?.trim() || !!keys().get(keyEnv) : false,
3146
- models: rt.models,
3147
- budgets: rt.budgets,
3148
- path: rel()
4287
+ run: view3,
4288
+ allowed: a.allowed,
4289
+ ...a.allowed ? {} : { why: NOT_ALLOWED },
4290
+ ...a.model ? { model: a.model } : {}
3149
4291
  }
3150
4292
  };
3151
- }).add("PUT", "/api/settings/ai", async ({ body }) => {
3152
- const b = body ?? {};
3153
- const text2 = existsSync(file()) ? await readFile6(file(), "utf8") : void 0;
3154
- const raw = text2 !== void 0 ? (0, import_yaml5.parse)(text2) ?? {} : structuredClone(DEFAULT_RUNTIME);
3155
- for (const f of FIELDS)
3156
- if (b[f] !== void 0)
3157
- raw[f] = b[f];
3158
- if (b.model !== void 0) {
3159
- const adapters = { ...raw.adapters ?? {} };
3160
- if (b.model === null)
3161
- delete adapters.model;
3162
- else {
3163
- const m = b.model;
3164
- const settings = Object.fromEntries(Object.entries(m.settings ?? {}).filter(([, v]) => v !== "" && v !== void 0));
3165
- for (const [name, v] of Object.entries(settings))
3166
- for (const one of Array.isArray(v) ? v : [v])
3167
- if (typeof one === "string" && looksLikeKey(one))
3168
- throw unprocessable(`${name} looks like a key. A key never goes in runtime.yaml: keep it in an environment variable (or the OS's credential store) and give that variable's name as keyEnv.`);
3169
- if (settings.keyEnv !== void 0 && !ENV_NAME.test(String(settings.keyEnv)))
3170
- throw unprocessable(`keyEnv is the name of an environment variable, such as ATTICUS_MODEL_KEY.`);
3171
- adapters.model = { type: m.type, settings };
3172
- }
3173
- raw.adapters = adapters;
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;
3174
4314
  }
3175
- const checked = Runtime.safeParse(raw);
3176
- if (!checked.success)
3177
- throw unprocessable(checked.error.issues.map((i) => `${i.path.join(".") || "runtime.yaml"}: ${i.message}`).join("; "));
3178
- const header = (text2 ?? "").split(/\r?\n/).filter((l, i, a) => a.slice(0, i + 1).every((x) => x.startsWith("#")));
3179
- await writeFileAtomic(file(), `${header.length ? `${header.join("\n")}
3180
- ` : ""}${(0, import_yaml5.stringify)(raw, { lineWidth: 0 })}`);
3181
- return {
3182
- status: 200,
3183
- body: {
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
+ }
4970
+
4971
+ // packages/cli/dist/ui-api-history.js
4972
+ function uiHistoryRoutes(r, ctx) {
4973
+ const root = ctx.root;
4974
+ return r.add("GET", "/api/history", async ({ query }) => {
4975
+ const limitText = query.get("limit");
4976
+ const limit = limitText === null ? 200 : Number(limitText);
4977
+ if (!Number.isInteger(limit) || limit < 1)
4978
+ throw badRequest("limit is a whole number of 1 or more.");
4979
+ const opt = (k) => {
4980
+ const v = query.get(k)?.trim();
4981
+ return v ? v : void 0;
4982
+ };
4983
+ const change = opt("change");
4984
+ const session = opt("session");
4985
+ const filter = opt("q");
4986
+ const body = await listHistory(root, {
4987
+ ...change ? { change } : {},
4988
+ ...session ? { session } : {},
4989
+ ...filter ? { filter } : {},
4990
+ limit
4991
+ });
4992
+ return { status: 200, body };
4993
+ }).add("GET", "/api/history/{historyId}", async ({ params }) => {
4994
+ const e = await historyEntry(root, params.historyId);
4995
+ if (!e)
4996
+ throw notFound(`There is no record ${params.historyId}.`);
4997
+ return { status: 200, body: e };
4998
+ });
4999
+ }
5000
+
5001
+ // packages/cli/dist/ui-api-ai.js
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";
5006
+ var unprocessable = (detail) => new HttpError(422, "The settings aren't valid", detail);
5007
+ var ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,127}$/;
5008
+ var FIELDS = ["ai", "modes", "assistants", "models", "budgets", "decide"];
5009
+ function uiAiRoutes(r, ctx, deps = {}) {
5010
+ const root = ctx.root;
5011
+ const env = deps.env ?? process.env;
5012
+ const keys = () => deps.keys ?? defaultKeyStore();
5013
+ const file = () => join10(orgPackDirOf(root), "runtime.yaml");
5014
+ const rel = () => relative(root, file()).split(sep).join("/");
5015
+ const build = async (op, type, settings) => {
5016
+ if (deps.provider)
5017
+ return deps.provider(type, settings);
5018
+ const make = await providerFactoryFor(op, type);
5019
+ return make?.(settings);
5020
+ };
5021
+ return r.add("GET", "/api/settings/ai", async () => {
5022
+ const op = await loadOrgPack(orgPackDirOf(root));
5023
+ const rt = op.runtime;
5024
+ const model = rt.adapters.model;
5025
+ const keyEnv = typeof model?.settings.keyEnv === "string" ? model.settings.keyEnv : void 0;
5026
+ return {
5027
+ status: 200,
5028
+ body: {
5029
+ // The assistant atticus probe measured here, when one was (ADR 0124 addendum A-probe).
5030
+ runs: whatRunsWhere(rt, { probes: (await readProbes(root)).results }),
5031
+ ai: rt.ai,
5032
+ modes: rt.modes,
5033
+ assistants: ASSISTANTS.map((id) => ({
5034
+ id,
5035
+ name: ASSISTANT_FORMATS[id].name,
5036
+ allowed: rt.assistants.allowed.includes(id),
5037
+ default: rt.assistants.default === id
5038
+ })),
5039
+ providers: [
5040
+ ...Object.entries(PROVIDER_SETTINGS).map(([type, settings]) => ({
5041
+ type,
5042
+ settings: [...settings]
5043
+ })),
5044
+ // The org pack's model-provider plugins, with what each takes (ADR 0121 item 4).
5045
+ ...op.modelProviders.map(({ manifest: m }) => ({
5046
+ type: m.id,
5047
+ settings: [
5048
+ ...COMMON_SETTINGS,
5049
+ ...m.auth === "none" ? [] : ["keyEnv"],
5050
+ ...m.settings
5051
+ ],
5052
+ plugin: true,
5053
+ pinned: !!m.sha256
5054
+ }))
5055
+ ],
5056
+ model: model ? { type: model.type, settings: model.settings } : null,
5057
+ // Whether the key's variable is set here, by name: its value is never read out.
5058
+ keySet: keyEnv ? !!env[keyEnv]?.trim() || !!keys().get(keyEnv) : false,
5059
+ models: rt.models,
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,
5064
+ path: rel()
5065
+ }
5066
+ };
5067
+ }).add("PUT", "/api/settings/ai", async ({ body }) => {
5068
+ const b = body ?? {};
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);
5071
+ for (const f of FIELDS)
5072
+ if (b[f] !== void 0)
5073
+ raw[f] = b[f];
5074
+ if (b.model !== void 0) {
5075
+ const adapters = { ...raw.adapters ?? {} };
5076
+ if (b.model === null)
5077
+ delete adapters.model;
5078
+ else {
5079
+ const m = b.model;
5080
+ const settings = Object.fromEntries(Object.entries(m.settings ?? {}).filter(([, v]) => v !== "" && v !== void 0));
5081
+ for (const [name, v] of Object.entries(settings))
5082
+ for (const one of Array.isArray(v) ? v : [v])
5083
+ if (typeof one === "string" && looksLikeKey(one))
5084
+ throw unprocessable(`${name} looks like a key. A key never goes in runtime.yaml: keep it in an environment variable (or the OS's credential store) and give that variable's name as keyEnv.`);
5085
+ if (settings.keyEnv !== void 0 && !ENV_NAME.test(String(settings.keyEnv)))
5086
+ throw unprocessable(`keyEnv is the name of an environment variable, such as ATTICUS_MODEL_KEY.`);
5087
+ adapters.model = { type: m.type, settings };
5088
+ }
5089
+ raw.adapters = adapters;
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;
5101
+ const checked = Runtime.safeParse(raw);
5102
+ if (!checked.success)
5103
+ throw unprocessable(checked.error.issues.map((i) => `${i.path.join(".") || "runtime.yaml"}: ${i.message}`).join("; "));
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 ?? "")));
5118
+ return {
5119
+ status: 200,
5120
+ body: {
3184
5121
  ok: true,
3185
5122
  path: rel(),
3186
- 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.`
3187
5124
  }
3188
5125
  };
3189
5126
  }).add("POST", "/api/settings/ai/key", async ({ body }) => {
@@ -3230,7 +5167,7 @@ function uiDocumentRoutes(r, ctx) {
3230
5167
  const root = ctx.root;
3231
5168
  const changed = (path) => ctx.events.publish({ type: "file.changed", path });
3232
5169
  return r.add("GET", "/api/knowledge/documents", async () => {
3233
- const { listDocuments } = await import("./chunk-ONLS5QA5.js");
5170
+ const { listDocuments } = await import("./chunk-URIKUID7.js");
3234
5171
  return { status: 200, body: api_exports.DocumentList.parse(await listDocuments(root)) };
3235
5172
  }).add("POST", "/api/knowledge/documents", async ({ body }) => {
3236
5173
  const b = api_exports.AttachFileRequest.safeParse(body ?? {});
@@ -3239,7 +5176,7 @@ function uiDocumentRoutes(r, ctx) {
3239
5176
  const bytes = new Uint8Array(Buffer.from(b.data.content, "base64"));
3240
5177
  if (bytes.length > MAX_FILE)
3241
5178
  throw new HttpError(413, "The file is too large", "Add a file of 15 MB or less.");
3242
- const { addToInbox } = await import("./chunk-W3BQAZ3Q.js");
5179
+ const { addToInbox } = await import("./chunk-ZKYRDV7A.js");
3243
5180
  const res = await addToInbox(root, [{ name: b.data.name, bytes }]);
3244
5181
  if (!res.added.length)
3245
5182
  throw new HttpError(422, "Atticus can't add that", res.refused[0]?.why ?? "", {
@@ -3249,7 +5186,7 @@ function uiDocumentRoutes(r, ctx) {
3249
5186
  changed(a.path);
3250
5187
  return { status: 201, body: api_exports.AddDocumentResult.parse(res) };
3251
5188
  }, { maxBody: Math.ceil(MAX_FILE * 4 / 3) + 64 * 1024 }).add("POST", "/api/changes/{changeId}/inputs/{name}/promote", async ({ params }) => {
3252
- const { AttachError, promoteChangeInput } = await import("./chunk-NE2W3OO7.js");
5189
+ const { AttachError, promoteChangeInput } = await import("./chunk-XQ3YZ4QW.js");
3253
5190
  try {
3254
5191
  const res = await promoteChangeInput(root, params.changeId, params.name);
3255
5192
  changed(res.path);
@@ -3266,13 +5203,13 @@ function uiDocumentRoutes(r, ctx) {
3266
5203
  function uiRepoRoutes(r, ctx) {
3267
5204
  const root = ctx.root;
3268
5205
  return r.add("GET", "/api/repos/branches", async () => {
3269
- const { repoBranches } = await import("./chunk-64EQYWCA.js");
5206
+ const { repoBranches } = await import("./chunk-YBOVNMHC.js");
3270
5207
  return { status: 200, body: api_exports.RepoBranchesList.parse(await repoBranches(root)) };
3271
5208
  }).add("GET", "/api/repos/compare", async ({ query }) => {
3272
5209
  const branch = query.get("branch")?.trim() ?? "";
3273
5210
  if (!branch)
3274
5211
  throw badRequest("Say which branch: ?branch=<name>.");
3275
- const { compareBranch } = await import("./chunk-64EQYWCA.js");
5212
+ const { compareBranch } = await import("./chunk-YBOVNMHC.js");
3276
5213
  const c = await compareBranch(root, branch);
3277
5214
  if (!c)
3278
5215
  throw notFound(`${branch} hasn't been scanned beside the baseline yet.`);
@@ -3281,7 +5218,7 @@ function uiRepoRoutes(r, ctx) {
3281
5218
  const b = api_exports.EditRepoBranchesRequest.safeParse(body ?? {});
3282
5219
  if (!b.success)
3283
5220
  throw badRequest("Send defaultBranch and branches as branch names.");
3284
- const { editRepoBranches, RepoEditError } = await import("./chunk-64EQYWCA.js");
5221
+ const { editRepoBranches, RepoEditError } = await import("./chunk-YBOVNMHC.js");
3285
5222
  try {
3286
5223
  const file = await editRepoBranches(root, params.repoId, b.data);
3287
5224
  ctx.events.publish({ type: "file.changed", path: file });
@@ -3294,14 +5231,56 @@ function uiRepoRoutes(r, ctx) {
3294
5231
  };
3295
5232
  } catch (e) {
3296
5233
  if (e instanceof RepoEditError)
3297
- 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);
3298
5277
  throw e;
3299
5278
  }
3300
5279
  }).add("PUT", "/api/repos/{repoId}/description", async ({ params, body }) => {
3301
5280
  const b = api_exports.EditRepoDescriptionRequest.safeParse(body ?? {});
3302
5281
  if (!b.success)
3303
5282
  throw badRequest("Send what the repo is as text, or accept: true to confirm the draft, or reject: true to drop it.");
3304
- const { editRepoDescription, RepoEditError } = await import("./chunk-64EQYWCA.js");
5283
+ const { editRepoDescription, RepoEditError } = await import("./chunk-YBOVNMHC.js");
3305
5284
  try {
3306
5285
  const r2 = await editRepoDescription(root, params.repoId, b.data);
3307
5286
  ctx.events.publish({ type: "file.changed", path: r2.file });
@@ -3315,7 +5294,7 @@ function uiRepoRoutes(r, ctx) {
3315
5294
  };
3316
5295
  } catch (e) {
3317
5296
  if (e instanceof RepoEditError)
3318
- 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);
3319
5298
  throw e;
3320
5299
  }
3321
5300
  });
@@ -3324,11 +5303,11 @@ function uiRepoRoutes(r, ctx) {
3324
5303
  // packages/cli/dist/ui-api-cockpit.js
3325
5304
  function uiCockpitRoutes(r, ctx) {
3326
5305
  return r.add("GET", "/api/changes/{changeId}/cockpit", async ({ params }) => {
3327
- const { cockpit } = await import("./chunk-EDS2L2LD.js");
5306
+ const { cockpit } = await import("./chunk-6EXDI5GI.js");
3328
5307
  try {
3329
5308
  return {
3330
5309
  status: 200,
3331
- 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 }))
3332
5311
  };
3333
5312
  } catch (e) {
3334
5313
  if (e instanceof Error && e.name === "ChangeFileError")
@@ -3336,17 +5315,17 @@ function uiCockpitRoutes(r, ctx) {
3336
5315
  throw e;
3337
5316
  }
3338
5317
  }).add("GET", "/api/past-changes", async ({ query }) => {
3339
- const { changeAboutText, similarChanges } = await import("./chunk-HVUPKCGJ.js");
5318
+ const { changeAboutText, similarChanges } = await import("./chunk-BC6KKZ67.js");
3340
5319
  const change = query.get("change");
3341
- const text2 = change ? await changeAboutText(ctx.root, change) : query.get("text")?.trim();
3342
- if (change && !text2)
5320
+ const text = change ? await changeAboutText(ctx.root, change) : query.get("text")?.trim();
5321
+ if (change && !text)
3343
5322
  throw notFound(`There is no change ${change}.`);
3344
- if (!text2)
5323
+ if (!text)
3345
5324
  throw badRequest("Say what the new change is about: ?text=<its title or brief>, or ?change=<a change's id>.");
3346
5325
  const limit = Number(query.get("limit") ?? 5);
3347
5326
  return {
3348
5327
  status: 200,
3349
- body: api_exports.SimilarChanges.parse(await similarChanges(ctx.root, text2.slice(0, 2e4), {
5328
+ body: api_exports.SimilarChanges.parse(await similarChanges(ctx.root, text.slice(0, 2e4), {
3350
5329
  limit: Number.isInteger(limit) && limit > 0 && limit <= 20 ? limit : 5
3351
5330
  }))
3352
5331
  };
@@ -3354,11 +5333,11 @@ function uiCockpitRoutes(r, ctx) {
3354
5333
  const b = api_exports.AttachPastChangeRequest.safeParse(body ?? {});
3355
5334
  if (!b.success)
3356
5335
  throw badRequest("Say which archived change: { like: <its folder or id> }.");
3357
- const { attachPastChange, findPastChange } = await import("./chunk-HVUPKCGJ.js");
5336
+ const { attachPastChange, findPastChange } = await import("./chunk-BC6KKZ67.js");
3358
5337
  const folder = await findPastChange(ctx.root, b.data.like);
3359
5338
  if (!folder)
3360
5339
  throw notFound(`There is no archived change ${b.data.like}.`);
3361
- const { AttachError } = await import("./chunk-NE2W3OO7.js");
5340
+ const { AttachError } = await import("./chunk-XQ3YZ4QW.js");
3362
5341
  try {
3363
5342
  const name = await attachPastChange(ctx.root, params.changeId, folder, ctx.user);
3364
5343
  ctx.events.publish({ type: "file.changed", path: `inputs/${name}` });
@@ -3374,396 +5353,521 @@ function uiCockpitRoutes(r, ctx) {
3374
5353
  });
3375
5354
  }
3376
5355
 
3377
- // packages/cli/dist/ui-api-trust.js
3378
- var import_yaml6 = __toESM(require_dist(), 1);
3379
- import { readdir as readdir3, readFile as readFile7, stat as stat4 } from "node:fs/promises";
3380
- import { join as join9, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
3381
- var SHA = /^[0-9a-f]{7,64}$/;
3382
- var FULL_SHA = /^[0-9a-f]{40,64}$/;
3383
- var FACT_BY = /* @__PURE__ */ new Set(["assistant", "reader", "person"]);
3384
- var UNKNOWN_SHOWN = 50;
3385
- var firstLine = (e) => String(e.message ?? e).split("\n")[0];
3386
- var text = (p) => readFile7(p, "utf8").catch(() => void 0);
3387
- async function headFromFiles(dir) {
3388
- let git = join9(dir, ".git");
3389
- const st = await stat4(git).catch(() => void 0);
3390
- if (!st)
3391
- return void 0;
3392
- if (st.isFile()) {
3393
- const m = /^gitdir:\s*(.+)$/m.exec(await text(git) ?? "");
3394
- if (!m)
3395
- return void 0;
3396
- git = resolve2(dir, m[1].trim());
3397
- }
3398
- const common = (await text(join9(git, "commondir")))?.trim();
3399
- const commonDir = common ? resolve2(git, common) : git;
3400
- const head = (await text(join9(git, "HEAD")) ?? "").trim();
3401
- if (FULL_SHA.test(head))
3402
- return head;
3403
- const ref = /^ref:\s*(refs\/[A-Za-z0-9._/-]+)$/.exec(head)?.[1];
3404
- if (!ref || ref.split("/").includes(".."))
3405
- return void 0;
3406
- for (const d of [git, commonDir]) {
3407
- const loose = (await text(join9(d, ...ref.split("/"))) ?? "").trim();
3408
- if (FULL_SHA.test(loose))
3409
- return loose;
3410
- }
3411
- for (const line of (await text(join9(commonDir, "packed-refs")) ?? "").split(/\r?\n/)) {
3412
- const [sha2, name] = line.split(" ");
3413
- if (name === ref && sha2 && FULL_SHA.test(sha2))
3414
- return sha2;
3415
- }
3416
- return void 0;
5356
+ // packages/cli/dist/ui-api-audit.js
5357
+ var REPO3 = /^[a-z0-9][a-z0-9._-]{0,99}$/i;
5358
+ var repoOf = (v) => {
5359
+ if (!REPO3.test(v))
5360
+ throw badRequest(`${JSON.stringify(v)} isn't a repo name.`);
5361
+ return v;
5362
+ };
5363
+ async function itemsWithLines(root, repo, items) {
5364
+ const text = await auditLines(root, repo, items);
5365
+ return items.map((i) => ({
5366
+ ...i,
5367
+ lines: i.cites.map((c) => {
5368
+ const t = text.get(c);
5369
+ return { cite: c, ...t ? { text: t.length > 300 ? `${t.slice(0, 297)}...` : t } : {} };
5370
+ })
5371
+ }));
3417
5372
  }
3418
- function reportOf(raw) {
3419
- const r = raw;
3420
- if (!r || typeof r.accepted !== "number" || typeof r.rejected !== "number")
3421
- return "not a verifier's report: accepted and rejected are missing";
3422
- const list = (v) => Array.isArray(v) ? v.map(String) : [];
3423
- const unknown = list(r.coverage?.unknown);
3424
- return {
3425
- accepted: r.accepted,
3426
- rejected: r.rejected,
3427
- redacted: typeof r.redacted === "number" ? r.redacted : 0,
3428
- coverage: {
3429
- ratio: typeof r.coverage?.ratio === "number" ? r.coverage.ratio : 0,
3430
- read: typeof r.coverage?.read === "number" ? r.coverage.read : 0,
3431
- unknown: unknown.length,
3432
- excluded: list(r.coverage?.excluded).length
3433
- },
3434
- unknownFiles: unknown.slice(0, UNKNOWN_SHOWN),
3435
- ...typeof r.commit === "string" && SHA.test(r.commit) ? { commit: r.commit } : {}
3436
- };
5373
+ var ofDraw = (audit, drawn) => {
5374
+ const byId = new Map(audit.items.map((i) => [i.id, i]));
5375
+ return drawn.map((d) => byId.get(d.id) ?? d);
5376
+ };
5377
+ function uiAuditRoutes(r, ctx, deps = {}) {
5378
+ const now = deps.now ?? (() => /* @__PURE__ */ new Date());
5379
+ return r.add("GET", "/api/facts/{repo}/audit", async ({ params, query }) => {
5380
+ const repo = repoOf(params.repo);
5381
+ const seed = query.get("seed") || void 0;
5382
+ const sizeText = query.get("size");
5383
+ const size = sizeText ? Number(sizeText) : void 0;
5384
+ if (size !== void 0 && (!Number.isInteger(size) || size < 1 || size > 100))
5385
+ throw badRequest("size is a whole number from 1 to 100.");
5386
+ const p = await prepareAudit(ctx.root, repo, {
5387
+ ...seed ? { seed } : {},
5388
+ ...size ? { size } : {},
5389
+ now
5390
+ });
5391
+ const command = (s, n) => `atticus verify audit ${repo} --seed ${s} --size ${n}`;
5392
+ if ("refused" in p) {
5393
+ const body2 = {
5394
+ repo,
5395
+ file: `${factsRelOf(ctx.root)}/${repo}.audit.yaml`,
5396
+ seed: seed ?? "",
5397
+ size: size ?? 10,
5398
+ items: [],
5399
+ refused: p.refused,
5400
+ ...p.unverified ? { unverified: p.unverified } : {},
5401
+ command: `atticus verify audit ${repo}`
5402
+ };
5403
+ return { status: 200, body: body2 };
5404
+ }
5405
+ const precision = await precisionOf(ctx.root, repo, p.audit, p.facts);
5406
+ const commit = [...new Set(p.facts.map((f) => f.commit))].sort()[0];
5407
+ const body = {
5408
+ repo,
5409
+ file: p.fileRel,
5410
+ seed: p.seed,
5411
+ size: p.size,
5412
+ defaultSeed: defaultAuditSeed(repo, commit),
5413
+ items: await itemsWithLines(ctx.root, repo, ofDraw(p.audit, p.drawn)),
5414
+ precision: { facts: precision.facts, links: precision.links },
5415
+ command: command(p.seed, p.size)
5416
+ };
5417
+ return { status: 200, body };
5418
+ }).add("POST", "/api/facts/{repo}/audit", async ({ params, body }) => {
5419
+ const repo = repoOf(params.repo);
5420
+ const b = api_exports.AuditAnswerRequest.safeParse(body ?? {});
5421
+ if (!b.success)
5422
+ throw badRequest("Say the draw and the answers: { seed, size, answers: [{ id, answer, note }] }.", {
5423
+ errors: b.error.issues.map((i) => ({ path: i.path.join("."), message: i.message }))
5424
+ });
5425
+ const p = await prepareAudit(ctx.root, repo, {
5426
+ seed: b.data.seed,
5427
+ size: b.data.size,
5428
+ now
5429
+ });
5430
+ if ("refused" in p) {
5431
+ if (p.refused === "no-facts")
5432
+ throw notFound(p.lines.join(" "));
5433
+ throw new HttpError(422, "Atticus can't audit this repo yet", p.lines.join(" "));
5434
+ }
5435
+ const inDraw = new Set(p.drawn.map((d) => d.id));
5436
+ const outside = b.data.answers.filter((a) => !inDraw.has(a.id)).map((a) => a.id);
5437
+ if (outside.length)
5438
+ throw new HttpError(422, "Not in this sample", `Not in this sample: ${outside.join(", ")}. Answer the items the draw lists.`);
5439
+ const answers = new Map(b.data.answers.map((a) => [
5440
+ a.id,
5441
+ { answer: a.answer, ...a.note ? { note: a.note } : {} }
5442
+ ]));
5443
+ answerAudit(p.audit, answers, ctx.user, p.now);
5444
+ await writeAudit(ctx.root, repo, p.audit);
5445
+ ctx.events.publish({ type: "file.changed", path: p.fileRel });
5446
+ const precision = await precisionOf(ctx.root, repo, p.audit, p.facts);
5447
+ const result = {
5448
+ file: p.fileRel,
5449
+ answered: p.audit.items.filter((i) => i.answer).length,
5450
+ open: p.audit.items.filter((i) => !i.answer).length,
5451
+ items: await itemsWithLines(ctx.root, repo, ofDraw(p.audit, p.drawn)),
5452
+ precision: { facts: precision.facts, links: precision.links },
5453
+ commit: `git add ${p.fileRel}
5454
+ git commit -m "${repo}: a person's sample audit answers"`
5455
+ };
5456
+ return { status: 200, body: result };
5457
+ });
3437
5458
  }
3438
- var sameCommit = (a, b) => a && b ? a.startsWith(b) || b.startsWith(a) : null;
3439
- function nextOf(repo, r) {
3440
- const read = { command: `atticus map ${repo}`, slash: `/atticus-map ${repo}` };
3441
- if (!r.facts.present)
3442
- return { state: "not-read", next: read };
3443
- const report = r.verify.report;
3444
- if (!report)
3445
- return { state: "unverified", next: { command: `atticus verify ${repo}` } };
3446
- if (report.rejected > 0)
3447
- return {
3448
- state: "rejected",
3449
- next: { command: `atticus verify ${repo}`, slash: `/atticus-map ${repo}` }
3450
- };
3451
- if (r.sameCommit === false)
3452
- return { state: "stale", next: read };
3453
- if (r.facts.unverified > 0)
3454
- return { state: "unverified", next: { command: `atticus verify ${repo}` } };
3455
- if (r.catalog.fromFacts === 0)
3456
- return { state: "unwritten", next: { command: `atticus verify ${repo} --write` } };
3457
- if (report.coverage.unknown > 0)
3458
- return { state: "unknown-files", next: read };
3459
- return { state: "trusted" };
3460
- }
3461
- async function trustView(root) {
3462
- const rel = (p) => relative2(root, p).split(sep2).join("/") || ".";
3463
- const problems = [];
3464
- const dir = factsDirOf(root);
3465
- const factsRel = factsRelOf(root);
3466
- const ids = [];
3467
- try {
3468
- for (const r of await loadRepos(root))
3469
- ids.push(r.id);
3470
- } catch (e) {
3471
- problems.push({ file: "org-pack/profiles", message: firstLine(e) });
3472
- }
3473
- const files = await readdir3(dir).catch(() => []);
3474
- const named = /* @__PURE__ */ new Set();
3475
- for (const f of files) {
3476
- const of = factsFileOf(f);
3477
- if (of && (of.facts || f.endsWith(".verify.yaml")))
3478
- named.add(of.repo);
5459
+
5460
+ // packages/cli/dist/ui-api-outside.js
5461
+ import { readFile as readFile9 } from "node:fs/promises";
5462
+ import { join as join11 } from "node:path";
5463
+ var SPECS = /* @__PURE__ */ new Set(["openapi", "swagger"]);
5464
+ var cmp = (a, b) => a < b ? -1 : a > b ? 1 : 0;
5465
+ var OUTSIDE_ADD_SPEC = (id) => [
5466
+ `atticus intake add <the ${id} spec file>`,
5467
+ "atticus intake list",
5468
+ "atticus triage"
5469
+ ];
5470
+ async function outsideView(root) {
5471
+ const quiet = { out: () => void 0, err: () => void 0 };
5472
+ const { op, records, inputs } = await intakeContext(root, quiet);
5473
+ const systems = outsideProfileSystems(op);
5474
+ const stamp2 = await stampOf(root);
5475
+ const estate = await estateFacts(root, op, "");
5476
+ const facts = /* @__PURE__ */ new Map();
5477
+ for (const r of estate.repos)
5478
+ for (const f of r.facts)
5479
+ facts.set(`${r.repo}\0${f.id}`, f);
5480
+ const joined = await estateJoinOnFile(root, op, estate, stamp2);
5481
+ const repos = joinReposOf(op, estate.repos, componentRenames(estate.repos, op));
5482
+ const { maps: sharedMaps } = await readSharedMaps(root);
5483
+ const sharedJoin = sharedMaps.length ? joinSharedMaps({ outside: joined.outside ?? [], repos, maps: sharedMaps, systems }) : { matches: [], gaps: [], asks: [] };
5484
+ const specs = /* @__PURE__ */ new Map();
5485
+ for (const d of inputs.documents) {
5486
+ if (!d.id || d.supersededBy || !SPECS.has(d.classification))
5487
+ continue;
5488
+ const system = systems.find((s) => s.id === d.systems[0]);
5489
+ if (!system || specs.has(system.id))
5490
+ continue;
5491
+ const body = await readFile9(join11(inputsDirOf(root), ...d.path.split("/").slice(1)), "utf8");
5492
+ const spec = readVendorSpec(d.path, body, system.id);
5493
+ if (spec)
5494
+ specs.set(system.id, spec);
3479
5495
  }
3480
- for (const id of [...named].sort())
3481
- if (!ids.includes(id))
3482
- ids.push(id);
3483
- const catalog = await readCatalog(join9(catalogDirOf(root), "generated"), { cacheRoot: root });
3484
- for (const p of catalog.problems)
3485
- problems.push({ file: rel(join9(catalogDirOf(root), "generated", p.file)), message: p.message });
3486
- const counts = /* @__PURE__ */ new Map();
3487
- const count = (r) => {
3488
- const seen = /* @__PURE__ */ new Map();
3489
- for (const e of r.evidence) {
3490
- if (!e.repo)
3491
- continue;
3492
- const s = seen.get(e.repo) ?? { facts: false, readers: false };
3493
- if (!e.extractor && e.by && FACT_BY.has(e.by))
3494
- s.facts = true;
3495
- else if (e.source !== "manual")
3496
- s.readers = true;
3497
- seen.set(e.repo, s);
3498
- }
3499
- for (const [repo, s] of seen) {
3500
- const c = counts.get(repo) ?? { facts: 0, readers: 0 };
3501
- if (s.facts)
3502
- c.facts += 1;
3503
- else if (s.readers)
3504
- c.readers += 1;
3505
- counts.set(repo, c);
3506
- }
5496
+ const vendors = systems.flatMap((s) => {
5497
+ const spec = specs.get(s.id);
5498
+ if (!spec)
5499
+ return [];
5500
+ return [
5501
+ {
5502
+ system: s.id,
5503
+ boundary: "internal",
5504
+ names: [s.name, ...s.aliases],
5505
+ hosts: [.../* @__PURE__ */ new Set([...spec.hosts, ...hostAliases(s.aliases)])],
5506
+ operations: spec.operations.map((o) => ({
5507
+ id: o.id,
5508
+ method: o.method,
5509
+ path: o.path,
5510
+ fullPath: o.fullPath,
5511
+ cite: o.cite
5512
+ }))
5513
+ }
5514
+ ];
5515
+ });
5516
+ const matched = vendors.length ? joinVendorOperations({
5517
+ repos,
5518
+ vendors,
5519
+ targets: estate.targets,
5520
+ ...joinConventions(op)
5521
+ }) : { matches: [], gaps: [], asks: [] };
5522
+ const contracts = await contractCheckOf(root).catch(() => void 0);
5523
+ const catalog = records.length ? records : (await readCatalog(join11(catalogDirOf(root), "generated"), { cacheRoot: root })).records;
5524
+ const ifaces = catalog.filter((r) => r.kind === "Interface");
5525
+ const componentsOf = (id) => /* @__PURE__ */ new Set([
5526
+ id,
5527
+ ...catalog.filter((r) => r.kind === "Component" && r.system === id).map((r) => r.id)
5528
+ ]);
5529
+ const open = await listQuestions(root, { state: "open" }).then((l) => l.questions, () => []);
5530
+ const callOf = (e) => {
5531
+ const f = facts.get(`${e.repo}\0${e.fact}`);
5532
+ const x = f?.kind === "call" ? f : void 0;
5533
+ return {
5534
+ repo: e.repo,
5535
+ ...e.component ? { component: e.component } : {},
5536
+ fact: e.fact,
5537
+ at: e.at,
5538
+ ...x?.method ? { method: x.method.toUpperCase() } : {},
5539
+ ...x?.path ? { path: x.path } : {}
5540
+ };
3507
5541
  };
3508
- for (const r of catalog.records)
3509
- count(r);
3510
- const adopted = isAdopted(root);
3511
- const repos = [];
3512
- for (const repo of ids) {
3513
- const factsPath = join9(dir, `${repo}.jsonl`);
3514
- const raw = await text(factsPath);
3515
- const facts = {
3516
- path: rel(factsPath),
3517
- present: raw !== void 0,
3518
- count: 0,
3519
- unverified: 0
3520
- };
3521
- let frameworks;
3522
- if (raw !== void 0 && raw.trim()) {
3523
- const parsed = parseFacts(raw);
3524
- if (parsed.ok) {
3525
- facts.count = parsed.facts.length;
3526
- facts.unverified = parsed.facts.filter((f) => f.verified === "none").length;
3527
- const commit = parsed.facts[0]?.commit;
3528
- if (commit)
3529
- facts.commit = commit;
3530
- const gaps = new Map(frameworkGaps(parsed.facts).map((g) => [g.id, g.question]));
3531
- const built = frameworkFacts(parsed.facts).map((f) => ({
3532
- name: f.name,
3533
- ...f.version ? { version: f.version } : {},
3534
- ...f.component ? { component: f.component } : {},
3535
- cite: `${f.cite.file}:${f.cite.line}`,
3536
- ...gaps.has(f.id) ? { gap: gaps.get(f.id) } : {}
3537
- }));
3538
- if (built.length)
3539
- frameworks = built;
5542
+ const contractsOf = (id) => {
5543
+ const findings = contracts?.result.findings.filter((f) => f.system === id) ?? [];
5544
+ const fresh = contracts?.newBySystem.get(id);
5545
+ if (!findings.length && !fresh)
5546
+ return {};
5547
+ return {
5548
+ contracts: { findings, ...fresh ? { new: fresh.map((f) => f.id) } : {} }
5549
+ };
5550
+ };
5551
+ const out = systems.map((s) => {
5552
+ const names = new Set([s.id, s.name, ...s.aliases].map(factIdPart));
5553
+ const comps = componentsOf(s.id);
5554
+ const callers = /* @__PURE__ */ new Map();
5555
+ const add = (c, seen) => {
5556
+ const k = `${c.repo}|${c.at}`;
5557
+ const had = callers.get(k);
5558
+ if (had) {
5559
+ if (!had.seen.includes(seen))
5560
+ had.seen = [...had.seen, seen].sort();
5561
+ Object.assign(had, { ...c, ...had, seen: had.seen });
3540
5562
  } else
3541
- problems.push({
3542
- file: rel(factsPath),
3543
- message: `doesn't read as facts: ${parsed.issues[0]?.message ?? "invalid"}`
3544
- });
3545
- }
3546
- const reportPath = join9(dir, `${repo}.verify.yaml`);
3547
- let report = null;
3548
- const reportText = await text(reportPath);
3549
- if (reportText !== void 0) {
3550
- try {
3551
- const r = reportOf((0, import_yaml6.parse)(reportText));
3552
- if (typeof r === "string")
3553
- problems.push({ file: rel(reportPath), message: r });
3554
- else
3555
- report = r;
3556
- } catch (e) {
3557
- problems.push({ file: rel(reportPath), message: `can't read YAML: ${firstLine(e)}` });
3558
- }
5563
+ callers.set(k, { ...c, seen: [seen] });
5564
+ };
5565
+ for (const o of joined.outside ?? [])
5566
+ if (names.has(factIdPart(o.target)))
5567
+ add(callOf(o.call), "join");
5568
+ for (const r of ifaces) {
5569
+ if (!comps.has(r.to))
5570
+ continue;
5571
+ for (const e of r.evidence)
5572
+ if (e.side !== "callee" && e.source !== "config" && e.repo && e.path && e.line !== void 0)
5573
+ add({
5574
+ repo: e.repo,
5575
+ component: r.from,
5576
+ at: `${e.path}:${e.line}`,
5577
+ ...r.method ? { method: r.method } : {},
5578
+ path: r.normalizedPath,
5579
+ interface: r.id
5580
+ }, "interface");
3559
5581
  }
3560
- if (!facts.commit && report?.commit)
3561
- facts.commit = report.commit;
3562
- let checkoutDir;
3563
- try {
3564
- checkoutDir = adopted ? root : baseClonePath(root, repo);
3565
- } catch {
3566
- checkoutDir = void 0;
5582
+ for (const g of [...matched.gaps, ...matched.asks].filter((x) => x.vendor === s.id))
5583
+ add(callOf(g.call), "spec");
5584
+ for (const m of matched.matches.filter((x) => x.vendor === s.id)) {
5585
+ add({ ...callOf(m.call), operation: m.operation }, "spec");
5586
+ if (m.direct || !m.route)
5587
+ continue;
5588
+ const route = m.route;
5589
+ for (const j of joined.joins)
5590
+ if (j.entry.repo === route.repo && j.entry.fact === route.fact)
5591
+ add({
5592
+ ...callOf(j.call),
5593
+ operation: m.operation,
5594
+ through: { repo: route.repo, at: route.at }
5595
+ }, "spec");
3567
5596
  }
3568
- const head = checkoutDir ? await headFromFiles(checkoutDir) : void 0;
3569
- const present = checkoutDir ? await stat4(join9(checkoutDir, ".git")).then(() => true, () => false) : false;
3570
- const checkout = {
3571
- path: checkoutDir ? rel(checkoutDir) : `workspace/${repo}`,
3572
- present,
3573
- ...head ? { commit: head } : {}
5597
+ for (const m of sharedJoin.matches.filter((x) => x.system === s.id))
5598
+ add({
5599
+ ...callOf(m.call),
5600
+ entry: { team: m.team, repo: m.entry.repo, fact: m.entry.fact, at: m.entry.at }
5601
+ }, "shared");
5602
+ for (const g of sharedJoin.gaps.filter((x) => x.system === s.id))
5603
+ add(callOf(g.call), "shared");
5604
+ const list = [...callers.values()].sort((a, b) => cmp(a.repo, b.repo) || cmp(a.at, b.at));
5605
+ const spec = specs.get(s.id);
5606
+ const map = sharedMaps.find((m) => m.system === s.id);
5607
+ const lines = new Set(list.map((c) => `${c.repo}|${c.at}`));
5608
+ const named = [s.id, s.name, ...s.aliases].map((n) => n.toLowerCase());
5609
+ const questions = open.filter((q) => named.some((n) => q.question.toLowerCase().includes(n)) || q.options.some((o) => named.some((n) => o.label.toLowerCase().includes(n))) || q.evidence.some((e) => lines.has(`${q.repo}|${e}`))).map((q) => ({ id: q.id, repo: q.repo, question: q.question })).sort((a, b) => cmp(a.id, b.id));
5610
+ return {
5611
+ id: s.id,
5612
+ name: s.name,
5613
+ boundary: s.boundary,
5614
+ ...s.owner ? { owner: s.owner } : {},
5615
+ aliases: [...s.aliases],
5616
+ callers: list,
5617
+ ...spec ? {
5618
+ spec: {
5619
+ path: spec.path,
5620
+ ...spec.title ? { title: spec.title } : {},
5621
+ ...spec.version ? { version: spec.version } : {},
5622
+ operations: spec.operations.map((o) => ({
5623
+ id: o.id,
5624
+ method: o.method,
5625
+ path: o.fullPath,
5626
+ cite: o.cite,
5627
+ calls: list.filter((c) => c.operation === o.id).length
5628
+ }))
5629
+ }
5630
+ } : {},
5631
+ unmatched: matched.gaps.filter((g) => g.vendor === s.id).map((g) => ({ ...callOf(g.call), seen: ["spec"], message: g.message })),
5632
+ ambiguous: matched.asks.filter((a) => a.vendor === s.id).map((a) => ({ ...callOf(a.call), seen: ["spec"], candidates: a.candidates })),
5633
+ questions,
5634
+ ...spec ? {} : { addSpec: OUTSIDE_ADD_SPEC(s.id) },
5635
+ ...contractsOf(s.id),
5636
+ ...map ? {
5637
+ shared: {
5638
+ team: map.team,
5639
+ name: map.name,
5640
+ commit: map.commit,
5641
+ importedAt: map.file.importedAt,
5642
+ entries: map.repos.flatMap((r) => r.facts.flatMap((f) => f.kind === "entry" ? [
5643
+ {
5644
+ repo: r.repo,
5645
+ component: factIdPart(f.component ?? r.component ?? r.repo),
5646
+ fact: f.id,
5647
+ ...f.method ? { method: f.method.toUpperCase() } : {},
5648
+ path: f.path,
5649
+ at: `${f.cite.file}:${f.cite.line}`,
5650
+ ...f.commit ? { commit: f.commit } : {},
5651
+ calls: list.filter((c) => c.entry?.repo === r.repo && c.entry.fact === f.id).length
5652
+ }
5653
+ ] : [])).sort((a, b) => cmp(a.repo, b.repo) || cmp(a.at, b.at)),
5654
+ unmatched: sharedJoin.gaps.filter((g) => g.system === s.id).map((g) => ({ ...callOf(g.call), seen: ["shared"], message: g.message }))
5655
+ }
5656
+ } : {}
3574
5657
  };
3575
- const c = counts.get(repo) ?? { facts: 0, readers: 0 };
3576
- const from = c.facts && c.readers ? "both" : c.facts ? "facts" : c.readers ? "readers" : "none";
3577
- const base = {
3578
- repo,
3579
- facts,
3580
- verify: { path: rel(reportPath), report },
3581
- checkout,
3582
- sameCommit: sameCommit(report?.commit ?? facts.commit, head),
3583
- catalog: { from, fromFacts: c.facts, fromReaders: c.readers }
3584
- };
3585
- const { state, next } = nextOf(repo, base);
3586
- repos.push({
3587
- ...base,
3588
- state,
3589
- ...next ? { next } : {},
3590
- ...frameworks ? { frameworks } : {}
3591
- });
3592
- }
3593
- return { factsDir: factsRel, repos, problems };
5658
+ });
5659
+ return { systems: out, notes: estate.notes };
3594
5660
  }
3595
- function uiTrustRoutes(r, ctx) {
3596
- return r.add("GET", "/api/trust", async () => ({ status: 200, body: await trustView(ctx.root) }));
5661
+ function uiOutsideRoutes(r, ctx) {
5662
+ return r.add("GET", "/api/outside", async () => ({
5663
+ status: 200,
5664
+ body: await outsideView(ctx.root)
5665
+ }));
3597
5666
  }
3598
5667
 
3599
- // packages/cli/dist/ui-api-facts.js
3600
- import { appendFile, mkdir, readFile as readFile8 } from "node:fs/promises";
3601
- import { join as join10 } from "node:path";
3602
- var REPO = /^[a-z0-9][a-z0-9._-]{0,99}$/i;
3603
- var fixed = new Set(FACT_FIXED_FIELDS);
3604
- function factLabel(f) {
3605
- switch (f.kind) {
3606
- case "entry":
3607
- return `${f.method ? `${f.method} ` : ""}${f.path}`;
3608
- case "call":
3609
- return `${f.method ? `${f.method} ` : ""}${f.path}${f.to ? ` \u2192 ${f.to}` : ""}`;
3610
- case "store":
3611
- return `${f.name} (${f.op})`;
3612
- case "field":
3613
- return `${f.name}${f.type ? `: ${f.type}` : ""}`;
3614
- case "deploy":
3615
- return `${f.platform}${f.resourceName ? ` ${f.resourceName}` : ""}`;
3616
- case "config":
3617
- return `${f.key}${f.value !== void 0 ? ` = ${f.value}` : ""}`;
3618
- // ADR 0124 addendum A-framework: what the repo or the module is built on.
3619
- case "framework":
3620
- return `${f.name}${f.version ? ` ${f.version}` : ""}`;
3621
- case "flow":
3622
- return f.name;
3623
- }
5668
+ // packages/cli/dist/ui-api-contracts.js
5669
+ async function contractsView(root, check = contractCheckOf) {
5670
+ const { result, notes } = await check(root);
5671
+ return { links: result.links, findings: result.findings, counts: result.counts, notes };
3624
5672
  }
3625
- function claimOf(f) {
3626
- const out = {};
3627
- for (const [k, v] of Object.entries(f))
3628
- if (!fixed.has(k) && typeof v === "string")
3629
- out[k] = v;
3630
- out.value = f.cite.value;
5673
+ function uiContractsRoutes(r, ctx, o = {}) {
5674
+ return r.add("GET", "/api/contracts", async () => ({
5675
+ status: 200,
5676
+ body: await contractsView(ctx.root, o.check)
5677
+ }));
5678
+ }
5679
+
5680
+ // packages/cli/dist/ui-api-stages.js
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
+ }
3631
5718
  return out;
3632
5719
  }
3633
- var spot = (c) => ({
3634
- file: c.file,
3635
- line: c.line,
3636
- value: c.value,
3637
- ...c.quote ? { quote: c.quote } : {}
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)
3638
5725
  });
3639
- async function readFacts(root, repo) {
3640
- const file = `${factsRelOf(root)}/${repo}.jsonl`;
3641
- const text2 = await readFile8(join10(factsDirOf(root), `${repo}.jsonl`), "utf8").catch(() => void 0);
3642
- if (text2 === void 0)
3643
- return { file, present: false, facts: [], bad: [] };
3644
- const p = parseFactsLines(text2);
3645
- return {
3646
- file,
3647
- present: true,
3648
- facts: p.facts,
3649
- bad: p.bad.map((b) => ({
3650
- file,
3651
- message: `line ${b.line} isn't a fact: ${b.issues.map((i) => i.message).join("; ")}`
3652
- }))
3653
- };
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;
3654
5735
  }
3655
- async function repoFactsView(root, repo) {
3656
- const f = await readFacts(root, repo);
3657
- const cFile = `${factsRelOf(root)}/${repo}.corrections.jsonl`;
3658
- const c = await readCorrections(root, repo);
3659
- const problems = [
3660
- ...f.bad,
3661
- ...c.bad.map((b) => ({
3662
- file: cFile,
3663
- message: `line ${b.line} isn't a correction: ${b.message}`
3664
- }))
3665
- ];
3666
- const facts = f.facts.map((x) => {
3667
- const corr = correctionFor(x, c.corrections);
3668
- return {
3669
- id: x.id,
3670
- kind: x.kind,
3671
- label: factLabel(x),
3672
- claim: claimOf(x),
3673
- cite: spot(x.cite),
3674
- ...x.kind === "flow" ? {
3675
- steps: [
3676
- ...x.steps.map((s) => ({ ...spot(s.cite), what: s.what })),
3677
- ...x.errors.map((e) => ({ ...spot(e.cite), what: `${e.when} \u2192 ${e.then}` }))
3678
- ]
3679
- } : {},
3680
- verified: x.verified,
3681
- by: x.by,
3682
- ...corr ? { correction: corr } : {}
3683
- };
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 {};
3684
5745
  });
3685
- const stale = applyCorrections(f.facts, c.corrections).stale.filter((s) => s.why === "none").map((s) => s.correction.fact);
3686
- const questions = await listQuestions(root, { repo, state: "open" }).then((l) => l.questions.map((q) => ({ id: q.id, question: q.question, evidence: q.evidence })), () => []);
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");
3687
5768
  return {
3688
- repo,
3689
- file: f.file,
3690
- present: f.present,
3691
- ...f.facts[0] ? { commit: f.facts[0].commit } : {},
3692
- facts,
3693
- corrections: { file: cFile, count: c.corrections.length, stale: [...new Set(stale)].sort() },
3694
- questions,
3695
- verify: `atticus verify ${repo}`,
5769
+ change,
5770
+ page,
5771
+ files,
5772
+ corrections: mine.map(view),
5773
+ next: nextOf(waiting?.stage ?? PAGE_STAGE[page], change),
3696
5774
  problems
3697
5775
  };
3698
5776
  }
3699
- var repoOf = (v) => {
3700
- if (!REPO.test(v))
3701
- throw badRequest(`${JSON.stringify(v)} isn't a repo name.`);
3702
- return v;
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;
3703
5789
  };
3704
- function uiFactsRoutes(r, ctx, deps = {}) {
5790
+ function uiDraftRoutes(r, ctx, deps = {}) {
3705
5791
  const now = deps.now ?? (() => /* @__PURE__ */ new Date());
3706
- return r.add("GET", "/api/facts/{repo}", async ({ params }) => ({
3707
- status: 200,
3708
- body: await repoFactsView(ctx.root, repoOf(params.repo))
3709
- })).add("POST", "/api/facts/{repo}/corrections", async ({ params, body }) => {
3710
- const repo = repoOf(params.repo);
3711
- const b = api_exports.FactCorrectionRequest.safeParse(body ?? {});
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 ?? {});
3712
5813
  if (!b.success)
3713
- throw badRequest("Say which fact and what is wrong: { fact, action, set, line, reason }.", {
5814
+ throw badRequest("Say which draft (file, and item) and write the correction: { file, item, text }.", {
3714
5815
  errors: b.error.issues.map((i) => ({ path: i.path.join("."), message: i.message }))
3715
5816
  });
3716
- const f = await readFacts(ctx.root, repo);
3717
- if (!f.present)
3718
- throw notFound(`There are no facts for ${repo} at ${f.file}.`);
3719
- const fact = f.facts.find((x) => x.id === b.data.fact);
3720
- if (!fact)
3721
- throw notFound(`${f.file} has no fact ${b.data.fact}.`);
3722
- const was = claimOf(fact);
3723
- const set = Object.fromEntries(Object.entries(b.data.set ?? {}).map(([k, v]) => [k, v.trim()]).filter(([k, v]) => was[k] !== v));
3724
- const line = b.data.line !== void 0 && b.data.line !== fact.cite.line ? b.data.line : void 0;
3725
- const c = FactCorrection.safeParse({
3726
- schemaVersion: 1,
3727
- fact: fact.id,
3728
- target: {
3729
- kind: fact.kind,
3730
- file: fact.cite.file,
3731
- line: fact.cite.line,
3732
- value: fact.cite.value
3733
- },
3734
- action: b.data.action,
3735
- ...b.data.action === "edit" && Object.keys(set).length ? { set } : {},
3736
- ...b.data.action === "edit" && line !== void 0 ? { line } : {},
3737
- ...b.data.reason ? { reason: b.data.reason } : {},
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] } : {},
3738
5840
  by: ctx.user,
3739
5841
  at: now().toISOString()
3740
- });
3741
- if (!c.success)
3742
- throw new HttpError(422, "Atticus can't record that", c.error.issues.map((i) => i.message).join("; "));
3743
- if (c.data.action === "edit") {
3744
- const fixedFact = correctedFact(fact, c.data);
3745
- if (typeof fixedFact === "string")
3746
- throw new HttpError(422, "That isn't a fact", `The corrected ${fact.kind} isn't one: ${fixedFact}.`);
3747
- }
3748
- const rel = `${factsRelOf(ctx.root)}/${repo}.corrections.jsonl`;
3749
- await mkdir(factsDirOf(ctx.root), { recursive: true });
3750
- await appendFile(join10(factsDirOf(ctx.root), `${repo}.corrections.jsonl`), `${correctionLine(c.data)}
3751
- `, "utf8");
3752
- ctx.events.publish({ type: "file.changed", path: rel });
5842
+ }).catch(refusal);
5843
+ const written = `${changesRelOf(ctx.root)}/${change}/${CORRECTIONS_FILE}`;
5844
+ ctx.events.publish({ type: "file.changed", path: written });
3753
5845
  const result = {
3754
- correction: c.data,
3755
- file: rel,
3756
- commit: `git add ${rel} && git commit -m "${repo}: a person's correction of ${fact.id}"`,
3757
- verify: `atticus verify ${repo}`
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)
3758
5851
  };
3759
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) };
3760
5866
  });
3761
5867
  }
3762
5868
 
3763
5869
  // packages/cli/dist/ui-api-stages.js
3764
- import { stat as stat5 } from "node:fs/promises";
3765
- import { join as join11 } from "node:path";
3766
- var ID2 = /^[a-z0-9][a-z0-9._-]{0,99}$/;
5870
+ var ID4 = /^[a-z0-9][a-z0-9._-]{0,99}$/;
3767
5871
  function findingOptions(f) {
3768
5872
  const open = f.state === "open";
3769
5873
  const issue = f.kind === "issue";
@@ -3775,8 +5879,8 @@ function findingOptions(f) {
3775
5879
  ...open && f.question ? { ask: { text: f.question.text, owner: f.question.owner } } : {}
3776
5880
  };
3777
5881
  }
3778
- var exists2 = (p) => stat5(p).then(() => true, () => false);
3779
- var slashOf = (stage, change) => `/atticus-${stage} ${change}`;
5882
+ var exists3 = (p) => stat6(p).then(() => true, () => false);
5883
+ var slashOf2 = (stage, change) => `/atticus-${stage} ${change}`;
3780
5884
  async function gateOf(root, change, stage) {
3781
5885
  const problems = [];
3782
5886
  let gate;
@@ -3802,7 +5906,7 @@ async function latest(root, change, gate) {
3802
5906
  const r = await readRound(root, change, gate, round);
3803
5907
  return { round, findings: r.findings, bad: r.bad };
3804
5908
  }
3805
- var view = (c) => ({
5909
+ var view2 = (c) => ({
3806
5910
  question: c.question,
3807
5911
  text: c.text,
3808
5912
  by: c.by,
@@ -3826,7 +5930,7 @@ async function stageReviewView(root, change, stage) {
3826
5930
  finding: s.finding,
3827
5931
  file: fileRel,
3828
5932
  options: findingOptions(s.finding),
3829
- comments: comments.comments.filter((c) => c.about === fileRel).map(view),
5933
+ comments: comments.comments.filter((c) => c.about === fileRel).map(view2),
3830
5934
  ...p ? {
3831
5935
  proposal: {
3832
5936
  claim: p.claim,
@@ -3859,10 +5963,37 @@ async function stageReviewView(root, change, stage) {
3859
5963
  },
3860
5964
  findings,
3861
5965
  questions,
3862
- next: { command: rerunOf(stage, change), slash: slashOf(stage, change) },
5966
+ next: { command: rerunOf(stage, change), slash: slashOf2(stage, change) },
3863
5967
  problems
3864
5968
  };
3865
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
+ }
3866
5997
  function tabOf(v) {
3867
5998
  const r = api_exports.StageTab.safeParse(v);
3868
5999
  if (!r.success)
@@ -3872,13 +6003,57 @@ function tabOf(v) {
3872
6003
  function uiStageRoutes(r, ctx, deps = {}) {
3873
6004
  const now = deps.now ?? (() => /* @__PURE__ */ new Date());
3874
6005
  const changeOf = async (v) => {
3875
- if (!ID2.test(v))
6006
+ if (!ID4.test(v))
3876
6007
  throw badRequest(`${JSON.stringify(v)} isn't a change id.`);
3877
- if (!await exists2(join11(changeFolder(ctx.root, v), "change.yaml")))
6008
+ if (!await exists3(join13(changeFolder(ctx.root, v), "change.yaml")))
3878
6009
  throw notFound(`There is no change ${v}.`);
3879
6010
  return v;
3880
6011
  };
3881
- 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 }) => {
3882
6057
  const stage = tabOf(params.stage);
3883
6058
  const change = await changeOf(params.changeId);
3884
6059
  return { status: 200, body: await stageReviewView(ctx.root, change, stage) };
@@ -3926,12 +6101,13 @@ function uiStageRoutes(r, ctx, deps = {}) {
3926
6101
  comment: {
3927
6102
  question: q.id,
3928
6103
  text: q.answer.text,
3929
- by: q.answer.by,
6104
+ by: answerByWords(q.answer.by),
3930
6105
  at: q.answer.at,
3931
6106
  read: false
3932
6107
  },
3933
6108
  files: written.files,
3934
- 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}"`,
3935
6111
  next: v.next
3936
6112
  };
3937
6113
  return { status: 200, body: result };
@@ -3942,13 +6118,23 @@ function uiStageRoutes(r, ctx, deps = {}) {
3942
6118
  function catalogMayHaveChanged(root, path) {
3943
6119
  const p = path.replace(/^\.\/?/, "").replace(/\/+$/, "");
3944
6120
  const near = (dir) => {
3945
- const rel = relative3(root, dir).split(sep3).join("/");
6121
+ const rel = relative2(root, dir).split(sep2).join("/");
3946
6122
  if (rel.startsWith("..") || isAbsolute(rel))
3947
6123
  return true;
3948
6124
  return p === "" || p === rel || p.startsWith(rel + "/") || rel.startsWith(p + "/");
3949
6125
  };
3950
6126
  return near(catalogDirOf(root)) || near(orgPackDirOf(root));
3951
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
+ }
3952
6138
  async function startUi(root, o = {}, deps = {}) {
3953
6139
  const srv = await startServer({
3954
6140
  root,
@@ -3971,17 +6157,10 @@ async function startUi(root, o = {}, deps = {}) {
3971
6157
  log.debug("request", fields);
3972
6158
  };
3973
6159
  })(),
3974
- extend: (r, ctx) => {
3975
- const base = uiKnowRoutes(uiRunRoutes(uiRoutes(r, ctx, deps), ctx, deps), ctx, {
3976
- warm: o.warm === true
3977
- });
3978
- const views = uiGraphRoutes(uiAcrossRoutes(uiMeRoutes(base, ctx), ctx), ctx);
3979
- const specs = uiSpecRoutes(uiOnboardingRoutes(uiQuestionRoutes(views, ctx), ctx), ctx);
3980
- return uiStageRoutes(uiFactsRoutes(uiTrustRoutes(uiCockpitRoutes(uiRepoRoutes(uiDocumentRoutes(uiAiRoutes(uiHistoryRoutes(uiModelRunRoutes(uiKnowledgeRoutes(uiCiteRoutes(specs, ctx), ctx), ctx), ctx), ctx), ctx), ctx), ctx), ctx), ctx), ctx);
3981
- }
6160
+ extend: (r, ctx) => extendUiRoutes(r, ctx, deps, { warm: o.warm === true })
3982
6161
  });
3983
6162
  keepCatalogsInMemory(true);
3984
- void readCatalog(join12(catalogDirOf(root), "generated"), { cacheRoot: root }).catch(() => void 0);
6163
+ void readCatalog(join14(catalogDirOf(root), "generated"), { cacheRoot: root }).catch(() => void 0);
3985
6164
  const stop = srv.events.listen((e) => {
3986
6165
  const changed = e.type === "file.changed" && (typeof e.path !== "string" || catalogMayHaveChanged(root, e.path));
3987
6166
  if (changed || e.type === "index.rebuilt")
@@ -4005,7 +6184,7 @@ function openBrowser(url) {
4005
6184
  }
4006
6185
  }
4007
6186
  function untilStopped(embedded, stdin = process.stdin, o = {}) {
4008
- return new Promise((resolve3) => {
6187
+ return new Promise((resolve2) => {
4009
6188
  const alive = o.alive ?? ((pid) => {
4010
6189
  try {
4011
6190
  process.kill(pid, 0);
@@ -4028,7 +6207,7 @@ function untilStopped(embedded, stdin = process.stdin, o = {}) {
4028
6207
  stdin.off("close", stop);
4029
6208
  stdin.pause();
4030
6209
  }
4031
- resolve3();
6210
+ resolve2();
4032
6211
  };
4033
6212
  process.once("SIGINT", stop);
4034
6213
  process.once("SIGTERM", stop);
@@ -4074,12 +6253,14 @@ async function uiCommand(root, o, io) {
4074
6253
  if (o.open)
4075
6254
  openBrowser(srv.url);
4076
6255
  await untilStopped(o.embed === "vscode", process.stdin, o.parent !== void 0 ? { parent: o.parent } : {});
6256
+ stopAllBuilds();
4077
6257
  await srv.close();
4078
6258
  io.out("Stopped.");
4079
6259
  return 0;
4080
6260
  }
4081
6261
  export {
4082
6262
  catalogMayHaveChanged,
6263
+ extendUiRoutes,
4083
6264
  startUi,
4084
6265
  startedLines,
4085
6266
  uiCommand,