@maycombhq/atticus 2.0.0-beta.18 → 2.0.0-beta.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (270) hide show
  1. package/dist/bin.js +8 -4
  2. package/dist/{chunk-MUSJJ6CQ.js → chunk-265LWMOA.js} +16 -16
  3. package/dist/chunk-26WBY34Y.js +37 -0
  4. package/dist/{chunk-6H5JG5GL.js → chunk-26Z7OGQW.js} +63 -30
  5. package/dist/{chunk-4REDBQOZ.js → chunk-2D7PWSOK.js} +20 -20
  6. package/dist/{chunk-YPH6ZJ6R.js → chunk-2HH3UCZM.js} +4 -4
  7. package/dist/{chunk-RTUUX7PX.js → chunk-2HTEMYTB.js} +12 -26
  8. package/dist/{chunk-EHQUY26D.js → chunk-2UCRFXFZ.js} +11 -11
  9. package/dist/{chunk-ILDGIXNH.js → chunk-2V2ZPQQU.js} +81 -13
  10. package/dist/{chunk-YSEZQ6D3.js → chunk-36RY7ZXM.js} +107 -64
  11. package/dist/{chunk-QXFDM2FZ.js → chunk-37WE3LS2.js} +3 -3
  12. package/dist/{chunk-BAXM3JFQ.js → chunk-3AQ2R5NL.js} +1212 -754
  13. package/dist/chunk-3G6S7LAA.js +43 -0
  14. package/dist/{chunk-7TOAS7KI.js → chunk-3J5I7ZOU.js} +4 -4
  15. package/dist/{chunk-TAKAEJRD.js → chunk-3LZ6P5EP.js} +1 -1
  16. package/dist/chunk-3SSTNB4E.js +191 -0
  17. package/dist/chunk-3TD6S4TC.js +49 -0
  18. package/dist/{chunk-UUL6ZGMU.js → chunk-3UTYPHN3.js} +1 -1
  19. package/dist/{chunk-6AP2ZUUM.js → chunk-3WON4G3M.js} +2 -2
  20. package/dist/{chunk-AXY6LBW4.js → chunk-42EPP7OB.js} +12 -12
  21. package/dist/{chunk-QKMDT7VU.js → chunk-455SYIIE.js} +2 -2
  22. package/dist/{chunk-MFUFB3LN.js → chunk-4B6BT75O.js} +110 -20
  23. package/dist/{chunk-75MYLZSB.js → chunk-4CLK2RJO.js} +7 -7
  24. package/dist/chunk-4CYTJ5TZ.js +38 -0
  25. package/dist/{chunk-IH7RANGB.js → chunk-4D62IZII.js} +5 -5
  26. package/dist/{chunk-2QMZOCWN.js → chunk-4FOAYLI4.js} +14 -7
  27. package/dist/{chunk-V3UEPUHT.js → chunk-4KNOJC6J.js} +294 -215
  28. package/dist/{chunk-O6ILVBYB.js → chunk-4PYC2UGC.js} +1 -1
  29. package/dist/{chunk-EZBAS5BL.js → chunk-4SLBCEPD.js} +82 -50
  30. package/dist/{chunk-VIRTPODJ.js → chunk-4TKVU2I2.js} +6 -14
  31. package/dist/{chunk-IVCDU443.js → chunk-4WXRPQDE.js} +281 -51
  32. package/dist/{chunk-RMEEHH24.js → chunk-5CMPVC3N.js} +24 -24
  33. package/dist/chunk-5G7ILBUG.js +66 -0
  34. package/dist/{chunk-Z75XDWQR.js → chunk-5PV7S2ZP.js} +3 -3
  35. package/dist/{chunk-WWPC2AZ2.js → chunk-5QUOPNLN.js} +4 -4
  36. package/dist/{chunk-7OVH2L7L.js → chunk-5TDRBOAV.js} +5 -5
  37. package/dist/{chunk-YXUIUOQZ.js → chunk-5WERBKLE.js} +199 -63
  38. package/dist/{chunk-6WNNXEG6.js → chunk-62DLJSLV.js} +8 -8
  39. package/dist/{chunk-KCQS52TH.js → chunk-64AADMTW.js} +99 -25
  40. package/dist/{chunk-KLRUXRPN.js → chunk-67BP7HXL.js} +17 -17
  41. package/dist/chunk-6DQYZU2W.js +379 -0
  42. package/dist/{chunk-RONDYDMQ.js → chunk-6EXDI5GI.js} +51 -34
  43. package/dist/chunk-6FNAKCET.js +38 -0
  44. package/dist/{chunk-6SNEA73E.js → chunk-75ASLHBT.js} +21 -15
  45. package/dist/{chunk-EFT4KM6K.js → chunk-75ZOZFGH.js} +18 -18
  46. package/dist/{chunk-B3KW7EQW.js → chunk-77O633L5.js} +12 -12
  47. package/dist/{chunk-TLDL2ZZI.js → chunk-7AB6OWTV.js} +3 -3
  48. package/dist/{chunk-R5YXY6NT.js → chunk-7CZHUZBL.js} +25 -25
  49. package/dist/{chunk-F4D4G2A6.js → chunk-7FHNFCHK.js} +306 -125
  50. package/dist/{chunk-CC5RNYZF.js → chunk-7FUIVIBG.js} +19 -9
  51. package/dist/{chunk-UBTOSSNK.js → chunk-7QPCGTPS.js} +21 -21
  52. package/dist/{chunk-CDLTRJ4N.js → chunk-A2G72UI4.js} +7 -7
  53. package/dist/{chunk-YGSOQ5LX.js → chunk-AE4P57KK.js} +23 -17
  54. package/dist/{chunk-PGQL5YGH.js → chunk-AHW3RY3X.js} +7 -7
  55. package/dist/chunk-AM6FP6JW.js +43 -0
  56. package/dist/{chunk-DYNC6BAQ.js → chunk-AOCWFA7T.js} +93 -10
  57. package/dist/{chunk-CYX36GKZ.js → chunk-API5TOUB.js} +1 -1
  58. package/dist/{chunk-BJ5T3KDF.js → chunk-AZDITBCT.js} +3 -3
  59. package/dist/{chunk-U6GZSUMC.js → chunk-BA2HUMDN.js} +25 -3
  60. package/dist/chunk-BA3U7ALS.js +99 -0
  61. package/dist/{chunk-63565QTF.js → chunk-BC6KKZ67.js} +5 -5
  62. package/dist/{chunk-XO5RQKNM.js → chunk-BEPCKDCP.js} +1843 -577
  63. package/dist/{chunk-KUH62AQ3.js → chunk-BPPU3PW7.js} +4 -4
  64. package/dist/{chunk-52ULYRV4.js → chunk-BVWWPBKD.js} +18 -18
  65. package/dist/{chunk-R2FGEBIU.js → chunk-BXZ4PKIT.js} +13 -11
  66. package/dist/{chunk-4BU26GFB.js → chunk-C7CEK3KW.js} +4 -4
  67. package/dist/chunk-CFH3QH26.js +29 -0
  68. package/dist/chunk-CG6CULP6.js +170 -0
  69. package/dist/chunk-CLD4RVTA.js +45 -0
  70. package/dist/{chunk-BU543ZEC.js → chunk-CNPY4NTZ.js} +13 -13
  71. package/dist/{chunk-PLBIWEPV.js → chunk-CNU5Q2D5.js} +32 -31
  72. package/dist/{chunk-JTYYEYRR.js → chunk-COYN3UAN.js} +124 -21
  73. package/dist/{chunk-TNCROWPQ.js → chunk-CPK7QSBG.js} +10 -10
  74. package/dist/{chunk-43E6XX67.js → chunk-D2WSW4GU.js} +1 -1
  75. package/dist/{chunk-SHEUEHKC.js → chunk-D57MFJPP.js} +3 -3
  76. package/dist/chunk-D5KV5PSK.js +45 -0
  77. package/dist/chunk-D5MASS4I.js +48 -0
  78. package/dist/{chunk-TQYBXDVH.js → chunk-DCWN3HFV.js} +1 -1
  79. package/dist/{chunk-FF4IGX4Z.js → chunk-DDJPM5YX.js} +2 -2
  80. package/dist/chunk-DN4A2Q2O.js +57 -0
  81. package/dist/{chunk-4ZCFCIEL.js → chunk-DUPZPDCJ.js} +52 -16
  82. package/dist/{chunk-CUJ5TIEB.js → chunk-E2VXBSIR.js} +4 -4
  83. package/dist/{chunk-BX47X5DJ.js → chunk-E57AOFYN.js} +13 -13
  84. package/dist/{chunk-SSTI7GAV.js → chunk-ECLXZWAG.js} +12 -12
  85. package/dist/{chunk-KYK3TA4Q.js → chunk-EDZ45WIR.js} +14 -14
  86. package/dist/chunk-EFCQSMXB.js +37 -0
  87. package/dist/{chunk-YC2K3FJG.js → chunk-EZIN5E5B.js} +8 -8
  88. package/dist/{chunk-MIZNACDI.js → chunk-F6H2RJ3N.js} +1 -1
  89. package/dist/{chunk-EMZVHDJP.js → chunk-FAJDKNSD.js} +7 -3
  90. package/dist/chunk-FGAGWYZB.js +47 -0
  91. package/dist/{chunk-KBGQ3IAM.js → chunk-FLVZFTLQ.js} +18 -18
  92. package/dist/{chunk-2NNXLH53.js → chunk-FQ6WH6PB.js} +22 -7
  93. package/dist/{chunk-QWSLMVHI.js → chunk-FRHD22WM.js} +61 -23
  94. package/dist/{chunk-5MEN4SM5.js → chunk-FVMPFCDS.js} +4 -4
  95. package/dist/{chunk-KLRZKW7Y.js → chunk-FY3QIRTL.js} +3 -3
  96. package/dist/{chunk-B3SWPNJF.js → chunk-FYBF5MB2.js} +13 -11
  97. package/dist/{chunk-CVZCW3NI.js → chunk-G5YJBNDZ.js} +14 -5
  98. package/dist/{chunk-U6EPS5PF.js → chunk-G6IQYM25.js} +5 -5
  99. package/dist/chunk-G6X4YL4D.js +161 -0
  100. package/dist/{chunk-DOSX3MRW.js → chunk-GC2VK3JT.js} +25 -25
  101. package/dist/{chunk-EEWBTFRP.js → chunk-GCS3FZOB.js} +27 -3
  102. package/dist/{chunk-6H6OPNEP.js → chunk-GDZB57IU.js} +5 -5
  103. package/dist/chunk-GIFMBCTW.js +221 -0
  104. package/dist/{chunk-X5WYPIH3.js → chunk-GK4PYVEC.js} +18 -8
  105. package/dist/{chunk-VBSJ5NRB.js → chunk-GNWAOVFQ.js} +5 -5
  106. package/dist/{chunk-TXFDQRBL.js → chunk-GS76RXQE.js} +11 -11
  107. package/dist/{chunk-U3YC4RGC.js → chunk-GSGIZ2OO.js} +23 -23
  108. package/dist/chunk-GUYL7RMK.js +60 -0
  109. package/dist/{chunk-AZYBHPV4.js → chunk-GX74YWR7.js} +14 -13
  110. package/dist/{chunk-YY7VP7KW.js → chunk-HE3HRDNZ.js} +2 -2
  111. package/dist/{chunk-Z7HYYDZM.js → chunk-HGHRSF3E.js} +14 -13
  112. package/dist/{chunk-Q2X4IREC.js → chunk-HK3VOHIE.js} +106 -36
  113. package/dist/{chunk-P5JYMO7Y.js → chunk-HS4BGB4G.js} +22 -22
  114. package/dist/{chunk-IOW6BF3U.js → chunk-HSZMPH5T.js} +798 -141
  115. package/dist/{chunk-L3T33FA7.js → chunk-HWD4JPU7.js} +4 -2
  116. package/dist/{chunk-7NYCH5HY.js → chunk-HXXP2NTE.js} +6 -6
  117. package/dist/{chunk-7S7OFVIU.js → chunk-I2A3Z5AK.js} +4 -4
  118. package/dist/{chunk-WQSDKX7M.js → chunk-ICHDSWEY.js} +7 -7
  119. package/dist/chunk-IFFRGC3N.js +31 -0
  120. package/dist/{chunk-HMZQAIR5.js → chunk-IFHDV6KQ.js} +24 -17
  121. package/dist/chunk-J2O4LFVI.js +80 -0
  122. package/dist/chunk-J5AAZ6OJ.js +161 -0
  123. package/dist/{chunk-CVRWAG76.js → chunk-JAABT3DA.js} +3 -3
  124. package/dist/chunk-JITJP64K.js +34 -0
  125. package/dist/chunk-JKIGL3XQ.js +162 -0
  126. package/dist/chunk-JRVNXLZP.js +615 -0
  127. package/dist/chunk-JRXHON4E.js +41 -0
  128. package/dist/{chunk-XFCK5B27.js → chunk-JS6WMC3J.js} +8 -8
  129. package/dist/chunk-JU4QNJSR.js +62 -0
  130. package/dist/{chunk-IPSFMTK2.js → chunk-JYMIB5XY.js} +152 -42
  131. package/dist/{chunk-L336AJCZ.js → chunk-JYTIHTIK.js} +8 -6
  132. package/dist/{chunk-VEJYZEPC.js → chunk-K332XW7K.js} +135 -16
  133. package/dist/{chunk-KCF7TFOE.js → chunk-K6JB6XTX.js} +11 -11
  134. package/dist/{chunk-LFIIX4OV.js → chunk-KDAHXQIE.js} +46 -34
  135. package/dist/chunk-KYZMRSPS.js +93 -0
  136. package/dist/chunk-L4CESDPB.js +22 -0
  137. package/dist/{chunk-3LR4522O.js → chunk-LEBR5NHW.js} +14 -14
  138. package/dist/{chunk-US3QZU23.js → chunk-LIJJCBQI.js} +67 -7
  139. package/dist/{chunk-XLHL6WTI.js → chunk-LIRVU3HW.js} +16 -16
  140. package/dist/{chunk-BAT4CBB7.js → chunk-LN77DXBM.js} +19 -10
  141. package/dist/{chunk-E5FM44EJ.js → chunk-LNWEWYCM.js} +2474 -315
  142. package/dist/{chunk-TX4OGZKU.js → chunk-LO3BLMVG.js} +3 -3
  143. package/dist/{chunk-RUOGHYSK.js → chunk-LPV6T362.js} +5 -5
  144. package/dist/{chunk-2T5CQH4R.js → chunk-LQH4P45K.js} +1 -1
  145. package/dist/chunk-LU4MQVAL.js +41 -0
  146. package/dist/{chunk-HW4RRIJA.js → chunk-LWDNKE4X.js} +12 -10
  147. package/dist/chunk-M4H2UG4R.js +218 -0
  148. package/dist/{chunk-CANMOOE6.js → chunk-M6D2U3YM.js} +3 -3
  149. package/dist/{chunk-AB5E5HCN.js → chunk-MD7KUEJ3.js} +14 -272
  150. package/dist/{chunk-ENTAAEDK.js → chunk-MMZUN7ZO.js} +20 -20
  151. package/dist/{chunk-L4MHJG3I.js → chunk-MZGRDONP.js} +46 -36
  152. package/dist/{chunk-IYI2S6BB.js → chunk-N3RUGTGV.js} +18 -18
  153. package/dist/{chunk-WZRCGGXG.js → chunk-NCB6656Z.js} +7 -7
  154. package/dist/{chunk-SBE773JU.js → chunk-NRQ65LTQ.js} +5 -5
  155. package/dist/{chunk-NRPMPZWJ.js → chunk-NTAVOWYD.js} +117 -18
  156. package/dist/{chunk-UJBZS5I7.js → chunk-NYOABJNK.js} +36 -27
  157. package/dist/{chunk-LXAPJ3BL.js → chunk-O4IKAIZ2.js} +24 -20
  158. package/dist/{chunk-MMB2OHUY.js → chunk-ODMBPDDV.js} +48 -13
  159. package/dist/chunk-OKUB6RJA.js +533 -0
  160. package/dist/{chunk-MKFYQQ5Q.js → chunk-OMPENNWY.js} +4 -1
  161. package/dist/chunk-OPQZISG3.js +25 -0
  162. package/dist/{chunk-ROJMXG4E.js → chunk-OV57AHCG.js} +24 -24
  163. package/dist/{chunk-7KGXDIWV.js → chunk-OZJ3JLYI.js} +9 -8
  164. package/dist/{chunk-G5BYKJIM.js → chunk-P2VBXX7N.js} +11 -10
  165. package/dist/{chunk-YK6CSODO.js → chunk-PAINMSID.js} +4353 -1106
  166. package/dist/chunk-PKJAV25S.js +43 -0
  167. package/dist/{chunk-R5MNPIMD.js → chunk-PNQLCRSS.js} +4 -4
  168. package/dist/{chunk-3SMMFQED.js → chunk-PQH56YF6.js} +37 -5
  169. package/dist/chunk-PRTBPXYA.js +50 -0
  170. package/dist/{chunk-FF4ZMLSL.js → chunk-PRW3HXM3.js} +2 -2
  171. package/dist/chunk-PRZ7KMWL.js +42 -0
  172. package/dist/{chunk-IFV6MKYV.js → chunk-PTQ3ZALZ.js} +42 -4
  173. package/dist/{chunk-ELQSLAP6.js → chunk-PUEWDYGP.js} +2 -2
  174. package/dist/{chunk-F2ZCZYIK.js → chunk-Q26LDIUV.js} +12 -11
  175. package/dist/chunk-Q46QDDMF.js +277 -0
  176. package/dist/chunk-QBNWF5ZU.js +19 -0
  177. package/dist/{chunk-ANETFNTV.js → chunk-QE527XRY.js} +24 -21
  178. package/dist/{chunk-QNMLLYTN.js → chunk-QHJJMZLX.js} +397 -32
  179. package/dist/chunk-QJCJOMW3.js +425 -0
  180. package/dist/{chunk-3HOONZUE.js → chunk-QKMRFNMA.js} +15 -15
  181. package/dist/{chunk-7S6VJ643.js → chunk-QOOEYDNJ.js} +101 -13
  182. package/dist/{chunk-4YNKN4DF.js → chunk-QSIDVFB7.js} +20 -18
  183. package/dist/{chunk-2IT7SIJY.js → chunk-RA33EYS4.js} +19 -18
  184. package/dist/{chunk-P7NV3FEJ.js → chunk-RALY3E5U.js} +4 -4
  185. package/dist/{chunk-6NESNLVU.js → chunk-S2RY3BOD.js} +35 -21
  186. package/dist/chunk-SB5GRUFM.js +54 -0
  187. package/dist/{chunk-EUQHM2CH.js → chunk-SH27FYPF.js} +18 -18
  188. package/dist/chunk-T74WQQGS.js +40 -0
  189. package/dist/{chunk-LGDW4G73.js → chunk-TGVVUKTG.js} +2 -2
  190. package/dist/chunk-THN26IEF.js +48 -0
  191. package/dist/{chunk-ZV2VCBMQ.js → chunk-TJH2EUAZ.js} +18 -12
  192. package/dist/{chunk-NGT5LLSE.js → chunk-TQ4TG57V.js} +6 -6
  193. package/dist/{chunk-WEDXFJPG.js → chunk-TQTRWENN.js} +12 -12
  194. package/dist/{chunk-ZRWS54RI.js → chunk-TSM6T6GQ.js} +23 -23
  195. package/dist/{chunk-KET7KOVE.js → chunk-TW3KOSQK.js} +15 -7
  196. package/dist/{chunk-RBXDHCN7.js → chunk-TWQRPM76.js} +2 -2
  197. package/dist/{chunk-IO6DQCZX.js → chunk-TYD6I4CZ.js} +4 -4
  198. package/dist/{chunk-5NLJH6R4.js → chunk-U3NBK4EU.js} +56 -17
  199. package/dist/{chunk-U542BJME.js → chunk-UDXWIPCB.js} +7 -7
  200. package/dist/{chunk-OSB6AKF2.js → chunk-UEVZZZU2.js} +4 -4
  201. package/dist/{chunk-5ZV7BK22.js → chunk-UF4UHCIG.js} +2 -2
  202. package/dist/{chunk-PHJOOL6U.js → chunk-UFE36WJH.js} +4 -4
  203. package/dist/{chunk-IHRP4OYV.js → chunk-URIKUID7.js} +12 -12
  204. package/dist/chunk-UYLBRRFK.js +1106 -0
  205. package/dist/chunk-UZACEG2C.js +515 -0
  206. package/dist/{chunk-LR4XBH4L.js → chunk-VAMHOHUU.js} +12 -12
  207. package/dist/{chunk-S5PSGAQ2.js → chunk-VUVGX5L2.js} +1 -1
  208. package/dist/{chunk-2TG2QGKW.js → chunk-VYOCPTUB.js} +18 -16
  209. package/dist/{chunk-VTEDQ6GY.js → chunk-WAZEXEJH.js} +175 -3
  210. package/dist/{chunk-UTSFKZ72.js → chunk-WCEF7H6L.js} +3 -3
  211. package/dist/{chunk-DXPUYG5L.js → chunk-WEOMJHPV.js} +12 -12
  212. package/dist/{chunk-NFKBU2UH.js → chunk-WFU77T3A.js} +5 -5
  213. package/dist/chunk-WIWTJMMR.js +292 -0
  214. package/dist/{chunk-MRFSUAJH.js → chunk-WNTIOBOG.js} +18 -18
  215. package/dist/{chunk-UROTYIGJ.js → chunk-X2CUXTWU.js} +6 -6
  216. package/dist/chunk-X7EMBM5X.js +34 -0
  217. package/dist/{chunk-OO5TKEAG.js → chunk-XEQCQO66.js} +8 -8
  218. package/dist/{chunk-NBQO75XT.js → chunk-XG4WJHLQ.js} +17 -1
  219. package/dist/{chunk-ZQC7MNQB.js → chunk-XOL3GFW2.js} +101 -30
  220. package/dist/{chunk-GXDATVSY.js → chunk-XQ3YZ4QW.js} +22 -22
  221. package/dist/{chunk-GTW54D3D.js → chunk-Y6K3CJBU.js} +726 -88
  222. package/dist/{chunk-CQCAJI6G.js → chunk-YAH5IQAL.js} +5 -5
  223. package/dist/{chunk-U6TD7UOH.js → chunk-YBOVNMHC.js} +51 -14
  224. package/dist/{chunk-OJICVOZH.js → chunk-YF5DZCPN.js} +79 -39
  225. package/dist/chunk-YIGBJNH4.js +37 -0
  226. package/dist/{chunk-7C7OZARH.js → chunk-YJ3TBOTK.js} +25 -25
  227. package/dist/{chunk-HXFSM4LG.js → chunk-YKTE2RU3.js} +2 -2
  228. package/dist/{chunk-7IM4BVVH.js → chunk-YO2PKTXT.js} +268 -36
  229. package/dist/{chunk-Z3UU7XV6.js → chunk-YTTNTQOX.js} +15 -15
  230. package/dist/{chunk-2ZICHO3V.js → chunk-YWAKDNMH.js} +183 -33
  231. package/dist/{chunk-OXOSJXOI.js → chunk-YZDZOEV4.js} +4 -4
  232. package/dist/{chunk-MRCETHW5.js → chunk-Z3QFH7YG.js} +4 -4
  233. package/dist/{chunk-5KZTRD4V.js → chunk-Z6VLC5X4.js} +17 -17
  234. package/dist/{chunk-EDURU5DF.js → chunk-ZFQMMU5Y.js} +2 -2
  235. package/dist/{chunk-3GLDB4IT.js → chunk-ZKYRDV7A.js} +12 -12
  236. package/dist/{chunk-GBGJ6WSO.js → chunk-ZLS6PU2O.js} +3 -3
  237. package/dist/{chunk-JZOOX3PQ.js → chunk-ZOSD4FXV.js} +3 -3
  238. package/dist/chunk-ZPDDEVQX.js +47 -0
  239. package/dist/{chunk-2TTSX7NB.js → chunk-ZQLRCA2V.js} +7 -7
  240. package/dist/{chunk-ESP3RUUR.js → chunk-ZX7235KR.js} +128 -54
  241. package/dist/{chunk-MH4XG5XS.js → chunk-ZZFHLRQW.js} +18 -16
  242. package/dist/files.json +559 -439
  243. package/dist/packages/sdk/npm/index.js +4679 -2672
  244. package/dist/packages/ui/dist/assets/{elk-api-BuFBvNyV.js → elk-api-CowTnhEJ.js} +2 -2
  245. package/dist/packages/ui/dist/assets/index-Dw20IRhQ.css +1 -0
  246. package/dist/packages/ui/dist/assets/index-z2wv0d5d.js +47 -0
  247. package/dist/packages/ui/dist/index.html +2 -2
  248. package/package.json +3 -3
  249. package/dist/chunk-25TJC2CL.js +0 -48
  250. package/dist/chunk-3Q5RHGCA.js +0 -47
  251. package/dist/chunk-4W7FOGAB.js +0 -41
  252. package/dist/chunk-6AF3CVJT.js +0 -48
  253. package/dist/chunk-A3KEXFQS.js +0 -34
  254. package/dist/chunk-BAUINGGJ.js +0 -41
  255. package/dist/chunk-BOFUCRJX.js +0 -47
  256. package/dist/chunk-GGKGILIP.js +0 -54
  257. package/dist/chunk-IKN6C67W.js +0 -53
  258. package/dist/chunk-K7MYVIFK.js +0 -54
  259. package/dist/chunk-KHA5LVPE.js +0 -34
  260. package/dist/chunk-LSXGM6MB.js +0 -37
  261. package/dist/chunk-M54TDRM3.js +0 -29
  262. package/dist/chunk-M5GF4NAB.js +0 -45
  263. package/dist/chunk-UTZGBCXE.js +0 -38
  264. package/dist/chunk-UVTSMBM4.js +0 -43
  265. package/dist/chunk-XXE3S3OG.js +0 -44
  266. package/dist/chunk-YJRQCUVJ.js +0 -43
  267. package/dist/chunk-ZFTK4BDN.js +0 -41
  268. package/dist/chunk-ZMARAW37.js +0 -38
  269. package/dist/packages/ui/dist/assets/index-C5QnK82A.css +0 -1
  270. package/dist/packages/ui/dist/assets/index-CQ0a1cBg.js +0 -29
@@ -10,13 +10,25 @@ import {
10
10
  FlowDelta,
11
11
  STAGE_PHASE,
12
12
  STAGE_SCHEMA_VERSION,
13
+ Stage,
13
14
  StageLoop,
14
15
  fieldNames,
15
16
  parseAt,
16
17
  parseNewFile,
17
18
  rerunOf,
18
19
  usesOf
19
- } from "./chunk-L3T33FA7.js";
20
+ } from "./chunk-HWD4JPU7.js";
21
+ import {
22
+ GAP_STEP,
23
+ candidateRef,
24
+ contextToolBox,
25
+ defaultCodeDir,
26
+ gapNameOf,
27
+ gapQuestion,
28
+ namesInCode,
29
+ nodesById,
30
+ openIndex
31
+ } from "./chunk-5WERBKLE.js";
20
32
  import {
21
33
  CriticError,
22
34
  MERGED_FLOWS,
@@ -42,12 +54,13 @@ import {
42
54
  readRoundMeta,
43
55
  readSnowDrafts,
44
56
  readTasks,
57
+ roundsOf,
45
58
  runCritic,
46
59
  sectionLines,
47
60
  triggerReason,
48
61
  waitingLenses,
49
62
  writeQuarantineItems
50
- } from "./chunk-VEJYZEPC.js";
63
+ } from "./chunk-K332XW7K.js";
51
64
  import {
52
65
  changeFolder,
53
66
  changeFootprint,
@@ -63,20 +76,20 @@ import {
63
76
  scoreRisk,
64
77
  stageCommand,
65
78
  testPlanFor
66
- } from "./chunk-IOW6BF3U.js";
79
+ } from "./chunk-HSZMPH5T.js";
67
80
  import {
68
81
  ARTIFACT_STAGE,
69
82
  artifactsOfStage,
70
83
  createOpenSpec
71
- } from "./chunk-43E6XX67.js";
84
+ } from "./chunk-D2WSW4GU.js";
72
85
  import {
73
86
  AdapterError,
74
87
  createAdapters,
75
88
  fileStepStore
76
- } from "./chunk-V3UEPUHT.js";
89
+ } from "./chunk-4KNOJC6J.js";
77
90
  import {
78
91
  readCatalog
79
- } from "./chunk-EZBAS5BL.js";
92
+ } from "./chunk-4SLBCEPD.js";
80
93
  import {
81
94
  CONFLICT_STEP,
82
95
  checkQuote,
@@ -95,28 +108,17 @@ import {
95
108
  snapshotPages,
96
109
  syncSources,
97
110
  workingDays
98
- } from "./chunk-KCQS52TH.js";
111
+ } from "./chunk-64AADMTW.js";
99
112
  import {
100
113
  changeInputSources,
101
114
  citedParts,
102
115
  heldOutInputs,
103
116
  readServiceRequestExports,
104
117
  readWorkItemExports
105
- } from "./chunk-TNCROWPQ.js";
118
+ } from "./chunk-CPK7QSBG.js";
106
119
  import {
107
120
  readDocument
108
- } from "./chunk-7IM4BVVH.js";
109
- import {
110
- GAP_STEP,
111
- candidateRef,
112
- contextToolBox,
113
- defaultCodeDir,
114
- gapNameOf,
115
- gapQuestion,
116
- namesInCode,
117
- nodesById,
118
- openIndex
119
- } from "./chunk-YXUIUOQZ.js";
121
+ } from "./chunk-YO2PKTXT.js";
120
122
  import {
121
123
  APPLIES_SPEC,
122
124
  ASKS_CHECK_SPEC,
@@ -137,9 +139,10 @@ import {
137
139
  toRunSummary,
138
140
  verdictOf,
139
141
  wrapData
140
- } from "./chunk-QNMLLYTN.js";
142
+ } from "./chunk-QHJJMZLX.js";
141
143
  import {
142
144
  AgentDefinition,
145
+ JOINED_BY_AI,
143
146
  JOIN_GUESS_FLAGS,
144
147
  KNOWLEDGE_KINDS,
145
148
  Procedure,
@@ -149,18 +152,22 @@ import {
149
152
  RecordId,
150
153
  RuntimeKind,
151
154
  ServiceRequestTemplate,
155
+ answerByWords,
152
156
  answerText,
153
157
  answerWords,
154
158
  authorityRank,
155
159
  catalogDirOf,
156
160
  changesDirOf,
157
161
  changesRelOf,
162
+ componentTypeRulesText,
163
+ effectiveComponentTypes,
158
164
  external_exports,
159
165
  factsDirOf,
160
166
  fileJudgments,
161
167
  gateRoundLimit,
162
168
  identifierWords,
163
169
  inputsDirOf,
170
+ isAiBy,
164
171
  isSecretPath,
165
172
  judgmentKey,
166
173
  knowledgeKindOf,
@@ -170,6 +177,7 @@ import {
170
177
  normalizePath,
171
178
  openspecDirOf,
172
179
  orgPackDirOf,
180
+ orgPackRelOf,
173
181
  overrideJudgment,
174
182
  ownerOfQuestion,
175
183
  proposalsDirOf,
@@ -194,10 +202,10 @@ import {
194
202
  taskBranch,
195
203
  toCanonicalYaml,
196
204
  writeQuestion
197
- } from "./chunk-YK6CSODO.js";
205
+ } from "./chunk-PAINMSID.js";
198
206
  import {
199
207
  writeFileAtomic
200
- } from "./chunk-MMB2OHUY.js";
208
+ } from "./chunk-ODMBPDDV.js";
201
209
  import {
202
210
  __toESM
203
211
  } from "./chunk-TV45EUGM.js";
@@ -236,10 +244,10 @@ function stem(word) {
236
244
  w = w.slice(0, -1);
237
245
  return w;
238
246
  }
239
- function contentWords(text2) {
247
+ function contentWords(text4) {
240
248
  const out = [];
241
249
  const seen = /* @__PURE__ */ new Set();
242
- for (const raw of text2.replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9']+/)) {
250
+ for (const raw of text4.replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9']+/)) {
243
251
  const w = raw.replace(/^'+|'+$/g, "");
244
252
  if (!w || STOP.has(w) || /^\d+$/.test(w))
245
253
  continue;
@@ -298,12 +306,12 @@ function readRequirements(files) {
298
306
  then
299
307
  });
300
308
  }
301
- const text2 = doc.lines.slice(req.line - 1, req.end).map((l) => strip(l.replace(/^#+\s*/, ""))).filter(Boolean).join("\n");
309
+ const text4 = doc.lines.slice(req.line - 1, req.end).map((l) => strip(l.replace(/^#+\s*/, ""))).filter(Boolean).join("\n");
302
310
  out.push({
303
311
  file: doc.path,
304
312
  name: req.heading.replace(/^requirement:\s*/i, "").trim(),
305
313
  line: req.line,
306
- text: text2,
314
+ text: text4,
307
315
  scenarios
308
316
  });
309
317
  }
@@ -363,7 +371,7 @@ async function loadPlanContext(root, changeId, o = {}) {
363
371
  return systemOf2(t.dataStore);
364
372
  return void 0;
365
373
  };
366
- const repoOf = (component) => get("Component", component)?.repo ?? manifestRepos.find((r) => r.component === component)?.id;
374
+ const repoOf2 = (component) => get("Component", component)?.repo ?? manifestRepos.find((r) => r.component === component)?.id;
367
375
  const runtime = lifecycle.orgPack.runtime;
368
376
  const intro = docOf(files, "proposal.md");
369
377
  const questions = [];
@@ -396,7 +404,7 @@ async function loadPlanContext(root, changeId, o = {}) {
396
404
  boundaryOf: (s) => get("System", s)?.boundary ?? profileSystems.get(s)?.boundary,
397
405
  systemName: (s) => get("System", s)?.name ?? profileSystems.get(s)?.name ?? s,
398
406
  isKnown: (id) => get("Component", id) !== void 0 || get("System", id) !== void 0 || profileSystems.has(id) || endpointOwners.has(id),
399
- repoOf,
407
+ repoOf: repoOf2,
400
408
  componentOfRepo: (repo) => records.find((r) => r.kind === "Component" && r.repo === repo)?.id ?? manifestRepos.find((r) => r.id === repo)?.component,
401
409
  knownRepos: /* @__PURE__ */ new Set([
402
410
  ...manifestRepos.map((r) => r.id),
@@ -440,9 +448,9 @@ function requirementIsAboutHop(ctx, r, h) {
440
448
  }
441
449
  function requirementMentionsData(r, d) {
442
450
  const flat = (s) => ` ${words(s).replace(/[^a-z0-9]+/g, " ").trim()} `;
443
- const text2 = flat(r.text);
451
+ const text4 = flat(r.text);
444
452
  const names = d.newTable ? [d.table] : d.newColumns.map((c) => c.name);
445
- return names.some((n) => text2.includes(flat(n)));
453
+ return names.some((n) => text4.includes(flat(n)));
446
454
  }
447
455
  var escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
448
456
  function hopKey(ctx, h) {
@@ -451,10 +459,10 @@ function hopKey(ctx, h) {
451
459
  const last = seg ? slug(seg) : slug(h.operation);
452
460
  return last.startsWith(slug(sys)) ? last : `${slug(sys)}-${last}`;
453
461
  }
454
- function traceToProposal(ctx, text2) {
462
+ function traceToProposal(ctx, text4) {
455
463
  let best;
456
464
  for (const b of ctx.changes) {
457
- const n = overlap(text2, b.text);
465
+ const n = overlap(text4, b.text);
458
466
  if (n >= 2 && (!best || n > best.n))
459
467
  best = { ...b, n };
460
468
  }
@@ -518,7 +526,7 @@ function buildSkeleton(ctx) {
518
526
  material: []
519
527
  });
520
528
  const known = (h) => ctx.isKnown(h.callee) && ctx.isKnown(h.caller);
521
- const named = new Set(hops.filter((h) => known(h) && ctx.requirements.some((r) => requirementMentionsHop(ctx, r, h))));
529
+ const named2 = new Set(hops.filter((h) => known(h) && ctx.requirements.some((r) => requirementMentionsHop(ctx, r, h))));
522
530
  for (const r of ctx.requirements) {
523
531
  const s = blank(slug(r.name), "requirement");
524
532
  s.requirement = r;
@@ -531,7 +539,7 @@ function buildSkeleton(ctx) {
531
539
  }
532
540
  if (s.hops.length === 0) {
533
541
  for (const h of hops)
534
- if (known(h) && !named.has(h) && requirementIsAboutHop(ctx, r, h)) {
542
+ if (known(h) && !named2.has(h) && requirementIsAboutHop(ctx, r, h)) {
535
543
  s.hops.push(h);
536
544
  used.add(h);
537
545
  }
@@ -663,12 +671,12 @@ ${m}`);
663
671
  uiRepos.add(repo);
664
672
  }
665
673
  for (const s of stories) {
666
- const text2 = s.hops.map((h) => ctx.mapping(h)).join("\n").toLowerCase();
674
+ const text4 = s.hops.map((h) => ctx.mapping(h)).join("\n").toLowerCase();
667
675
  for (const o of stories) {
668
676
  if (o === s)
669
677
  continue;
670
678
  const adds = o.data.flatMap((d) => d.newColumns.map((c) => `${d.table.toLowerCase()}.${c.name.toLowerCase()}`));
671
- if (adds.some((a) => text2.includes(a)))
679
+ if (adds.some((a) => text4.includes(a)))
672
680
  s.dependsOn.push(o.id);
673
681
  }
674
682
  }
@@ -879,13 +887,13 @@ function storyPack(ctx, skeleton, agent, now) {
879
887
  }
880
888
  ];
881
889
  const add = (rel, priority) => {
882
- const text2 = doc(rel);
883
- if (text2)
890
+ const text4 = doc(rel);
891
+ if (text4)
884
892
  sources.push({
885
893
  id: rel,
886
894
  kind: "artifact",
887
895
  origin: `${changesRelOf(ctx.root)}/${ctx.change.id}/${rel}`,
888
- text: text2,
896
+ text: text4,
889
897
  classification: "internal",
890
898
  priority
891
899
  });
@@ -1280,6 +1288,98 @@ function readDrafts(ctx) {
1280
1288
  return [{ template: t, record, fields: fields2, heading: d.heading }];
1281
1289
  });
1282
1290
  }
1291
+ var SnowFillError = class extends Error {
1292
+ constructor(message) {
1293
+ super(message);
1294
+ this.name = "SnowFillError";
1295
+ }
1296
+ };
1297
+ var rowCells = (line) => {
1298
+ let s = line.trim();
1299
+ if (s.startsWith("|"))
1300
+ s = s.slice(1);
1301
+ if (s.endsWith("|"))
1302
+ s = s.slice(0, -1);
1303
+ return s.split("|").map((c) => c.trim());
1304
+ };
1305
+ function fillSnowFields(text4, heading, values) {
1306
+ const given = Object.entries(values).map(([name, v]) => [name.trim(), v.trim()]);
1307
+ if (!given.length)
1308
+ throw new SnowFillError("Give at least one field to fill.");
1309
+ for (const [name, v] of given)
1310
+ if (!name || /[|\r\n]/.test(v) || /[|\r\n]/.test(name))
1311
+ throw new SnowFillError(`${name || "A field"}: a value can't hold | or a line break (snow.md keeps it in a table cell).`);
1312
+ const eol = text4.includes("\r\n") ? "\r\n" : "\n";
1313
+ const doc = parseDoc("snow.md", text4);
1314
+ const raw = doc.rawLines;
1315
+ const s = doc.sections.find((x) => x.level === 2 && x.heading === heading);
1316
+ if (!s)
1317
+ throw new SnowFillError(`snow.md has no request "${heading}". Reload the page to see its requests.`);
1318
+ const table = doc.tables.find((t) => t.line >= s.line && t.line <= s.end && t.headers.includes("field"));
1319
+ if (!table || !table.headers.includes("value"))
1320
+ throw new SnowFillError(`"${heading}" in snow.md has no Field | Value table to fill.`);
1321
+ const fieldAt = table.headers.indexOf("field");
1322
+ const valueAt2 = table.headers.indexOf("value");
1323
+ const edited = /* @__PURE__ */ new Map();
1324
+ const added = [];
1325
+ const row = (cells) => `| ${cells.join(" | ")} |`;
1326
+ for (const [name, v] of given) {
1327
+ const r = table.rows.find((x) => (x.cells.field ?? "").toLowerCase() === name.toLowerCase());
1328
+ if (!r) {
1329
+ const cells2 = table.headers.map(() => "");
1330
+ cells2[fieldAt] = name;
1331
+ cells2[valueAt2] = v;
1332
+ added.push(row(cells2));
1333
+ continue;
1334
+ }
1335
+ const at2 = r.line - 1;
1336
+ if (raw[at2].includes("<!--"))
1337
+ throw new SnowFillError(`${name}'s row in snow.md holds a comment: edit that row there.`);
1338
+ const cells = rowCells(raw[at2]);
1339
+ while (cells.length < table.headers.length)
1340
+ cells.push("");
1341
+ cells[valueAt2] = v;
1342
+ edited.set(at2, row(cells));
1343
+ }
1344
+ const filled = given.filter(([, v]) => v).map(([n]) => n.toLowerCase());
1345
+ const drop = /* @__PURE__ */ new Set();
1346
+ const lines = doc.lines;
1347
+ for (let i = s.line; i < s.end; i++) {
1348
+ if (lines[i].trim() !== "To fill:")
1349
+ continue;
1350
+ let j = i + 1;
1351
+ const bullets2 = [];
1352
+ for (; j < s.end; j++) {
1353
+ const l = lines[j].trim();
1354
+ if (!l)
1355
+ continue;
1356
+ if (!l.startsWith("- "))
1357
+ break;
1358
+ bullets2.push(j);
1359
+ }
1360
+ for (const b of bullets2) {
1361
+ const l = lines[b].trim().slice(2).toLowerCase();
1362
+ if (filled.some((n) => l.startsWith(`${n}:`)))
1363
+ drop.add(b);
1364
+ }
1365
+ if (bullets2.length && bullets2.every((b) => drop.has(b))) {
1366
+ const last = bullets2[bullets2.length - 1];
1367
+ for (let k = i; k <= last; k++)
1368
+ drop.add(k);
1369
+ if (last + 1 < raw.length && !raw[last + 1].trim() && last + 1 < s.end)
1370
+ drop.add(last + 1);
1371
+ }
1372
+ }
1373
+ const lastRow = table.rows.length ? table.rows[table.rows.length - 1].line - 1 : table.line;
1374
+ const out = [];
1375
+ raw.forEach((l, i) => {
1376
+ if (!drop.has(i))
1377
+ out.push(edited.get(i) ?? l);
1378
+ if (i === lastRow)
1379
+ out.push(...added);
1380
+ });
1381
+ return out.join(eol);
1382
+ }
1283
1383
 
1284
1384
  // packages/planner/dist/plan.js
1285
1385
  import { readdir, readFile, rm } from "node:fs/promises";
@@ -1295,8 +1395,8 @@ async function guard(ctx, rels, force) {
1295
1395
  const p = join(ctx.dir, rel);
1296
1396
  if (!await exists(p))
1297
1397
  continue;
1298
- const text2 = await readFile(p, "utf8");
1299
- if (!text2.startsWith(GENERATED_MARK))
1398
+ const text4 = await readFile(p, "utf8");
1399
+ if (!text4.startsWith(GENERATED_MARK))
1300
1400
  own.push(rel);
1301
1401
  }
1302
1402
  if (own.length)
@@ -1430,15 +1530,19 @@ var body = (doc, re) => {
1430
1530
  };
1431
1531
  var bullets = (lines = []) => lines.map((l) => /^\s*[-*]\s+(.*)$/.exec(l)?.[1]?.trim()).filter((x) => !!x);
1432
1532
  var list = (s) => s.split(/[,;]/).map((x) => x.trim()).filter((x) => x && !/^(none|nothing)\.?$/i.test(x));
1433
- function parseStory(path, text2) {
1434
- const doc = parseDoc(path, text2);
1533
+ function parseStory(path, text4) {
1534
+ const doc = parseDoc(path, text4);
1435
1535
  const id = path.replace(/^stories\//, "").replace(/\.md$/, "");
1436
1536
  const h1 = doc.sections.find((s) => s.level === 1);
1437
1537
  const firstH2 = doc.sections.find((s) => s.level === 2);
1438
1538
  const intro = h1 ? doc.lines.slice(h1.line, firstH2 ? firstH2.line - 1 : doc.lines.length).join(" ") : "";
1439
1539
  const w = /\bas an?\s+(.+?)\s*,?\s+i want\s+(.+?)\s*,?\s+so that\s+(.+?)\s*$/i.exec(intro.replace(/\s+/g, " ").trim());
1440
- const sysLine = (body(doc, /^systems and repos$/i) ?? []).join(" ");
1540
+ const sysSection = section(doc, /^systems and repos$/i);
1541
+ const sysLines = sysSection ? sectionLines(doc, sysSection) : [];
1542
+ const sysEnd = sysLines.findIndex((l, i) => i > 0 && l.n !== sysLines[i - 1].n + 1);
1543
+ const sysLine = sysLines.slice(0, sysEnd < 0 ? void 0 : sysEnd).map((l) => l.text).join(" ").trim();
1441
1544
  const m = /^(.*?)(?:\((.*)\))?\s*$/.exec(sysLine);
1545
+ const parens = !!m?.[2];
1442
1546
  const links2 = bullets(body(doc, /^links$/i));
1443
1547
  const interfaces = links2.flatMap((l) => {
1444
1548
  const x = /^Interface\s+(\S+):\s*(\S+)\s*→\s*(\S+)\s+([A-Z]+\s+\S+)/.exec(l);
@@ -1459,6 +1563,7 @@ function parseStory(path, text2) {
1459
1563
  ...body(doc, /^traces to$/i)?.[0] ? { traces: body(doc, /^traces to$/i)[0].trim() } : {},
1460
1564
  systems: list(m?.[1] ?? ""),
1461
1565
  repos: list(m?.[2] ?? ""),
1566
+ ...sysLine && !parens ? { bare: true } : {},
1462
1567
  tests: list((body(doc, /^tests$/i) ?? []).join(" ")),
1463
1568
  ...dep ? { dependsOn: list(dep.join(" ")) } : {},
1464
1569
  interfaces,
@@ -1529,6 +1634,8 @@ function knownNames(ctx, storyIds) {
1529
1634
  const names = /* @__PURE__ */ new Set([
1530
1635
  ...ctx.records.map((r) => r.id),
1531
1636
  ...storyIds,
1637
+ // The change's own id: the stage names it in each task's prompt line.
1638
+ ctx.change.id,
1532
1639
  ...systems,
1533
1640
  ...ctx.knownRepos
1534
1641
  ]);
@@ -1559,6 +1666,18 @@ function knownNames(ctx, storyIds) {
1559
1666
  for (const g of n.text.matchAll(/\bgw-[a-z0-9-]+\b/g))
1560
1667
  names.add(g[0]);
1561
1668
  }
1669
+ const design = [
1670
+ ...(ctx.files.flows ?? []).map((d) => d.raw),
1671
+ ctx.files.docs.get("design.md")?.raw ?? ""
1672
+ ];
1673
+ for (const text4 of design) {
1674
+ for (const m of text4.matchAll(COMPONENT_LIKE))
1675
+ names.add(m[0]);
1676
+ for (const m of text4.matchAll(/\bcomponent:\s*([a-z0-9][a-z0-9._-]*)/g))
1677
+ names.add(m[1]);
1678
+ for (const m of text4.matchAll(OPERATION))
1679
+ operations.add(op(m[1], m[2].replace(/[.:'"]+$/, "")));
1680
+ }
1562
1681
  const records = /* @__PURE__ */ new Set([
1563
1682
  ...ctx.records.filter((r) => r.kind === "Table" || r.kind === "Column").map((r) => r.id),
1564
1683
  ...ctx.data.map((d) => d.tableId)
@@ -1579,12 +1698,39 @@ function knownNames(ctx, storyIds) {
1579
1698
  }
1580
1699
  var COMPONENT_LIKE = /(?<![\w-])[a-z][a-z0-9]*(?:-[a-z0-9]+)*-(?:api|svc|service|bff|ui|db|job|engine|gateway|app|worker)(?![\w-])/g;
1581
1700
  var OPERATION = /\b(GET|POST|PUT|PATCH|DELETE|HEAD)\s+(\/[^\s,;)]*)/g;
1701
+ function named(s, known) {
1702
+ if (!s.bare)
1703
+ return { systems: s.systems, repos: s.repos };
1704
+ return {
1705
+ systems: s.systems.filter((x) => !known.repos.has(x)),
1706
+ repos: s.systems.filter((x) => known.repos.has(x))
1707
+ };
1708
+ }
1709
+ function knownOperation(method, path, known) {
1710
+ const key3 = `${method} ${normalizePath2(path)}`;
1711
+ if (known.has(key3))
1712
+ return true;
1713
+ const want = normalizePath2(path).split("/");
1714
+ const wild = (x) => x === "{}" || x === "*" || x === "**";
1715
+ for (const k of known) {
1716
+ const sp = k.indexOf(" ");
1717
+ if (k.slice(0, sp) !== method)
1718
+ continue;
1719
+ const have = k.slice(sp + 1).split("/");
1720
+ if (have.length !== want.length)
1721
+ continue;
1722
+ if (have.every((h, i) => h === want[i] || wild(h) || wild(want[i])))
1723
+ return true;
1724
+ }
1725
+ return false;
1726
+ }
1582
1727
  function invented(s, known) {
1583
1728
  const out = /* @__PURE__ */ new Set();
1584
- for (const x of s.systems)
1729
+ const n = named(s, known);
1730
+ for (const x of n.systems)
1585
1731
  if (!known.systems.has(x))
1586
1732
  out.add(`system ${x}`);
1587
- for (const x of s.repos)
1733
+ for (const x of n.repos)
1588
1734
  if (!known.repos.has(x))
1589
1735
  out.add(`repo ${x}`);
1590
1736
  for (const r of s.records)
@@ -1604,11 +1750,9 @@ function invented(s, known) {
1604
1750
  for (const m of prose.matchAll(COMPONENT_LIKE))
1605
1751
  if (!known.names.has(m[0]))
1606
1752
  out.add(m[0]);
1607
- for (const m of prose.matchAll(OPERATION)) {
1608
- const key3 = `${m[1]} ${normalizePath2(m[2].replace(/[.:]+$/, ""))}`;
1609
- if (!known.operations.has(key3))
1753
+ for (const m of prose.matchAll(OPERATION))
1754
+ if (!knownOperation(m[1], m[2].replace(/[.:'"]+$/, ""), known.operations))
1610
1755
  out.add(`${m[1]} ${m[2]}`);
1611
- }
1612
1756
  return [...out].sort();
1613
1757
  }
1614
1758
  function checkStory(ctx, s, checks, known) {
@@ -1619,10 +1763,18 @@ function checkStory(ctx, s, checks, known) {
1619
1763
  if (checks.has("traced")) {
1620
1764
  const m = s.traces ? /^(\S+?):(\d+)/.exec(s.traces) : null;
1621
1765
  const doc = m ? ctx.files.docs.get(m[1]) : void 0;
1622
- if (!m)
1766
+ const paths = m ? [] : (s.traces ?? "").split(";").map((p) => /^\s*((?:target-flows|specs)\/\S+\.(?:ya?ml|md))\b/.exec(p)?.[1]).filter((p) => !!p);
1767
+ const inChange = (p) => ctx.files.docs.has(p) || (ctx.files.flows ?? []).some((f) => f.path === p);
1768
+ if (m) {
1769
+ if (!doc || Number(m[2]) > doc.lines.length)
1770
+ problems.push(`traces to ${s.traces}, which isn't in the change`);
1771
+ } else if (paths.length === 0)
1623
1772
  problems.push("doesn't trace to the brief, proposal or a spec");
1624
- else if (!doc || Number(m[2]) > doc.lines.length)
1625
- problems.push(`traces to ${s.traces}, which isn't in the change`);
1773
+ else {
1774
+ const missing = paths.filter((p) => !inChange(p));
1775
+ if (missing.length)
1776
+ problems.push(`traces to ${missing.join(", ")}, which isn't in the change`);
1777
+ }
1626
1778
  }
1627
1779
  if (checks.has("criteria")) {
1628
1780
  const gwt = s.criteria.filter((c) => GWT.test(c));
@@ -1632,9 +1784,10 @@ function checkStory(ctx, s, checks, known) {
1632
1784
  wording.push(`criterion not in Given/When/Then form: ${c.slice(0, 60)}`);
1633
1785
  }
1634
1786
  if (checks.has("systems")) {
1635
- if (s.systems.length === 0)
1787
+ const n = named(s, known);
1788
+ if (n.systems.length === 0 && n.repos.length === 0)
1636
1789
  problems.push("names no system");
1637
- if (s.repos.length === 0)
1790
+ if (n.repos.length === 0)
1638
1791
  problems.push("names no repo");
1639
1792
  }
1640
1793
  if (checks.has("depends") && !s.dependsOn)
@@ -2104,8 +2257,8 @@ import { join as join4, relative, sep } from "node:path";
2104
2257
  var PROPOSALS_DIR = "proposals/templates";
2105
2258
  var HOW_TO_TITLE = /^how to\s+(?:request|raise|ask for|get|open|submit|create|file)\s+(?:an?\s+|the\s+)?(.+?)\s*$/i;
2106
2259
  var ref = (h, n) => h.lineRefs ? `${h.path}:${n}` : h.path;
2107
- function linesOf(text2) {
2108
- return text2.split(/\r?\n/).map((t, i) => ({ n: i + 1, text: t }));
2260
+ function linesOf(text4) {
2261
+ return text4.split(/\r?\n/).map((t, i) => ({ n: i + 1, text: t }));
2109
2262
  }
2110
2263
  function isHowTo(h) {
2111
2264
  return HOW_TO_TITLE.test(h.title) || h.lines.some((l) => /\bfields?\s*:/i.test(l.text));
@@ -2601,11 +2754,11 @@ async function uploads(root, inputsDir, skip) {
2601
2754
  const bytes = new Uint8Array(await readFile4(full));
2602
2755
  const doc = readDocument(e.name, bytes);
2603
2756
  const plain2 = /\.(md|markdown|txt)$/i.test(e.name);
2604
- const text2 = plain2 ? new TextDecoder().decode(bytes) : doc.text.sections.map((s) => [s.heading ? `# ${s.heading}` : "", s.text].filter(Boolean).join("\n")).join("\n");
2757
+ const text4 = plain2 ? new TextDecoder().decode(bytes) : doc.text.sections.map((s) => [s.heading ? `# ${s.heading}` : "", s.text].filter(Boolean).join("\n")).join("\n");
2605
2758
  out.push({
2606
2759
  path: relative(root, full).split(sep).join("/"),
2607
2760
  title: doc.text.title,
2608
- lines: linesOf(text2),
2761
+ lines: linesOf(text4),
2609
2762
  kind: "upload",
2610
2763
  lineRefs: plain2
2611
2764
  });
@@ -2635,13 +2788,13 @@ async function draftTemplates(root, o = {}) {
2635
2788
  if (s.type === "markdown-folder")
2636
2789
  wikiDirs.push(join4(orgPackDir, s.location));
2637
2790
  for (const p of wiki.pages) {
2638
- const text2 = await readFile4(join4(root, p.path), "utf8").catch(() => void 0);
2639
- if (text2 === void 0)
2791
+ const text4 = await readFile4(join4(root, p.path), "utf8").catch(() => void 0);
2792
+ if (text4 === void 0)
2640
2793
  continue;
2641
2794
  wikiHowTos.push({
2642
2795
  path: p.path,
2643
2796
  title: p.title,
2644
- lines: linesOf(text2),
2797
+ lines: linesOf(text4),
2645
2798
  kind: "wiki",
2646
2799
  lineRefs: true
2647
2800
  });
@@ -2734,8 +2887,8 @@ async function draftTemplates(root, o = {}) {
2734
2887
  });
2735
2888
  }
2736
2889
  const files = [];
2737
- const write3 = async (rel, text2) => {
2738
- await writeFileAtomic(join4(proposalsDirOf(root), "templates", rel), text2);
2890
+ const write3 = async (rel, text4) => {
2891
+ await writeFileAtomic(join4(proposalsDirOf(root), "templates", rel), text4);
2739
2892
  files.push(`${proposalsRelOf(root)}/templates/${rel}`);
2740
2893
  };
2741
2894
  for (const d of drafts)
@@ -2831,9 +2984,9 @@ function stem2(w) {
2831
2984
  s = s.slice(0, -1);
2832
2985
  return s;
2833
2986
  }
2834
- function words2(text2) {
2987
+ function words2(text4) {
2835
2988
  const out = /* @__PURE__ */ new Set();
2836
- for (const w of text2.split(/[^A-Za-z0-9]+/)) {
2989
+ for (const w of text4.split(/[^A-Za-z0-9]+/)) {
2837
2990
  if (w.length < 3 || /^\d+$/.test(w))
2838
2991
  continue;
2839
2992
  for (const p of w.split(/(?<=[a-z])(?=[A-Z])/)) {
@@ -2860,9 +3013,9 @@ function relevantMap(records, brief, max = 60) {
2860
3013
  want.set(w, 3);
2861
3014
  const all2 = records;
2862
3015
  const byId = new Map(all2.map((r) => [r.id, r]));
2863
- const named = new Set(all2.filter((r) => new RegExp(`\\b${r.id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`).test(brief)).map((r) => r.id));
2864
- const top = (kind, text2, n) => {
2865
- const pool = all2.filter((r) => r.kind === kind).map((r) => ({ r, w: words2(text2(r)) }));
3016
+ const named2 = new Set(all2.filter((r) => new RegExp(`\\b${r.id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`).test(brief)).map((r) => r.id));
3017
+ const top = (kind, text4, n) => {
3018
+ const pool = all2.filter((r) => r.kind === kind).map((r) => ({ r, w: words2(text4(r)) }));
2866
3019
  const df = /* @__PURE__ */ new Map();
2867
3020
  for (const p of pool)
2868
3021
  for (const w of p.w)
@@ -2873,7 +3026,7 @@ function relevantMap(records, brief, max = 60) {
2873
3026
  };
2874
3027
  const scored = pool.map((p) => ({
2875
3028
  r: p.r,
2876
- s: [...p.w].filter((w) => want.has(w)).reduce((a, w) => a + weight(w) * want.get(w), 0) + (named.has(p.r.id) ? 10 : 0)
3029
+ s: [...p.w].filter((w) => want.has(w)).reduce((a, w) => a + weight(w) * want.get(w), 0) + (named2.has(p.r.id) ? 10 : 0)
2877
3030
  })).filter((x) => x.s > 0);
2878
3031
  const best = Math.max(0, ...scored.map((x) => x.s));
2879
3032
  return scored.filter((x) => x.s >= best * 0.5).sort((a, b) => b.s - a.s || a.r.id.localeCompare(b.r.id)).slice(0, n).map((x) => x.r);
@@ -2895,7 +3048,7 @@ function relevantMap(records, brief, max = 60) {
2895
3048
  for (const e of endpoints.values())
2896
3049
  if (str(e.component))
2897
3050
  components.add(str(e.component));
2898
- for (const id of named)
3051
+ for (const id of named2)
2899
3052
  if (byId.get(id)?.kind === "Component")
2900
3053
  components.add(id);
2901
3054
  const calls = all2.filter((r) => r.kind === "Interface" && (str(r.endpoint) && endpoints.has(str(r.endpoint)) || [...endpoints.values()].some((e) => str(e.component) === str(r.to) && str(e.method) === str(r.method) && (str(e.pathOrTopic) ?? "").replace(/\{[^}]*\}/g, "{}") === (str(r.normalizedPath) ?? "").replace(/\{[^}]*\}/g, "{}"))));
@@ -2913,7 +3066,7 @@ function relevantMap(records, brief, max = 60) {
2913
3066
  if (s)
2914
3067
  systems.add(s);
2915
3068
  }
2916
- for (const id of named)
3069
+ for (const id of named2)
2917
3070
  if (byId.get(id)?.kind === "System")
2918
3071
  systems.add(id);
2919
3072
  const lines = [
@@ -2949,7 +3102,7 @@ function relevantMap(records, brief, max = 60) {
2949
3102
  lines.push("Nothing in the catalog shares a word with the brief: name systems or components by their catalog id in the brief's Systems section.");
2950
3103
  return { text: lines.join("\n"), ids };
2951
3104
  }
2952
- function parseDraft(text2) {
3105
+ function parseDraft(text4) {
2953
3106
  const files = /* @__PURE__ */ new Map();
2954
3107
  let path;
2955
3108
  let body2 = [];
@@ -2958,7 +3111,7 @@ function parseDraft(text2) {
2958
3111
  files.set(path, `${body2.join("\n").trim()}
2959
3112
  `);
2960
3113
  };
2961
- for (const line of text2.replace(/\r\n/g, "\n").split("\n")) {
3114
+ for (const line of text4.replace(/\r\n/g, "\n").split("\n")) {
2962
3115
  const m = /^===\s*(?:file:\s*)?(\S+)\s*(?:===)?\s*$/.exec(line);
2963
3116
  if (m) {
2964
3117
  flush();
@@ -3149,8 +3302,8 @@ async function draftChange(root, changeId, o) {
3149
3302
  };
3150
3303
  const own = [];
3151
3304
  const isOwn = async (rel) => {
3152
- const text2 = await readFile5(join6(dir, rel), "utf8").catch(() => void 0);
3153
- return text2 !== void 0 && !text2.startsWith(DRAFT_MARK);
3305
+ const text4 = await readFile5(join6(dir, rel), "utf8").catch(() => void 0);
3306
+ return text4 !== void 0 && !text4.startsWith(DRAFT_MARK);
3154
3307
  };
3155
3308
  for (const rel of ["proposal.md", "interfaces.md"])
3156
3309
  if (!o.force && await isOwn(rel))
@@ -3205,10 +3358,10 @@ async function draftChange(root, changeId, o) {
3205
3358
 
3206
3359
  `;
3207
3360
  const files = [];
3208
- for (const [rel, text2] of [...answer].sort(([a], [b]) => a.localeCompare(b))) {
3361
+ for (const [rel, text4] of [...answer].sort(([a], [b]) => a.localeCompare(b))) {
3209
3362
  const path = join6(dir, rel);
3210
3363
  await mkdir(dirname(path), { recursive: true });
3211
- await writeFileAtomic(path, stamp2 + text2);
3364
+ await writeFileAtomic(path, stamp2 + text4);
3212
3365
  files.push(rel);
3213
3366
  }
3214
3367
  return { ...base, state: "written", files, discarded, problems: [] };
@@ -3400,7 +3553,7 @@ var ESTATE_KINDS = ["System", "Component", "Capability", "Flow", "Endpoint", "Ta
3400
3553
  var ESTATE_HEADING = "The estate, from the repos' specs and the scanned catalog. Ids are exact: name only these, or say something is new.";
3401
3554
  function scopeRepos(e, touched) {
3402
3555
  const recs = e.records;
3403
- const repoOf = (id) => str2(e.byId.get(str2(id) ?? "")?.repo);
3556
+ const repoOf2 = (id) => str2(e.byId.get(str2(id) ?? "")?.repo);
3404
3557
  const out = new Set(touched);
3405
3558
  const systems = new Set(recs.filter((r) => r.kind === "Component" && out.has(str2(r.repo) ?? "")).flatMap((r) => str2(r.system) ? [str2(r.system)] : []));
3406
3559
  for (const r of recs)
@@ -3410,8 +3563,8 @@ function scopeRepos(e, touched) {
3410
3563
  for (const r of recs) {
3411
3564
  if (r.kind !== "Interface")
3412
3565
  continue;
3413
- const callee = repoOf(r.to) ?? repoOf(e.byId.get(str2(r.endpoint) ?? "")?.component);
3414
- const caller = repoOf(r.from);
3566
+ const callee = repoOf2(r.to) ?? repoOf2(e.byId.get(str2(r.endpoint) ?? "")?.component);
3567
+ const caller = repoOf2(r.from);
3415
3568
  if (caller && seed.has(caller) && callee)
3416
3569
  out.add(callee);
3417
3570
  if (callee && seed.has(callee) && caller)
@@ -3539,9 +3692,9 @@ function systemOf(e, id) {
3539
3692
  }
3540
3693
 
3541
3694
  // packages/planner/dist/stages/brief-endpoints.js
3542
- function endpointsIn(text2) {
3695
+ function endpointsIn(text4) {
3543
3696
  const out = [];
3544
- for (const m of text2.matchAll(/\b(GET|POST|PUT|PATCH|DELETE)\s+(\/[^\s`'",;)\]]*)/g)) {
3697
+ for (const m of text4.matchAll(/\b(GET|POST|PUT|PATCH|DELETE)\s+(\/[^\s`'",;)\]]*)/g)) {
3545
3698
  const e = `${m[1]} ${m[2].replace(/[.:]+$/, "")}`;
3546
3699
  if (!out.some((x) => endpointKey(x) === endpointKey(e)))
3547
3700
  out.push(e);
@@ -3642,8 +3795,8 @@ function readFigma(json) {
3642
3795
  screen.fields.push(f);
3643
3796
  ids.add(f.figmaId);
3644
3797
  };
3645
- const message = (figmaId, text2) => {
3646
- screen.messages.push({ figmaId, text: text2 });
3798
+ const message = (figmaId, text4) => {
3799
+ screen.messages.push({ figmaId, text: text4 });
3647
3800
  ids.add(figmaId);
3648
3801
  };
3649
3802
  const visit = (n) => {
@@ -3812,10 +3965,10 @@ function designGaps(d) {
3812
3965
  }
3813
3966
 
3814
3967
  // packages/planner/dist/stages/loop.js
3815
- var import_yaml9 = __toESM(require_dist(), 1);
3816
- import { createHash as createHash3 } from "node:crypto";
3817
- import { copyFile, mkdir as mkdir3, readdir as readdir7, readFile as readFile8, rm as rm3, stat } from "node:fs/promises";
3818
- import { join as join10 } from "node:path";
3968
+ var import_yaml10 = __toESM(require_dist(), 1);
3969
+ import { createHash as createHash4 } from "node:crypto";
3970
+ import { copyFile, mkdir as mkdir3, readdir as readdir7, readFile as readFile9, rm as rm3, stat } from "node:fs/promises";
3971
+ import { join as join11 } from "node:path";
3819
3972
 
3820
3973
  // packages/planner/dist/stages/cite.js
3821
3974
  var escape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -3901,9 +4054,9 @@ function issueWords(issue, value) {
3901
4054
  }
3902
4055
  return `${where2}: ${issue.message}.`;
3903
4056
  }
3904
- function schemaProblems(file, text2, issues) {
4057
+ function schemaProblems(file, text4, issues) {
3905
4058
  const lc = new import_yaml5.LineCounter();
3906
- const doc = (0, import_yaml5.parseDocument)(text2, { lineCounter: lc, keepSourceTokens: false });
4059
+ const doc = (0, import_yaml5.parseDocument)(text4, { lineCounter: lc, keepSourceTokens: false });
3907
4060
  const data = doc.toJS();
3908
4061
  return issues.map((i) => {
3909
4062
  let line = 1;
@@ -3926,12 +4079,12 @@ function schemaProblems(file, text2, issues) {
3926
4079
  }
3927
4080
 
3928
4081
  // packages/planner/dist/stages/answer.js
3929
- var unfence = (text2) => text2.replace(/\r\n/g, "\n").replace(/^\s*```(?:ya?ml)?\s*\n/, "").replace(/\n```\s*$/, "");
4082
+ var unfence = (text4) => text4.replace(/\r\n/g, "\n").replace(/^\s*```(?:ya?ml)?\s*\n/, "").replace(/\n```\s*$/, "");
3930
4083
  var YAML_QUOTE_RULE = "quote any value containing { } [ ] : or # in double quotes";
3931
4084
  var capitalQuote = `${YAML_QUOTE_RULE.charAt(0).toUpperCase()}${YAML_QUOTE_RULE.slice(1)}.`;
3932
- function parseYamlAs(text2, schema, fill, target) {
4085
+ function parseYamlAs(text4, schema, fill, target) {
3933
4086
  let data;
3934
- const body2 = unfence(text2);
4087
+ const body2 = unfence(text4);
3935
4088
  try {
3936
4089
  data = (0, import_yaml6.parse)(body2);
3937
4090
  } catch (e) {
@@ -4016,7 +4169,9 @@ async function questionsOnFile(root, repo) {
4016
4169
  `- ${q2.id} (${q2.state}): ${q2.question.trim()} [${q2.evidence.join(", ")}]`,
4017
4170
  ...q2.state === "answered" ? (
4018
4171
  // B-run2-fixes 8: the option's meaning, or the person's own words, not the label alone.
4019
- [` Answer (${q2.answer?.by ?? "a person"}): ${answerText(q2) ?? ""}`]
4172
+ [
4173
+ ` Answer (${q2.answer ? answerByWords(q2.answer.by) : "a person"}): ${answerText(q2) ?? ""}`
4174
+ ]
4020
4175
  ) : []
4021
4176
  ].join("\n")).join("\n"),
4022
4177
  classification: "internal",
@@ -4036,22 +4191,378 @@ function stepJudges(root, runtime, change, now) {
4036
4191
  return { judge: questionJudge(o), answerJudge: answerJudge(o) };
4037
4192
  }
4038
4193
 
4194
+ // packages/planner/dist/stages/drafts.js
4195
+ var import_yaml7 = __toESM(require_dist(), 1);
4196
+ import { createHash as createHash2 } from "node:crypto";
4197
+ import { readFile as readFile6 } from "node:fs/promises";
4198
+ import { join as join7 } from "node:path";
4199
+ var DRAFTS_SCHEMA_VERSION = 1;
4200
+ var DRAFTED_FILE = "stage/drafted.yaml";
4201
+ var CORRECTIONS_FILE = "stage/corrections.yaml";
4202
+ var V = external_exports.literal(DRAFTS_SCHEMA_VERSION);
4203
+ var text = () => external_exports.string().trim().min(1);
4204
+ var Id = external_exports.string().regex(/^[a-z0-9][a-z0-9._-]*$/);
4205
+ var DraftedRecord = external_exports.discriminatedUnion("by", [
4206
+ external_exports.strictObject({
4207
+ by: external_exports.literal("ai"),
4208
+ stage: Stage,
4209
+ /** The run that drafted it (a hand-off, a model run or a replay). */
4210
+ run: external_exports.string().optional(),
4211
+ /** The stage's draft number. */
4212
+ iteration: external_exports.number().int().min(0),
4213
+ at: external_exports.string(),
4214
+ sha256: external_exports.string().regex(/^[0-9a-f]{64}$/)
4215
+ }),
4216
+ external_exports.strictObject({
4217
+ by: external_exports.literal("person"),
4218
+ who: text(),
4219
+ at: external_exports.string(),
4220
+ sha256: external_exports.string().regex(/^[0-9a-f]{64}$/)
4221
+ })
4222
+ ]);
4223
+ var DraftedFile = external_exports.strictObject({
4224
+ schemaVersion: V,
4225
+ change: Id,
4226
+ /** By change-folder path: who wrote the file last. */
4227
+ files: external_exports.record(external_exports.string(), DraftedRecord).default({})
4228
+ });
4229
+ var CorrectionScope = external_exports.enum(["one", "similar"]);
4230
+ var CorrectionState = external_exports.enum(["waiting", "read", "applied"]);
4231
+ var Correction = external_exports.strictObject({
4232
+ id: external_exports.string().regex(/^c-[0-9a-f]{10}$/),
4233
+ /** The stage whose next run reads it: the one that drafted the file. */
4234
+ stage: Stage,
4235
+ /** The file, change-folder relative. */
4236
+ file: text(),
4237
+ /** The item in it (draftItems' id); left out, the whole file. */
4238
+ item: external_exports.string().optional(),
4239
+ /** What the person saw: the item's label. */
4240
+ label: text(),
4241
+ /** What the person says, in their words. */
4242
+ text: text().max(4e3),
4243
+ /** one: this item; similar: this item and every item like it. */
4244
+ scope: CorrectionScope,
4245
+ /** similar: the name the similar items share (a component, an entry, a table, a term). */
4246
+ like: external_exports.string().trim().min(1).optional(),
4247
+ by: text(),
4248
+ at: external_exports.string(),
4249
+ /**
4250
+ * waiting: no draft has read it yet; read: a draft had it and didn't change the item;
4251
+ * applied: a draft had it and changed the item. Kept on file either way.
4252
+ */
4253
+ state: CorrectionState,
4254
+ /** The run of the draft that first read it, and that draft's number. */
4255
+ run: external_exports.string().optional(),
4256
+ iteration: external_exports.number().int().min(0).optional(),
4257
+ /** When the draft changed the item. */
4258
+ appliedAt: external_exports.string().optional(),
4259
+ /** similar: the other items a draft with it changed, as `<file>#<item>`. */
4260
+ touched: external_exports.array(external_exports.string()).optional()
4261
+ });
4262
+ var CorrectionsFile = external_exports.strictObject({
4263
+ schemaVersion: V,
4264
+ change: Id,
4265
+ corrections: external_exports.array(Correction).default([])
4266
+ });
4267
+ var sha2 = (s) => createHash2("sha256").update(s).digest("hex");
4268
+ var draftSha = (s) => sha2(s.replace(/\r\n?/g, "\n"));
4269
+ async function readYaml(file, schema, empty) {
4270
+ const t = await readFile6(file, "utf8").catch(() => void 0);
4271
+ if (t === void 0)
4272
+ return { data: empty, present: false };
4273
+ let raw;
4274
+ try {
4275
+ raw = (0, import_yaml7.parse)(t);
4276
+ } catch (e) {
4277
+ throw new PlannerError("invalid", `${file} doesn't read as YAML: ${e.message}`);
4278
+ }
4279
+ const r = schema.safeParse(raw);
4280
+ if (!r.success)
4281
+ throw new PlannerError("invalid", `${file} doesn't read: ${r.error.issues.map((i) => `${i.path.join(".") || "(top)"}: ${i.message}`).join("; ")}`);
4282
+ return { data: r.data, present: true };
4283
+ }
4284
+ async function readDrafted(dir, change) {
4285
+ return (await readYaml(join7(dir, DRAFTED_FILE), DraftedFile, {
4286
+ schemaVersion: DRAFTS_SCHEMA_VERSION,
4287
+ change,
4288
+ files: {}
4289
+ })).data;
4290
+ }
4291
+ async function writeDrafted(dir, d) {
4292
+ const files = Object.fromEntries(Object.entries(d.files).sort(([a], [b]) => a.localeCompare(b)));
4293
+ await writeFileAtomic(join7(dir, DRAFTED_FILE), `# Who wrote each stage draft last (ADR 0181): the AI, with its stage and run, or a person who saved it in the app. Atticus writes this file.
4294
+ ${toCanonicalYaml(DraftedFile.parse({ ...d, files }), ["schemaVersion", "change", "files"])}`);
4295
+ }
4296
+ async function recordAiDraft(dir, change, o) {
4297
+ const d = await readDrafted(dir, change).catch(() => void 0);
4298
+ const out = d ?? { schemaVersion: DRAFTS_SCHEMA_VERSION, change, files: {} };
4299
+ let any = false;
4300
+ for (const f of o.files) {
4301
+ const t = await readFile6(join7(dir, ...f.split("/")), "utf8").catch(() => void 0);
4302
+ if (t === void 0)
4303
+ continue;
4304
+ const h = draftSha(t);
4305
+ const was = out.files[f];
4306
+ if (was?.by === "person" && was.sha256 === h)
4307
+ continue;
4308
+ if (was?.by === "ai" && was.sha256 === h && was.stage === o.stage)
4309
+ continue;
4310
+ out.files[f] = {
4311
+ by: "ai",
4312
+ stage: o.stage,
4313
+ ...o.run ? { run: o.run } : {},
4314
+ iteration: o.iteration,
4315
+ at: o.at,
4316
+ sha256: h
4317
+ };
4318
+ any = true;
4319
+ }
4320
+ if (any)
4321
+ await writeDrafted(dir, out);
4322
+ }
4323
+ async function recordPersonSave(dir, change, o) {
4324
+ const d = await readDrafted(dir, change).catch(() => void 0) ?? {
4325
+ schemaVersion: DRAFTS_SCHEMA_VERSION,
4326
+ change,
4327
+ files: {}
4328
+ };
4329
+ d.files[o.file] = { by: "person", who: o.who, at: o.at, sha256: draftSha(o.content) };
4330
+ await writeDrafted(dir, d);
4331
+ }
4332
+ async function draftProvenance(dir, change, files, written = []) {
4333
+ const d = await readDrafted(dir, change).catch(() => void 0);
4334
+ const out = {};
4335
+ for (const f of files) {
4336
+ const t = await readFile6(join7(dir, ...f.split("/")), "utf8").catch(() => void 0);
4337
+ if (t === void 0)
4338
+ continue;
4339
+ const h = draftSha(t);
4340
+ const r = d?.files[f];
4341
+ if (r) {
4342
+ if (r.sha256 !== h)
4343
+ out[f] = { by: "person" };
4344
+ else if (r.by === "ai")
4345
+ out[f] = { by: "ai", stage: r.stage, ...r.run ? { run: r.run } : {}, at: r.at };
4346
+ else
4347
+ out[f] = { by: "person", who: r.who, at: r.at };
4348
+ continue;
4349
+ }
4350
+ const w = written.find((x) => x.hashes[f] !== void 0);
4351
+ if (w)
4352
+ out[f] = w.hashes[f] === h ? { by: "ai", stage: w.stage } : { by: "person" };
4353
+ }
4354
+ return out;
4355
+ }
4356
+ var DRAFT_ITEMS_MAX = 80;
4357
+ var slug2 = (s) => s.toLowerCase().replace(/[`*_]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60) || "section";
4358
+ function markdownSpans(t) {
4359
+ const lines = t.replace(/\r\n?/g, "\n").split("\n");
4360
+ const heads = [];
4361
+ let fence = false;
4362
+ lines.forEach((l, i) => {
4363
+ if (/^\s*(```|~~~)/.test(l))
4364
+ fence = !fence;
4365
+ const m = !fence && /^(#{2,4})\s+(.+?)\s*#*\s*$/.exec(l);
4366
+ if (m)
4367
+ heads.push({ at: i, level: m[1].length, title: m[2] });
4368
+ });
4369
+ const seen = /* @__PURE__ */ new Map();
4370
+ return heads.map((h, k) => {
4371
+ const end = heads.slice(k + 1).find((x) => x.level <= h.level)?.at ?? lines.length;
4372
+ const base = slug2(h.title);
4373
+ const n = (seen.get(base) ?? 0) + 1;
4374
+ seen.set(base, n);
4375
+ return {
4376
+ item: { id: n === 1 ? base : `${base}-${n}`, label: h.title.replace(/`/g, "") },
4377
+ text: lines.slice(h.at, end).join("\n").trim()
4378
+ };
4379
+ });
4380
+ }
4381
+ function taskSpans(t) {
4382
+ const out = [];
4383
+ for (const l of t.replace(/\r\n?/g, "\n").split("\n")) {
4384
+ const m = /^\s*- \[[ xX]\]\s+(\d+(?:\.\d+)*)\s+(.*)$/.exec(l);
4385
+ if (m)
4386
+ out.push({ item: { id: `task-${m[1]}`, label: `${m[1]} ${m[2]}` }, text: l.trim() });
4387
+ }
4388
+ return out;
4389
+ }
4390
+ function flowSpans(t) {
4391
+ let raw;
4392
+ try {
4393
+ raw = (0, import_yaml7.parse)(t);
4394
+ } catch {
4395
+ return [];
4396
+ }
4397
+ const steps = raw?.steps;
4398
+ if (!Array.isArray(steps))
4399
+ return [];
4400
+ return steps.map((s, i) => {
4401
+ const st = s ?? {};
4402
+ const like = [st.to?.component, st.to?.entry, st.store?.table].filter((x) => typeof x === "string" && x.trim() !== "");
4403
+ const head = [st.op, st.kind].filter(Boolean).join(" ");
4404
+ return {
4405
+ item: {
4406
+ id: `step-${i + 1}`,
4407
+ label: `Step ${i + 1}${head ? ` (${head})` : ""}${st.what ? `: ${st.what}` : ""}`,
4408
+ ...like.length ? { like } : {}
4409
+ },
4410
+ text: JSON.stringify(s)
4411
+ };
4412
+ });
4413
+ }
4414
+ function spansOf(file, t) {
4415
+ if (/^target-flows\/[^/]+\.ya?ml$/.test(file))
4416
+ return flowSpans(t);
4417
+ if (file === "tasks.md")
4418
+ return taskSpans(t);
4419
+ if (file.endsWith(".md"))
4420
+ return markdownSpans(t);
4421
+ return [];
4422
+ }
4423
+ function draftItems(file, t) {
4424
+ return spansOf(file, t).slice(0, DRAFT_ITEMS_MAX).map((s) => s.item);
4425
+ }
4426
+ function draftItemText(file, t, item) {
4427
+ if (!item)
4428
+ return t;
4429
+ return spansOf(file, t).find((s) => s.item.id === item)?.text;
4430
+ }
4431
+ async function readCorrections(dir, change) {
4432
+ return (await readYaml(join7(dir, CORRECTIONS_FILE), CorrectionsFile, {
4433
+ schemaVersion: DRAFTS_SCHEMA_VERSION,
4434
+ change,
4435
+ corrections: []
4436
+ })).data.corrections;
4437
+ }
4438
+ async function writeCorrections(dir, change, cs) {
4439
+ await writeFileAtomic(join7(dir, CORRECTIONS_FILE), `# A person's corrections of AI-drafted items (ADR 0181): each stage's next run reads them as the person's word; Atticus marks what a draft applied. Atticus writes this file.
4440
+ ${toCanonicalYaml(CorrectionsFile.parse({ schemaVersion: DRAFTS_SCHEMA_VERSION, change, corrections: cs }), ["schemaVersion", "change", "corrections"])}`);
4441
+ }
4442
+ async function addCorrection(dir, change, o) {
4443
+ const words5 = o.text.trim();
4444
+ if (!words5)
4445
+ throw new PlannerError("invalid", "Write the correction first.");
4446
+ if (words5.length > 4e3)
4447
+ throw new PlannerError("invalid", "A correction is at most 4000 characters.");
4448
+ const cs = await readCorrections(dir, change);
4449
+ const id = `c-${sha2([o.stage, o.file, o.item ?? "", words5, o.by, o.at].join("\n")).slice(0, 10)}`;
4450
+ if (cs.some((c2) => c2.id === id))
4451
+ return cs.find((c2) => c2.id === id);
4452
+ const like = o.like?.trim();
4453
+ const c = Correction.parse({
4454
+ id,
4455
+ stage: o.stage,
4456
+ file: o.file,
4457
+ ...o.item ? { item: o.item } : {},
4458
+ label: o.label.trim() || o.file,
4459
+ text: words5,
4460
+ scope: o.similar ? "similar" : "one",
4461
+ ...o.similar && like ? { like } : {},
4462
+ by: o.by,
4463
+ at: o.at,
4464
+ state: "waiting"
4465
+ });
4466
+ await writeCorrections(dir, change, [...cs, c]);
4467
+ return c;
4468
+ }
4469
+ async function removeCorrection(dir, change, id) {
4470
+ const cs = await readCorrections(dir, change);
4471
+ const c = cs.find((x) => x.id === id);
4472
+ if (!c)
4473
+ return false;
4474
+ if (c.state !== "waiting")
4475
+ throw new PlannerError("refused", `Correction ${id} was read by run ${c.run ?? "of the stage"}; it stays on file. Write a new correction to change it: a later correction wins.`);
4476
+ await writeCorrections(dir, change, cs.filter((x) => x.id !== id));
4477
+ return true;
4478
+ }
4479
+ var CORRECTIONS_RULE = `A person corrected items of the stage's drafts; the "hand-backs" data block lists each correction under "Corrections a person made", with the item and the person's words. Each one is the person's instruction, like an answer: follow it in this draft, and where it says to apply it to similar items, make the same correction to every other item that names the same thing. A later correction of an item wins over an earlier one. A correction never overrides a fact verified from code: where one contradicts a cited line, keep what the code says and ask a question naming the correction.`;
4480
+ function correctionsText(cs) {
4481
+ if (!cs.length)
4482
+ return void 0;
4483
+ return [
4484
+ "Corrections a person made (the person's instruction; follow each one):",
4485
+ ...cs.map((c) => {
4486
+ const where2 = `${c.file}${c.item ? `, ${c.label}` : ""}`;
4487
+ const similar = c.scope === "similar" ? ` Apply the same correction to every similar item${c.like ? ` (every other step, story, section or task naming ${c.like})` : " (every other item like it)"}.` : "";
4488
+ return `- ${where2}: "${c.text}" (${c.by}, ${c.at.slice(0, 10)}).${similar}`;
4489
+ })
4490
+ ].join("\n");
4491
+ }
4492
+ async function stageCorrections(dir, change, stage) {
4493
+ const all2 = (await readCorrections(dir, change).catch(() => [])).filter((c) => c.stage === stage).sort((a, b) => a.at < b.at ? -1 : a.at > b.at ? 1 : 0);
4494
+ return { all: all2, waiting: all2.filter((c) => c.state === "waiting") };
4495
+ }
4496
+ async function snapshotFiles(dir, files) {
4497
+ const out = /* @__PURE__ */ new Map();
4498
+ for (const f of [...new Set(files)]) {
4499
+ const t = await readFile6(join7(dir, ...f.split("/")), "utf8").catch(() => void 0);
4500
+ if (t !== void 0)
4501
+ out.set(f, t);
4502
+ }
4503
+ return out;
4504
+ }
4505
+ async function settleCorrections(dir, change, o) {
4506
+ const cs = await readCorrections(dir, change).catch(() => void 0);
4507
+ if (!cs?.length)
4508
+ return [];
4509
+ const after2 = await snapshotFiles(dir, [...o.files, ...o.before.keys()]);
4510
+ const settled = [];
4511
+ const changedItems = (likeOf) => {
4512
+ const out = [];
4513
+ for (const f of [.../* @__PURE__ */ new Set([...o.before.keys(), ...after2.keys()])].sort()) {
4514
+ const was = new Map(spansOf(f, o.before.get(f) ?? "").map((s) => [s.item.id, s.text]));
4515
+ for (const s of spansOf(f, after2.get(f) ?? ""))
4516
+ if (was.get(s.item.id) !== s.text && likeOf(s))
4517
+ out.push(`${f}#${s.item.id}`);
4518
+ }
4519
+ return out;
4520
+ };
4521
+ for (const c of cs) {
4522
+ if (c.stage !== o.stage || c.state === "applied")
4523
+ continue;
4524
+ const before = o.before.has(c.file) ? draftItemText(c.file, o.before.get(c.file), c.item) : void 0;
4525
+ const now = after2.has(c.file) ? draftItemText(c.file, after2.get(c.file), c.item) : void 0;
4526
+ if (c.state === "waiting") {
4527
+ if (o.run)
4528
+ c.run = o.run;
4529
+ c.iteration = o.iteration;
4530
+ }
4531
+ if (before !== now) {
4532
+ c.state = "applied";
4533
+ c.appliedAt = o.at;
4534
+ } else
4535
+ c.state = "read";
4536
+ if (c.scope === "similar") {
4537
+ const like = c.like?.toLowerCase();
4538
+ const mine = `${c.file}#${c.item ?? ""}`;
4539
+ const touched = changedItems((s) => !like || s.text.toLowerCase().includes(like) || s.item.label.toLowerCase().includes(like) || (s.item.like ?? []).some((x) => x.toLowerCase() === like)).filter((x) => x !== mine);
4540
+ if (touched.length)
4541
+ c.touched = [.../* @__PURE__ */ new Set([...c.touched ?? [], ...touched])].sort();
4542
+ }
4543
+ settled.push(c);
4544
+ }
4545
+ if (settled.length)
4546
+ await writeCorrections(dir, change, cs);
4547
+ return settled;
4548
+ }
4549
+
4039
4550
  // packages/planner/dist/gaps.js
4040
- import { readdir as readdir5, readFile as readFile7 } from "node:fs/promises";
4041
- import { join as join8 } from "node:path";
4042
- var import_yaml8 = __toESM(require_dist(), 1);
4551
+ import { readdir as readdir5, readFile as readFile8 } from "node:fs/promises";
4552
+ import { join as join9 } from "node:path";
4553
+ var import_yaml9 = __toESM(require_dist(), 1);
4043
4554
 
4044
4555
  // packages/planner/dist/stages/expand.js
4045
- import { createHash as createHash2 } from "node:crypto";
4556
+ import { createHash as createHash3 } from "node:crypto";
4046
4557
 
4047
4558
  // packages/planner/dist/stages/judged.js
4048
- var import_yaml7 = __toESM(require_dist(), 1);
4049
- import { mkdir as mkdir2, readFile as readFile6, rm as rm2, writeFile } from "node:fs/promises";
4050
- import { dirname as dirname2, join as join7 } from "node:path";
4559
+ var import_yaml8 = __toESM(require_dist(), 1);
4560
+ import { mkdir as mkdir2, readFile as readFile7, rm as rm2, writeFile } from "node:fs/promises";
4561
+ import { dirname as dirname2, join as join8 } from "node:path";
4051
4562
  function filePending(root) {
4052
- const file = (kind, key3) => join7(stateDirOf(root), ".index", "pending-judgments", kind, `${key3}.txt`);
4563
+ const file = (kind, key3) => join8(stateDirOf(root), ".index", "pending-judgments", kind, `${key3}.txt`);
4053
4564
  return {
4054
- get: async (kind, key3) => (await readFile6(file(kind, key3), "utf8").catch(() => void 0))?.trim() || void 0,
4565
+ get: async (kind, key3) => (await readFile7(file(kind, key3), "utf8").catch(() => void 0))?.trim() || void 0,
4055
4566
  set: async (kind, key3, runId) => {
4056
4567
  const f = file(kind, key3);
4057
4568
  if (runId === void 0)
@@ -4138,10 +4649,10 @@ async function judged(o) {
4138
4649
  });
4139
4650
  return finish(await o.runtime.run(prepared.runId), false);
4140
4651
  }
4141
- function yamlOf(text2) {
4142
- const fenced = /```(?:ya?ml)?\s*\n([\s\S]*?)```/.exec(text2);
4652
+ function yamlOf(text4) {
4653
+ const fenced = /```(?:ya?ml)?\s*\n([\s\S]*?)```/.exec(text4);
4143
4654
  try {
4144
- return (0, import_yaml7.parse)(fenced ? fenced[1] : text2);
4655
+ return (0, import_yaml8.parse)(fenced ? fenced[1] : text4);
4145
4656
  } catch {
4146
4657
  return void 0;
4147
4658
  }
@@ -4172,8 +4683,17 @@ var Answer = external_exports.object({
4172
4683
  })).default([])
4173
4684
  });
4174
4685
  var fold = (s) => s.replace(/\s+/g, " ").trim().toLowerCase();
4175
- function checkExpansion(text2, input, ix) {
4176
- const r = Answer.safeParse(yamlOf(text2));
4686
+ function titledText(title, text4) {
4687
+ if (!title)
4688
+ return text4;
4689
+ const first = text4.replace(/^\uFEFF/, "").trimStart().split(/\r?\n/, 1)[0].trim();
4690
+ const heading = /^#\s+(.*?)\s*#*$/.exec(first)?.[1]?.trim();
4691
+ return heading === title.trim() ? text4 : `# ${title}
4692
+
4693
+ ${text4}`;
4694
+ }
4695
+ function checkExpansion(text4, input, ix) {
4696
+ const r = Answer.safeParse(yamlOf(text4));
4177
4697
  if (!r.success)
4178
4698
  return { problem: "the expansion isn't YAML with terms and names" };
4179
4699
  const all2 = fold(`${input.title ?? ""}
@@ -4255,7 +4775,7 @@ async function expandFor(root, change, input, o) {
4255
4775
  }
4256
4776
  try {
4257
4777
  const digest = digestSources(ix);
4258
- const names = createHash2("sha256").update(digest.map((d) => d.text).join("\n\n")).digest("hex");
4778
+ const names = createHash3("sha256").update(digest.map((d) => d.text).join("\n\n")).digest("hex");
4259
4779
  const r = await judged({
4260
4780
  kind: "expansion",
4261
4781
  inputs: { input, names, runtime: o.runtime.kind, v: 2 },
@@ -4277,9 +4797,7 @@ async function expandFor(root, change, input, o) {
4277
4797
  id: "input",
4278
4798
  kind: "document",
4279
4799
  origin: "the step's input",
4280
- text: `${input.title ? `# ${input.title}
4281
-
4282
- ` : ""}${input.text}`,
4800
+ text: titledText(input.title, input.text),
4283
4801
  classification: "internal",
4284
4802
  priority: 50
4285
4803
  },
@@ -4292,7 +4810,7 @@ async function expandFor(root, change, input, o) {
4292
4810
  store: o.store,
4293
4811
  pending: filePending(root),
4294
4812
  step: `${o.step} (expansion)`,
4295
- read: (text2) => checkExpansion(text2, input, ix)
4813
+ read: (text4) => checkExpansion(text4, input, ix)
4296
4814
  });
4297
4815
  if ("waiting" in r)
4298
4816
  return r;
@@ -4329,8 +4847,8 @@ List only candidates from the list, each once.`;
4329
4847
  function candidatesOf(s) {
4330
4848
  return [...s.picked.map((p) => ({ kind: p.kind, id: p.id })), ...s.leftOut];
4331
4849
  }
4332
- function checkRerank(text2, shown) {
4333
- const r = Answer2.safeParse(yamlOf(text2));
4850
+ function checkRerank(text4, shown) {
4851
+ const r = Answer2.safeParse(yamlOf(text4));
4334
4852
  if (!r.success)
4335
4853
  return { problem: "the rerank isn't YAML with keep" };
4336
4854
  const set = new Set(shown);
@@ -4371,9 +4889,7 @@ async function rerankFor(root, change, input, selection, o) {
4371
4889
  id: "input",
4372
4890
  kind: "document",
4373
4891
  origin: "the step's input",
4374
- text: `${input.title ? `# ${input.title}
4375
-
4376
- ` : ""}${input.text}`,
4892
+ text: titledText(input.title, input.text),
4377
4893
  classification: "internal",
4378
4894
  priority: 50
4379
4895
  },
@@ -4394,7 +4910,7 @@ ${lines.join("\n")}`,
4394
4910
  store: o.store,
4395
4911
  pending: filePending(root),
4396
4912
  step: `${o.step} (rerank)`,
4397
- read: (text2) => checkRerank(text2, shown)
4913
+ read: (text4) => checkRerank(text4, shown)
4398
4914
  });
4399
4915
  if ("waiting" in r)
4400
4916
  return r;
@@ -4489,11 +5005,11 @@ var wholeLists = () => ({
4489
5005
  leftOut: [],
4490
5006
  unknown: []
4491
5007
  });
4492
- var instructionSource = (text2) => ({
5008
+ var instructionSource = (text4) => ({
4493
5009
  id: "instructions",
4494
5010
  kind: "instruction",
4495
5011
  origin: "atticus",
4496
- text: text2,
5012
+ text: text4,
4497
5013
  classification: "public",
4498
5014
  trusted: true
4499
5015
  });
@@ -4564,7 +5080,7 @@ function oneSource(s, o) {
4564
5080
  }
4565
5081
  }
4566
5082
  const left = s.leftOut.filter((l) => keep(l.kind)).map((l) => l.id);
4567
- const text2 = [
5083
+ const text4 = [
4568
5084
  ...o.heading ? [o.heading, ""] : [],
4569
5085
  ...parts.length ? parts : [o.empty ?? "Nothing in the index matches."],
4570
5086
  ...left.length ? [
@@ -4576,7 +5092,7 @@ function oneSource(s, o) {
4576
5092
  id: o.id,
4577
5093
  kind: o.kind,
4578
5094
  origin: o.origin,
4579
- text: text2,
5095
+ text: text4,
4580
5096
  classification: "internal",
4581
5097
  priority: o.priority
4582
5098
  };
@@ -4595,18 +5111,18 @@ function asSources(s, kind) {
4595
5111
  // packages/planner/dist/gaps.js
4596
5112
  var TARGET_DIR = "target-flows";
4597
5113
  async function changeTexts(root, change) {
4598
- const dir = join8(changesDirOf(root), change);
5114
+ const dir = join9(changesDirOf(root), change);
4599
5115
  const out = [];
4600
5116
  const top = (await readdir5(dir).catch(() => [])).filter((f) => /\.md$/.test(f)).sort();
4601
5117
  for (const f of ["brief.md", ...top.filter((x) => x !== "brief.md")]) {
4602
- const t = await readFile7(join8(dir, f), "utf8").catch(() => void 0);
5118
+ const t = await readFile8(join9(dir, f), "utf8").catch(() => void 0);
4603
5119
  if (t !== void 0)
4604
5120
  out.push([f, t]);
4605
5121
  }
4606
5122
  for (const sub of ["stories", "target-flows"])
4607
- for (const f of (await readdir5(join8(dir, sub)).catch(() => [])).sort())
5123
+ for (const f of (await readdir5(join9(dir, sub)).catch(() => [])).sort())
4608
5124
  if (/\.(md|ya?ml)$/.test(f)) {
4609
- const t = await readFile7(join8(dir, sub, f), "utf8").catch(() => void 0);
5125
+ const t = await readFile8(join9(dir, sub, f), "utf8").catch(() => void 0);
4610
5126
  if (t !== void 0)
4611
5127
  out.push([`${sub}/${f}`, t]);
4612
5128
  }
@@ -4637,9 +5153,9 @@ async function gapsOf(root, change, unknown) {
4637
5153
  return out;
4638
5154
  }
4639
5155
  async function codeReaders() {
4640
- const x = await import("./chunk-OSB6AKF2.js");
5156
+ const x = await import("./chunk-UEVZZZU2.js");
4641
5157
  return {
4642
- outline: (path, text2) => x.declarationOutline(path, text2),
5158
+ outline: (path, text4) => x.declarationOutline(path, text4),
4643
5159
  outlines: (path) => x.syntaxLanguageOf(path) !== void 0
4644
5160
  };
4645
5161
  }
@@ -4658,24 +5174,24 @@ var sameName = (a, b) => {
4658
5174
  };
4659
5175
  async function ownNames(root, change) {
4660
5176
  const rel = `${changesRelOf(root)}/${change}/${TARGET_DIR}`;
4661
- const dir = join8(changesDirOf(root), change, TARGET_DIR);
5177
+ const dir = join9(changesDirOf(root), change, TARGET_DIR);
4662
5178
  const out = [];
4663
5179
  for (const f of (await readdir5(dir).catch(() => [])).sort()) {
4664
5180
  if (!/\.ya?ml$/.test(f))
4665
5181
  continue;
4666
- const text2 = await readFile7(join8(dir, f), "utf8").catch(() => void 0);
4667
- if (text2 === void 0)
5182
+ const text4 = await readFile8(join9(dir, f), "utf8").catch(() => void 0);
5183
+ if (text4 === void 0)
4668
5184
  continue;
4669
5185
  let d;
4670
5186
  try {
4671
- const r = FlowDelta.safeParse((0, import_yaml8.parse)(text2));
5187
+ const r = FlowDelta.safeParse((0, import_yaml9.parse)(text4));
4672
5188
  if (!r.success)
4673
5189
  continue;
4674
5190
  d = r.data;
4675
5191
  } catch {
4676
5192
  continue;
4677
5193
  }
4678
- const lines = text2.split(/\r?\n/);
5194
+ const lines = text4.split(/\r?\n/);
4679
5195
  const add = (name, key3) => {
4680
5196
  if (!name?.trim())
4681
5197
  return;
@@ -4853,7 +5369,7 @@ async function gapAnswers(root, change) {
4853
5369
  origin: "questions/ (answered gaps)",
4854
5370
  text: [
4855
5371
  "People said what these names are; their answers are asserted knowledge (who and when), not read from code:",
4856
- ...answered.map((q2) => `- ${gapNameOf(q2)}: ${answerText(q2) ?? ""} (${q2.answer.by}, ${q2.answer.at}, question ${q2.id})`)
5372
+ ...answered.map((q2) => `- ${gapNameOf(q2)}: ${answerText(q2) ?? ""} (${answerByWords(q2.answer.by)}, ${q2.answer.at}, question ${q2.id})`)
4857
5373
  ].join("\n"),
4858
5374
  classification: "internal",
4859
5375
  priority: 96
@@ -4863,12 +5379,12 @@ async function gapAnswers(root, change) {
4863
5379
  function reliedOn(gaps, answer) {
4864
5380
  let data;
4865
5381
  try {
4866
- data = (0, import_yaml8.parse)(unfence(answer));
5382
+ data = (0, import_yaml9.parse)(unfence(answer));
4867
5383
  } catch {
4868
5384
  return { relied: [], unknown: [] };
4869
5385
  }
4870
- const listed = data?.relies_on_gaps;
4871
- const names = Array.isArray(listed) ? listed.filter((x) => typeof x === "string" && x.trim() !== "") : [];
5386
+ const listed2 = data?.relies_on_gaps;
5387
+ const names = Array.isArray(listed2) ? listed2.filter((x) => typeof x === "string" && x.trim() !== "") : [];
4872
5388
  const relied = [];
4873
5389
  const unknown = [];
4874
5390
  for (const n of names) {
@@ -4903,7 +5419,7 @@ async function askGaps(root, change, gaps, answer, by) {
4903
5419
  }
4904
5420
 
4905
5421
  // packages/planner/dist/stages/finding-kind.js
4906
- import { join as join9 } from "node:path";
5422
+ import { join as join10 } from "node:path";
4907
5423
  import { readdir as readdir6 } from "node:fs/promises";
4908
5424
  var DRAFTING_CHECKS = /* @__PURE__ */ new Set([
4909
5425
  "story-has-acceptance-criteria",
@@ -4979,17 +5495,17 @@ async function criticRuleChecks(root) {
4979
5495
  };
4980
5496
  add((await loadConstitution()).rules);
4981
5497
  const org = orgPackDirOf(root);
4982
- for (const p of (await readPackFiles(root, join9(org, "checklists"))).packs)
5498
+ for (const p of (await readPackFiles(root, join10(org, "checklists"))).packs)
4983
5499
  add(p.pack.rules);
4984
- const teams = await readdir6(join9(org, "teams"), { withFileTypes: true }).catch(() => []);
5500
+ const teams = await readdir6(join10(org, "teams"), { withFileTypes: true }).catch(() => []);
4985
5501
  for (const t of teams.filter((x) => x.isDirectory()).sort((a, b) => a.name.localeCompare(b.name)))
4986
- for (const p of (await readPackFiles(root, join9(org, "teams", t.name, "checklists"))).packs)
5502
+ for (const p of (await readPackFiles(root, join10(org, "teams", t.name, "checklists"))).packs)
4987
5503
  add(p.pack.rules);
4988
5504
  return out;
4989
5505
  }
4990
5506
 
4991
5507
  // packages/planner/dist/stages/loop.js
4992
- var issueFingerprint = (rule, target, evidence = "") => createHash3("sha256").update([rule, target, evidence].join("\n")).digest("hex").slice(0, 12);
5508
+ var issueFingerprint = (rule, target, evidence = "") => createHash4("sha256").update([rule, target, evidence].join("\n")).digest("hex").slice(0, 12);
4993
5509
  var loopRel = (stage) => `stage/${stage}.yaml`;
4994
5510
  var STAGE_WORDS = {
4995
5511
  feature: "feature placement",
@@ -4998,9 +5514,9 @@ var STAGE_WORDS = {
4998
5514
  small: "small change's plan"
4999
5515
  };
5000
5516
  async function readLoop(dir, change, stage) {
5001
- const text2 = await readFile8(join10(dir, loopRel(stage)), "utf8").catch(() => void 0);
5002
- if (text2 !== void 0) {
5003
- const r = StageLoop.safeParse((0, import_yaml9.parse)(text2));
5517
+ const text4 = await readFile9(join11(dir, loopRel(stage)), "utf8").catch(() => void 0);
5518
+ if (text4 !== void 0) {
5519
+ const r = StageLoop.safeParse((0, import_yaml10.parse)(text4));
5004
5520
  if (r.success)
5005
5521
  return r.data;
5006
5522
  }
@@ -5014,12 +5530,12 @@ async function readLoop(dir, change, stage) {
5014
5530
  });
5015
5531
  }
5016
5532
  async function readStageLoop(dir, change, stage) {
5017
- const present = await stat(join10(dir, loopRel(stage))).then((s) => s.isFile(), () => false);
5533
+ const present = await stat(join11(dir, loopRel(stage))).then((s) => s.isFile(), () => false);
5018
5534
  return { loop: await readLoop(dir, change, stage), present, file: loopRel(stage) };
5019
5535
  }
5020
5536
  var writeStageLoop = (dir, l) => writeLoop(dir, l);
5021
5537
  async function writeLoop(dir, l) {
5022
- await writeFileAtomic(join10(dir, loopRel(l.stage)), `# The ${l.stage} stage's loop (ADR 0087): drafts, the findings each one had, and the questions it raised. Atticus writes this file.
5538
+ await writeFileAtomic(join11(dir, loopRel(l.stage)), `# The ${l.stage} stage's loop (ADR 0087): drafts, the findings each one had, and the questions it raised. Atticus writes this file.
5023
5539
  ${toCanonicalYaml(StageLoop.parse(l), ["schemaVersion", "change", "stage", "iteration", "state", "updatedAt", "run"])}`);
5024
5540
  }
5025
5541
  function stageBudget(ctx, agent) {
@@ -5058,7 +5574,7 @@ async function refuseWhileEarlierStuck(root, ctx, change, stage) {
5058
5574
  for (const earlier of path.slice(0, at2)) {
5059
5575
  if (change.handoffs.some((h) => h.phase === earlier.phase && h.state === "merged"))
5060
5576
  continue;
5061
- const ran = await readFile8(join10(dir, ...earlier.loop.split("/"))).then(() => true, () => false);
5577
+ const ran = await readFile9(join11(dir, ...earlier.loop.split("/"))).then(() => true, () => false);
5062
5578
  if (!ran)
5063
5579
  continue;
5064
5580
  const gate = phaseGates(ctx, earlier.phase)[0];
@@ -5162,8 +5678,10 @@ async function runStageLoop(root, changeId, spec, o) {
5162
5678
  loop.run = w.runId;
5163
5679
  loop.updatedAt = at2;
5164
5680
  await writeLoop(dir, loop);
5165
- return result("waiting", { ...extra, waiting: { runId: w.runId, lens: w.lens } });
5681
+ return result("waiting", { ...extra, waiting: waitingOf(w) });
5166
5682
  };
5683
+ if (await retireSettledEscalations(root, change.id, loop, at2))
5684
+ await writeLoop(dir, loop);
5167
5685
  if (!o.resume && loop.state === "questions") {
5168
5686
  const open = (await questionsOf(loop.questions)).filter((q2) => q2.state === "open");
5169
5687
  if (open.length)
@@ -5191,11 +5709,36 @@ async function runStageLoop(root, changeId, spec, o) {
5191
5709
  ...spec.sources,
5192
5710
  ...a !== void 0 && spec.reviewSources ? spec.reviewSources(a) : []
5193
5711
  ];
5194
- const write3 = async (a) => {
5712
+ const corrections = await stageCorrections(dir, change.id, spec.stage).catch(() => ({
5713
+ all: [],
5714
+ waiting: []
5715
+ }));
5716
+ const write3 = async (a, drafted) => {
5195
5717
  const before = await hashFiles(dir, [
5196
5718
  .../* @__PURE__ */ new Set([...Object.keys(loop.written ?? {}), ...await sourceFiles(dir, spec.source)])
5197
5719
  ]);
5720
+ const texts2 = drafted && corrections.all.length ? await snapshotFiles(dir, [...Object.keys(before), ...corrections.all.map((c) => c.file)]) : void 0;
5198
5721
  const files = await spec.write(a);
5722
+ if (drafted) {
5723
+ const draft = {
5724
+ stage: spec.stage,
5725
+ ...drafted.run ? { run: drafted.run } : {},
5726
+ iteration: loop.iteration,
5727
+ at: at2
5728
+ };
5729
+ await recordAiDraft(dir, change.id, { ...draft, files }).catch(() => void 0);
5730
+ if (texts2) {
5731
+ const settled = await settleCorrections(dir, change.id, {
5732
+ ...draft,
5733
+ before: texts2,
5734
+ files
5735
+ }).catch(() => []);
5736
+ const n = settled.length;
5737
+ const applied = settled.filter((c) => c.state === "applied").length;
5738
+ if (n)
5739
+ notes.push(`The draft had ${n === 1 ? "a person's correction" : `${n} corrections people made`} (stage/corrections.yaml); ${n === 1 ? applied ? "it changed the item" : "it didn't change the item" : `it changed the items of ${applied} of them`}.`);
5740
+ }
5741
+ }
5199
5742
  loop.source = spec.source;
5200
5743
  loop.written = await hashFiles(dir, [
5201
5744
  .../* @__PURE__ */ new Set([...files, ...await sourceFiles(dir, spec.source)])
@@ -5205,7 +5748,7 @@ async function runStageLoop(root, changeId, spec, o) {
5205
5748
  changed.add(f);
5206
5749
  if (loop.failedAnswer) {
5207
5750
  if (loop.failedAnswer !== spec.source)
5208
- await rm3(join10(dir, ...loop.failedAnswer.split("/")), { force: true });
5751
+ await rm3(join11(dir, ...loop.failedAnswer.split("/")), { force: true });
5209
5752
  delete loop.failedAnswer;
5210
5753
  }
5211
5754
  return files;
@@ -5225,18 +5768,22 @@ async function runStageLoop(root, changeId, spec, o) {
5225
5768
  let carried;
5226
5769
  let reviewAgain = false;
5227
5770
  if (!o.resume && loop.state === "waiting" && loop.run) {
5228
- const run = await readFile8(join10(runsDirOf(root), change.id, loop.run, "run.json"), "utf8").then((t) => {
5771
+ const run = await readFile9(join11(runsDirOf(root), change.id, loop.run, "run.json"), "utf8").then((t) => {
5229
5772
  try {
5230
5773
  return JSON.parse(t).state ?? "unreadable";
5231
5774
  } catch {
5232
5775
  return "unreadable";
5233
5776
  }
5234
5777
  }, (e) => e.code === "ENOENT" ? void 0 : "unreadable").catch(() => void 0);
5235
- const lens = loop.review ? (await readRoundMeta(root, change.id, loop.review.gate, loop.review.round))?.lenses.find((l) => l.runId === loop.run)?.lens ?? "AI" : void 0;
5778
+ const metaLenses = loop.review ? (await readRoundMeta(root, change.id, loop.review.gate, loop.review.round))?.lenses ?? [] : [];
5779
+ const batched = metaLenses.filter((l) => l.batch === loop.run && l.state === "waiting").map((l) => l.lens);
5780
+ const lens = loop.review ? metaLenses.find((l) => l.runId === loop.run)?.lens ?? (batched.length ? listed(batched) : "AI") : void 0;
5236
5781
  const out = ["waiting", "prepared", "produced", "unreadable", "failed"];
5237
5782
  if (run !== void 0 && out.includes(run)) {
5238
- notes.push(lens ? `The ${lens} lens's review of the draft is still with your assistant (run ${loop.run}).` : `The draft is still with your assistant (run ${loop.run}).`);
5239
- return result("waiting", { waiting: { runId: loop.run, ...lens ? { lens } : {} } });
5783
+ notes.push(batched.length ? `The critic's ${lens} ${batched.length === 1 ? "lens's" : "lenses'"} review of the draft is still with your assistant (run ${loop.run}).` : lens ? `The ${lens} lens's review of the draft is still with your assistant (run ${loop.run}).` : `The draft is still with your assistant (run ${loop.run}).`);
5784
+ return result("waiting", {
5785
+ waiting: batched.length ? { runId: loop.run, lenses: batched } : { runId: loop.run, ...lens ? { lens } : {} }
5786
+ });
5240
5787
  }
5241
5788
  if (lens) {
5242
5789
  reviewAgain = true;
@@ -5253,7 +5800,7 @@ async function runStageLoop(root, changeId, spec, o) {
5253
5800
  const edited = !o.resume && !carried && loop.state !== "waiting" && loop.iteration > 0 ? await editedFiles(dir, loop, spec.source) : [];
5254
5801
  if (edited.length) {
5255
5802
  const kept = await keepFiles(dir, rel, spec.stage, loop.iteration + 1, edited);
5256
- if (o.force || unreadAnswers.length) {
5803
+ if (o.force || unreadAnswers.length || corrections.waiting.length) {
5257
5804
  personDraft = await draftText(dir, edited);
5258
5805
  (loop.byPerson ??= []).push({
5259
5806
  iteration: loop.iteration + 1,
@@ -5262,7 +5809,7 @@ async function runStageLoop(root, changeId, spec, o) {
5262
5809
  kept,
5263
5810
  how: "redrafted"
5264
5811
  });
5265
- notes.push(`A person edited ${edited.join(", ")}; a copy is in ${kept}, and your assistant drafts again from those files${o.force ? " (--force)" : ", with the answers"}.`);
5812
+ notes.push(`A person edited ${edited.join(", ")}; a copy is in ${kept}, and your assistant drafts again from those files${o.force ? " (--force)" : unreadAnswers.length ? ", with the answers" : ", with the corrections"}.`);
5266
5813
  } else {
5267
5814
  loop.iteration++;
5268
5815
  (loop.byPerson ??= []).push({
@@ -5276,9 +5823,9 @@ async function runStageLoop(root, changeId, spec, o) {
5276
5823
  let problems = await spec.currentProblems?.() ?? [];
5277
5824
  const answerFile = loop.failedAnswer && edited.includes(loop.failedAnswer) ? loop.failedAnswer : cur === void 0 && !spec.source.endsWith("/") && edited.includes(spec.source) ? spec.source : void 0;
5278
5825
  if (answerFile) {
5279
- const text2 = await readFile8(join10(dir, ...answerFile.split("/")), "utf8").catch(() => void 0);
5280
- if (text2 !== void 0) {
5281
- const p = spec.parse(text2);
5826
+ const text4 = await readFile9(join11(dir, ...answerFile.split("/")), "utf8").catch(() => void 0);
5827
+ if (text4 !== void 0) {
5828
+ const p = spec.parse(text4);
5282
5829
  const bad = p.issues.filter((i) => i.blocking);
5283
5830
  if (p.answer !== void 0 && !bad.length) {
5284
5831
  cur = p.answer;
@@ -5323,7 +5870,7 @@ async function runStageLoop(root, changeId, spec, o) {
5323
5870
  })) : [];
5324
5871
  const answers = await questionsOf(loop.questions);
5325
5872
  const unread = answers.filter((q2) => q2.state === "answered" && !loop.read.includes(q2.id));
5326
- if (!o.resume && !o.force && loop.state === "done" && unread.length === 0) {
5873
+ if (!o.resume && !o.force && loop.state === "done" && unread.length === 0 && corrections.waiting.length === 0) {
5327
5874
  const cur = await spec.current();
5328
5875
  if (cur !== void 0) {
5329
5876
  issues = (await spec.check(cur, { onFile: true })).filter((i) => i.blocking);
@@ -5430,15 +5977,15 @@ async function runStageLoop(root, changeId, spec, o) {
5430
5977
  const risks = issues.filter((i) => !i.blocking && i.risk);
5431
5978
  if (risks.length)
5432
5979
  loop.handedBack = [.../* @__PURE__ */ new Set([...loop.handedBack ?? [], ...risks.map(riskKey)])].sort();
5433
- const back = handBacks(fix, answered, risks);
5434
- const last = (fix.length || risks.length) && !personDraft && (await sourceFiles(dir, spec.source)).length ? `${rel}/${spec.source}` : void 0;
5980
+ const back = handBacks(fix, answered, risks, corrections.all);
5981
+ const last = (fix.length || risks.length || corrections.all.length) && !personDraft && (await sourceFiles(dir, spec.source)).length ? `${rel}/${spec.source}` : void 0;
5435
5982
  const sources = [
5436
5983
  {
5437
5984
  id: "instructions",
5438
5985
  kind: "instruction",
5439
5986
  origin: "atticus",
5440
5987
  text: [
5441
- instructionsWith(spec.instructions, fix, answered, risks, last),
5988
+ instructionsWith(spec.instructions, fix, answered, risks, last, corrections.all),
5442
5989
  // The last answer is in the new run's folder already (2026-10-26): an edit, not a
5443
5990
  // rewrite. A person's own draft replaces it, so it isn't carried then.
5444
5991
  ...carriesAnswer && lastAnswerText !== void 0 && !personDraft ? ["", CARRIED_ANSWER_RULE] : [],
@@ -5506,7 +6053,7 @@ async function runStageLoop(root, changeId, spec, o) {
5506
6053
  await writeQuarantineItems(root, change.id, prepared.runId, quarantineItems(pack.defences ?? []));
5507
6054
  if (carriesAnswer && lastAnswerText !== void 0 && !personDraft) {
5508
6055
  const where2 = `${runsRelOf(root)}/${change.id}/${prepared.runId}/${spec.output}`;
5509
- await writeFileAtomic(join10(runsDirOf(root), change.id, prepared.runId, spec.output), lastAnswerText);
6056
+ await writeFileAtomic(join11(runsDirOf(root), change.id, prepared.runId, spec.output), lastAnswerText);
5510
6057
  notes.push(`Your last answer was copied to ${where2}: fix it there.`);
5511
6058
  }
5512
6059
  draftRun = await o.runtime.run(prepared.runId);
@@ -5598,7 +6145,7 @@ ${unfence(run.output)}`,
5598
6145
  failedOutput = unfence(run.output);
5599
6146
  if (answer !== void 0) {
5600
6147
  found.push(...await spec.check(answer));
5601
- files = await write3(answer);
6148
+ files = await write3(answer, { run: run.runId });
5602
6149
  }
5603
6150
  }
5604
6151
  if (answer !== void 0 && !found.some((i) => i.blocking)) {
@@ -5670,6 +6217,12 @@ ${unfence(run.output)}`,
5670
6217
  loop.findings = [...byFp.values()].sort((a, b) => a.fingerprint.localeCompare(b.fingerprint));
5671
6218
  loop.updatedAt = at2;
5672
6219
  loop.state = "drafting";
6220
+ if (answer !== void 0) {
6221
+ for (const f of loop.findings)
6222
+ if (f.question && f.source === "stage" && f.seenIn.at(-1) !== n)
6223
+ await retireOpen(root, f.question, `Draft ${n} no longer has it: ${f.message}`, at2);
6224
+ }
6225
+ await retireSettledEscalations(root, change.id, loop, at2);
5673
6226
  await writeLoop(dir, loop);
5674
6227
  await stopOnProposed(found, n);
5675
6228
  const drafted = [];
@@ -5733,7 +6286,7 @@ ${unfence(run.output)}`,
5733
6286
  }, { ...judges, hold: (q2) => !handedBack.has(q2.id) });
5734
6287
  held.push(...raised.held);
5735
6288
  for (const [i, a] of raised.decidedBy)
5736
- notes.push(`Your assistant asked again what ${a.id} answered ("${mine[i].q.question.trim()}"); the stage reads ${a.answer?.by ?? "the person"}'s answer instead of asking again.`);
6289
+ notes.push(`Your assistant asked again what ${a.id} answered ("${mine[i].q.question.trim()}"); the stage reads ${a.answer ? answerByWords(a.answer.by) : "the person"}'s answer instead of asking again.`);
5737
6290
  qs.push(...raised.raised, ...raised.known);
5738
6291
  mine.forEach((x, i) => {
5739
6292
  if (x.f && raised.of[i])
@@ -5786,15 +6339,15 @@ ${unfence(run.output)}`,
5786
6339
  const to = failedAnswerRel(spec);
5787
6340
  let kept;
5788
6341
  if (to === spec.source) {
5789
- const valid = await readFile8(join10(dir, ...to.split("/")), "utf8").catch(() => void 0);
6342
+ const valid = await readFile9(join11(dir, ...to.split("/")), "utf8").catch(() => void 0);
5790
6343
  if (valid !== void 0 && valid !== failedOutput) {
5791
- await writeFileAtomic(join10(dir, ...lastValidRel(spec).split("/")), valid);
6344
+ await writeFileAtomic(join11(dir, ...lastValidRel(spec).split("/")), valid);
5792
6345
  kept = `${rel}/${lastValidRel(spec)}`;
5793
6346
  }
5794
6347
  }
5795
- await writeFileAtomic(join10(dir, ...to.split("/")), failedOutput);
6348
+ await writeFileAtomic(join11(dir, ...to.split("/")), failedOutput);
5796
6349
  loop.failedAnswer = to;
5797
- loop.written = { ...loop.written ?? {}, [to]: sha2(failedOutput) };
6350
+ loop.written = { ...loop.written ?? {}, [to]: sha3(failedOutput) };
5798
6351
  const lines = new Map(found.filter((i) => i.line).map((i) => [issueFingerprint(i.rule, i.target, i.evidence ?? ""), i.line]));
5799
6352
  return { file: `${rel}/${to}`, ...kept ? { lastValid: kept } : {}, lines };
5800
6353
  };
@@ -5955,19 +6508,20 @@ var SELF_CHECK_RULE = "Check your answer with the stage's own checks before you
5955
6508
  var failedAnswerRel = (spec) => `stage/${spec.output}`;
5956
6509
  var lastValidRel = (spec) => `stage/${spec.stage}-answer.last-valid.yaml`;
5957
6510
  var PERSON_DRAFT = "current-draft";
5958
- var sha2 = (b) => createHash3("sha256").update(b).digest("hex");
6511
+ var sha3 = (b) => createHash4("sha256").update(b).digest("hex");
5959
6512
  async function sourceFiles(dir, source) {
5960
6513
  if (!source.endsWith("/"))
5961
- return await stat(join10(dir, source)).then((s) => s.isFile(), () => false) ? [source] : [];
6514
+ return await stat(join11(dir, source)).then((s) => s.isFile(), () => false) ? [source] : [];
5962
6515
  const sub = source.replace(/\/$/, "");
5963
- return (await readdir7(join10(dir, sub)).catch(() => [])).filter((f) => !f.startsWith(".")).sort().map((f) => `${sub}/${f}`);
6516
+ return (await readdir7(join11(dir, sub)).catch(() => [])).filter((f) => !f.startsWith(".")).sort().map((f) => `${sub}/${f}`);
5964
6517
  }
6518
+ var lf = (b) => b.includes(13) ? Buffer.from(b.toString("utf8").replace(/\r\n?/g, "\n"), "utf8") : b;
5965
6519
  async function hashFiles(dir, files) {
5966
6520
  const out = {};
5967
6521
  for (const f of [...files].sort()) {
5968
- const b = await readFile8(join10(dir, ...f.split("/"))).catch(() => void 0);
6522
+ const b = await readFile9(join11(dir, ...f.split("/"))).catch(() => void 0);
5969
6523
  if (b)
5970
- out[f] = sha2(b);
6524
+ out[f] = sha3(lf(b));
5971
6525
  }
5972
6526
  return out;
5973
6527
  }
@@ -5984,12 +6538,12 @@ async function editedFiles(dir, loop, source) {
5984
6538
  out2.add(f);
5985
6539
  return [...out2].sort();
5986
6540
  }
5987
- const since = await stat(join10(dir, loopRel(loop.stage))).then((s) => s.mtimeMs, () => void 0);
6541
+ const since = await stat(join11(dir, loopRel(loop.stage))).then((s) => s.mtimeMs, () => void 0);
5988
6542
  if (since === void 0)
5989
6543
  return [];
5990
6544
  const out = [];
5991
6545
  for (const f of on) {
5992
- const m = await stat(join10(dir, ...f.split("/"))).then((s) => s.mtimeMs, () => 0);
6546
+ const m = await stat(join11(dir, ...f.split("/"))).then((s) => s.mtimeMs, () => 0);
5993
6547
  if (m > since + 1e3)
5994
6548
  out.push(f);
5995
6549
  }
@@ -5998,9 +6552,9 @@ async function editedFiles(dir, loop, source) {
5998
6552
  async function keepFiles(dir, rel, stage, n, files) {
5999
6553
  const to = `stage/${stage}-by-person/${n}`;
6000
6554
  for (const f of files) {
6001
- const from = join10(dir, ...f.split("/"));
6002
- const dest = join10(dir, ...to.split("/"), ...f.split("/"));
6003
- await mkdir3(join10(dest, ".."), { recursive: true });
6555
+ const from = join11(dir, ...f.split("/"));
6556
+ const dest = join11(dir, ...to.split("/"), ...f.split("/"));
6557
+ await mkdir3(join11(dest, ".."), { recursive: true });
6004
6558
  await copyFile(from, dest).catch(() => void 0);
6005
6559
  }
6006
6560
  return `${rel}/${to}/`;
@@ -6008,13 +6562,13 @@ async function keepFiles(dir, rel, stage, n, files) {
6008
6562
  async function draftText(dir, files) {
6009
6563
  const out = [];
6010
6564
  for (const f of files) {
6011
- const t = await readFile8(join10(dir, ...f.split("/")), "utf8").catch(() => void 0);
6565
+ const t = await readFile9(join11(dir, ...f.split("/")), "utf8").catch(() => void 0);
6012
6566
  out.push(`# ${f}${t === void 0 ? " (deleted by the person)" : ""}`, ...t === void 0 ? [] : [t.replace(/\r\n/g, "\n")]);
6013
6567
  }
6014
6568
  return out.join("\n");
6015
6569
  }
6016
6570
  var HAND_BACKS = "hand-backs";
6017
- function instructionsWith(base, fix, answered, risks = [], last) {
6571
+ function instructionsWith(base, fix, answered, risks = [], last, corrections = []) {
6018
6572
  const out = [base];
6019
6573
  if (last)
6020
6574
  out.push("", `Your last draft is in ${last}, as Atticus wrote it from your answer. Start from it: keep what the hand-backs don't ask you to change, and answer in full again.`);
@@ -6024,9 +6578,11 @@ function instructionsWith(base, fix, answered, risks = [], last) {
6024
6578
  out.push("", `The critic also saw risks in your last draft; the "${HAND_BACKS}" data block lists them with what to fix. Fix each one the brief and the answers allow. A risk doesn't block: this draft is the one that may fix it.`);
6025
6579
  if (answered.length)
6026
6580
  out.push("", `People answered questions; the "${HAND_BACKS}" data block lists each question with its answer. Their answers are part of the brief: follow them, and never ask any of those questions again, in any words, or ask what an answer already says.`);
6581
+ if (corrections.length)
6582
+ out.push("", CORRECTIONS_RULE);
6027
6583
  return out.join("\n");
6028
6584
  }
6029
- function handBacks(fix, answered, risks = []) {
6585
+ function handBacks(fix, answered, risks = [], corrections = []) {
6030
6586
  const out = [];
6031
6587
  if (fix.length)
6032
6588
  out.push("Problems in the last draft:", ...fix.map((i) => `- ${i.message}${i.evidence ? ` (${i.evidence})` : ""}`));
@@ -6034,6 +6590,9 @@ function handBacks(fix, answered, risks = []) {
6034
6590
  out.push(...out.length ? [""] : [], "Risks the critic saw in the last draft:", ...risks.map((i) => `- ${i.message}${i.evidence ? ` (${i.evidence})` : ""}`));
6035
6591
  if (answered.length)
6036
6592
  out.push(...out.length ? [""] : [], "Questions people answered:", ...answered.map((q2) => `- ${q2.question} Answer: ${answerWords(q2) ?? ""}`));
6593
+ const said2 = correctionsText(corrections);
6594
+ if (said2)
6595
+ out.push(...out.length ? [""] : [], said2);
6037
6596
  return out.length ? out.join("\n") : void 0;
6038
6597
  }
6039
6598
  function askedAnswered(h) {
@@ -6054,14 +6613,16 @@ async function lineExists(root, at2) {
6054
6613
  const n = Number(at2.slice(cut + 1));
6055
6614
  if (!Number.isInteger(n) || n < 1 || path.includes(".."))
6056
6615
  return false;
6057
- const text2 = await readFile8(join10(root, ...path.split("/")), "utf8").catch(() => void 0);
6058
- if (text2 === void 0)
6616
+ const text4 = await readFile9(join11(root, ...path.split("/")), "utf8").catch(() => void 0);
6617
+ if (text4 === void 0)
6059
6618
  return false;
6060
- return n <= text2.replace(/\n$/, "").split(/\r?\n/).length;
6619
+ return n <= text4.replace(/\n$/, "").split(/\r?\n/).length;
6061
6620
  }
6621
+ var waitingOf = (w) => w.lenses ? { runId: w.runId, lenses: w.lenses } : { runId: w.runId, lens: w.lens };
6622
+ var listed = (names) => names.length < 2 ? names[0] ?? "" : `${names.slice(0, -1).join(", ")} and ${names.at(-1)}`;
6062
6623
  async function isLensOf(root, change, review, runId) {
6063
6624
  const meta = await readRoundMeta(root, change, review.gate, review.round);
6064
- return !!meta?.lenses.some((l) => l.runId === runId);
6625
+ return !!meta?.lenses.some((l) => l.runId === runId || l.batch === runId);
6065
6626
  }
6066
6627
  async function failedLenses(root, change, gate, round2) {
6067
6628
  const meta = await readRoundMeta(root, change, gate, round2);
@@ -6072,6 +6633,64 @@ function lensRuntimeOf(ctx, o) {
6072
6633
  return void 0;
6073
6634
  return o.criticRuntime ?? o.runtime;
6074
6635
  }
6636
+ async function retireOpen(root, id, why, at2) {
6637
+ const q2 = await readQuestion(root, id).catch(() => void 0);
6638
+ if (q2?.state !== "open")
6639
+ return false;
6640
+ await retireQuestion(root, id, { why, at: at2 });
6641
+ return true;
6642
+ }
6643
+ function settledWords(f) {
6644
+ const r = f.resolution;
6645
+ if (f.state === "open" || !r)
6646
+ return void 0;
6647
+ const reason = r.reason ? `: ${r.reason.trim().replace(/[.!?]+$/, "")}` : "";
6648
+ if (f.state === "dismissed")
6649
+ return `dismissed as not relevant by ${r.by}${reason}`;
6650
+ if (f.state === "waived")
6651
+ return `waived by ${r.by}${reason}`;
6652
+ if (f.state === "applied")
6653
+ return `fixed by ${r.by}${reason}`;
6654
+ if (f.state === "asked")
6655
+ return `asked of ${r.askedOf ?? "its owner"} by ${r.by}`;
6656
+ return void 0;
6657
+ }
6658
+ async function retireSettledEscalations(root, change, loop, at2) {
6659
+ const asked = loop.findings.filter((f) => f.question && f.source === "critic");
6660
+ if (!asked.length || !loop.critic.length)
6661
+ return false;
6662
+ const gate = loop.critic.at(-1).gate;
6663
+ const round2 = (await roundsOf(root, change, gate).catch(() => [])).at(-1);
6664
+ if (round2 === void 0)
6665
+ return false;
6666
+ const meta = await readRoundMeta(root, change, gate, round2).catch(() => void 0);
6667
+ const complete = !(meta?.lenses ?? []).some((l) => l.state === "waiting" || l.state === "failed");
6668
+ const { findings } = await readRound(root, change, gate, round2);
6669
+ let any = false;
6670
+ for (const f of asked) {
6671
+ const hit = findings.find((x) => fingerprint(x.finding) === f.target);
6672
+ let why;
6673
+ if (hit) {
6674
+ const words5 = assistantClosed(hit.finding) ? void 0 : settledWords(hit.finding);
6675
+ if (words5)
6676
+ why = `Its critic finding ${hit.finding.id} was ${words5}.`;
6677
+ } else if (complete)
6678
+ why = `Round ${round2} at the ${gate} gate no longer raises it: ${f.message}`;
6679
+ if (why && await retireOpen(root, f.question, why, at2))
6680
+ any = true;
6681
+ }
6682
+ return any;
6683
+ }
6684
+ async function retireChangeEscalations(root, change, at2) {
6685
+ const dir = changeFolder(root, change);
6686
+ const touched = [];
6687
+ for (const stage of Stage.options) {
6688
+ const { loop, present } = await readStageLoop(dir, change, stage);
6689
+ if (present && await retireSettledEscalations(root, change, loop, at2))
6690
+ touched.push(stage);
6691
+ }
6692
+ return touched;
6693
+ }
6075
6694
  async function criticIssues(root, change, findings, kindOf2) {
6076
6695
  const rel = `${changesRelOf(root)}/${change}`;
6077
6696
  const out = [];
@@ -6102,6 +6721,7 @@ async function criticIssues(root, change, findings, kindOf2) {
6102
6721
  }
6103
6722
  async function lensesIn(root, change, review, o, at2, kindOf2) {
6104
6723
  const rt = o.criticRuntime ?? o.runtime;
6724
+ const out = [];
6105
6725
  for (const w of await waitingLenses(root, change, review.gate, review.round)) {
6106
6726
  const run = await rt.result(w.runId).catch(() => void 0);
6107
6727
  if (run?.state === "validated") {
@@ -6112,7 +6732,29 @@ async function lensesIn(root, change, review, o, at2, kindOf2) {
6112
6732
  await failLens(root, change, review.gate, review.round, w.runId, run ? `its run ${w.runId} was cancelled` : `its run ${w.runId} is no longer there`);
6113
6733
  continue;
6114
6734
  }
6115
- return { waiting: { ...w, gate: review.gate }, issues: [], failed: [], discarded: [] };
6735
+ out.push({ ...w, run });
6736
+ }
6737
+ if (out.length) {
6738
+ const first = out[0];
6739
+ const batch = first.run.batch ? await rt.result(first.run.batch).catch(() => void 0) : void 0;
6740
+ if (batch && batch.state !== "validated" && batch.state !== "cancelled")
6741
+ return {
6742
+ waiting: {
6743
+ runId: batch.runId,
6744
+ lenses: out.filter((x) => x.run.batch === batch.runId).map((x) => x.lens),
6745
+ gate: review.gate,
6746
+ round: review.round
6747
+ },
6748
+ issues: [],
6749
+ failed: [],
6750
+ discarded: []
6751
+ };
6752
+ return {
6753
+ waiting: { runId: first.runId, lens: first.lens, gate: review.gate, round: review.round },
6754
+ issues: [],
6755
+ failed: [],
6756
+ discarded: []
6757
+ };
6116
6758
  }
6117
6759
  const { findings } = await readRound(root, change, review.gate, review.round);
6118
6760
  return {
@@ -6138,6 +6780,8 @@ async function criticRound(root, ctx, change, spec, o, loop, notes, kindOf2, dra
6138
6780
  gate,
6139
6781
  ...lensRuntime ? { runtime: lensRuntime } : {},
6140
6782
  review: { stage: spec.stage, files: draft.files, sources: draft.sources },
6783
+ // ADR 0185: the lenses on the draft go to the person's assistant in one prompt.
6784
+ batchLenses: true,
6141
6785
  now: (o.now ?? /* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z")
6142
6786
  });
6143
6787
  } catch (e) {
@@ -6155,6 +6799,16 @@ async function criticRound(root, ctx, change, spec, o, loop, notes, kindOf2, dra
6155
6799
  return { issues: [] };
6156
6800
  }
6157
6801
  loop.critic = [...loop.critic, { gate, round: r.round }];
6802
+ if (r.batch)
6803
+ return {
6804
+ issues: [],
6805
+ waiting: {
6806
+ runId: r.batch,
6807
+ lenses: r.lenses.filter((l) => l.batch === r.batch).map((l) => l.lens),
6808
+ gate,
6809
+ round: r.round
6810
+ }
6811
+ };
6158
6812
  const first = r.lenses.find((l) => l.state === "waiting" && l.runId);
6159
6813
  if (first)
6160
6814
  return {
@@ -6228,14 +6882,14 @@ var withoutResume = (o) => {
6228
6882
  };
6229
6883
 
6230
6884
  // packages/planner/dist/stages/feature.js
6231
- var import_yaml12 = __toESM(require_dist(), 1);
6232
- import { copyFile as copyFile2, mkdir as mkdir6, readdir as readdir9, readFile as readFile12 } from "node:fs/promises";
6233
- import { basename as basename2, join as join14 } from "node:path";
6885
+ var import_yaml13 = __toESM(require_dist(), 1);
6886
+ import { copyFile as copyFile2, mkdir as mkdir6, readdir as readdir9, readFile as readFile13 } from "node:fs/promises";
6887
+ import { basename as basename2, join as join15 } from "node:path";
6234
6888
 
6235
6889
  // packages/planner/dist/stages/knowledge-applies.js
6236
- var import_yaml10 = __toESM(require_dist(), 1);
6237
- import { mkdir as mkdir4, readFile as readFile9, stat as stat2, writeFile as writeFile2 } from "node:fs/promises";
6238
- import { join as join11 } from "node:path";
6890
+ var import_yaml11 = __toESM(require_dist(), 1);
6891
+ import { mkdir as mkdir4, readFile as readFile10, stat as stat2, writeFile as writeFile2 } from "node:fs/promises";
6892
+ import { join as join12 } from "node:path";
6239
6893
  var KNOWLEDGE_INDEX_KINDS = [
6240
6894
  "Pattern",
6241
6895
  "Decision",
@@ -6282,12 +6936,12 @@ var KnowledgeApplies = external_exports.strictObject({
6282
6936
  })).default([])
6283
6937
  });
6284
6938
  var FILE = "knowledge.yaml";
6285
- var pathOf = (root, change) => join11(changeFolder(root, change), FILE);
6939
+ var pathOf = (root, change) => join12(changeFolder(root, change), FILE);
6286
6940
  async function readKnowledgeApplies(root, change) {
6287
- const text2 = await readFile9(pathOf(root, change), "utf8").catch(() => void 0);
6288
- if (text2 === void 0)
6941
+ const text4 = await readFile10(pathOf(root, change), "utf8").catch(() => void 0);
6942
+ if (text4 === void 0)
6289
6943
  return void 0;
6290
- const r = KnowledgeApplies.safeParse((0, import_yaml10.parse)(text2));
6944
+ const r = KnowledgeApplies.safeParse((0, import_yaml11.parse)(text4));
6291
6945
  return r.success ? r.data : void 0;
6292
6946
  }
6293
6947
  async function write(root, doc) {
@@ -6513,8 +7167,8 @@ function knowledgeAppliesMarkdown(doc) {
6513
7167
  }
6514
7168
 
6515
7169
  // packages/planner/dist/stages/estate-context.js
6516
- import { readFile as readFile10 } from "node:fs/promises";
6517
- import { join as join12 } from "node:path";
7170
+ import { readFile as readFile11 } from "node:fs/promises";
7171
+ import { join as join13 } from "node:path";
6518
7172
  var SECTION_KINDS = ["WikiSection", "DocumentSection", "Party", "GlossaryTerm"];
6519
7173
  var SECTION_TOKENS = 1500;
6520
7174
  var SECTION_SHARE = 0.15;
@@ -6604,7 +7258,7 @@ async function estateContext(root, step, input, o) {
6604
7258
  priority: o.sections.priority
6605
7259
  }));
6606
7260
  if (e && o.change) {
6607
- const brief = await readFile10(join12(changeFolder(root, o.change), "brief.md"), "utf8").catch(() => "");
7261
+ const brief = await readFile11(join13(changeFolder(root, o.change), "brief.md"), "utf8").catch(() => "");
6608
7262
  sources.push(...briefEndpointsSource(brief, e));
6609
7263
  }
6610
7264
  const report = reportOf(s, room);
@@ -6639,16 +7293,16 @@ function sectionLine(n) {
6639
7293
  return void 0;
6640
7294
  const x = n.json ?? {};
6641
7295
  const cite3 = /\[[^\]]*\]$/.exec(n.line)?.[0];
6642
- const text2 = (v) => typeof v === "string" && v.trim() ? v.trim() : void 0;
7296
+ const text4 = (v) => typeof v === "string" && v.trim() ? v.trim() : void 0;
6643
7297
  const list2 = (v) => Array.isArray(v) ? v.filter((s) => typeof s === "string") : [];
6644
- const about = n.kind === "GlossaryTerm" ? text2(x.definition) : [
6645
- text2(x.owner) && `owner ${text2(x.owner)}`,
7298
+ const about = n.kind === "GlossaryTerm" ? text4(x.definition) : [
7299
+ text4(x.owner) && `owner ${text4(x.owner)}`,
6646
7300
  list2(x.owns).length && `owns ${list2(x.owns).join(", ")}`,
6647
7301
  list2(x.responsibilities)[0],
6648
- text2(x.quote && x.quote.text)
7302
+ text4(x.quote && x.quote.text)
6649
7303
  ].filter(Boolean).join("; ");
6650
7304
  const word = n.kind === "GlossaryTerm" ? "term" : x.partyKind === "vendor" ? "vendor" : "team";
6651
- const name = text2(x.term) ?? text2(x.name) ?? n.name;
7305
+ const name = text4(x.term) ?? text4(x.name) ?? n.name;
6652
7306
  return `${word} ${n.id} "${name}"${about ? `: ${about}` : ""}${cite3 ? ` ${cite3}` : ""}`;
6653
7307
  }
6654
7308
  async function sectionsFor(root, input, o) {
@@ -6731,18 +7385,18 @@ function dropUncitedQuestions(d) {
6731
7385
  }
6732
7386
 
6733
7387
  // packages/planner/dist/stages/openspec-files.js
6734
- var import_yaml11 = __toESM(require_dist(), 1);
7388
+ var import_yaml12 = __toESM(require_dist(), 1);
6735
7389
  import { existsSync } from "node:fs";
6736
- import { cp, mkdir as mkdir5, mkdtemp, readdir as readdir8, readFile as readFile11, rm as rm4, writeFile as writeFile3 } from "node:fs/promises";
7390
+ import { cp, mkdir as mkdir5, mkdtemp, readdir as readdir8, readFile as readFile12, rm as rm4, writeFile as writeFile3 } from "node:fs/promises";
6737
7391
  import { tmpdir } from "node:os";
6738
- import { basename, dirname as dirname3, join as join13 } from "node:path";
7392
+ import { basename, dirname as dirname3, join as join14 } from "node:path";
6739
7393
  var SPEC_MARK = "<!-- Generated by atticus plan stories from the stories' acceptance criteria. Edit the stories and run the stage again. -->";
6740
7394
  var PROPOSAL_MARK = "<!-- Generated by atticus change feature from brief.md and feature.yaml. Edit feature.yaml and run the stage again. -->";
6741
7395
  var MAX_REQUIREMENT = 480;
6742
7396
  var sentence3 = (s) => /[.!?]$/.test(s.trim()) ? s.trim() : `${s.trim()}.`;
6743
7397
  var bare = (s) => s.trim().replace(/[.\s]+$/, "");
6744
7398
  var cap = (s) => s ? `${s[0].toUpperCase()}${s.slice(1)}` : s;
6745
- var slug2 = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
7399
+ var slug3 = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
6746
7400
  var clip = (s, n) => {
6747
7401
  if (s.length <= n)
6748
7402
  return s;
@@ -6754,7 +7408,7 @@ function openspecRootOf(root) {
6754
7408
  const dir = openspecDirOf(root);
6755
7409
  if (basename(dir) !== "openspec")
6756
7410
  return void 0;
6757
- return existsSync(join13(dir, "config.yaml")) ? dirname3(dir) : void 0;
7411
+ return existsSync(join14(dir, "config.yaml")) ? dirname3(dir) : void 0;
6758
7412
  }
6759
7413
  function metarepoOpenSpec(root) {
6760
7414
  const r = openspecRootOf(root);
@@ -6781,13 +7435,13 @@ function capabilityOf(flow, repo, o) {
6781
7435
  const had = o.existing?.find((s) => s.flow === flow);
6782
7436
  if (had)
6783
7437
  return had.id;
6784
- const part = slug2(flow.split(".").slice(1).join("-") || flow);
6785
- const r = slug2(repo);
7438
+ const part = slug3(flow.split(".").slice(1).join("-") || flow);
7439
+ const r = slug3(repo);
6786
7440
  const name = o.multiRepo && !part.startsWith(r) ? `${r}-${part}` : part;
6787
7441
  return name.slice(0, 80).replace(/-+$/, "") || r;
6788
7442
  }
6789
- function scenarioOf(text2) {
6790
- const t = bare(text2);
7443
+ function scenarioOf(text4) {
7444
+ const t = bare(text4);
6791
7445
  const m = /^\s*given\s+(.+?),?\s+when\s+(.+?),?\s+then\s+(.+)$/is.exec(t) ?? /^\s*()when\s+(.+?),?\s+then\s+(.+)$/is.exec(t);
6792
7446
  const given = m?.[1]?.trim();
6793
7447
  const when = m?.[2]?.trim();
@@ -6843,15 +7497,15 @@ function specDeltas(change, stories, answer, o) {
6843
7497
  } else {
6844
7498
  const fresh = uniqueNames(criteriaOf(s, w).map((c) => scenarioOf(c.text)));
6845
7499
  if (fresh.length) {
6846
- const text2 = requirementText(change, s, w);
7500
+ const text4 = requirementText(change, s, w);
6847
7501
  if (prior) {
6848
7502
  const byName2 = new Map(fresh.map((x) => [x.name.toLowerCase(), x]));
6849
7503
  const kept = prior.scenarios.map((x) => byName2.get(x.name.toLowerCase()) ?? { name: x.name, body: x.rawText.trim() });
6850
7504
  const had2 = new Set(prior.scenarios.map((x) => x.name.toLowerCase()));
6851
7505
  const added = fresh.filter((x) => !had2.has(x.name.toLowerCase()));
6852
- d.modified.push({ name: prior.name, text: text2, scenarios: [...kept, ...added] });
7506
+ d.modified.push({ name: prior.name, text: text4, scenarios: [...kept, ...added] });
6853
7507
  } else
6854
- d.added.push({ name: bare(s.name), text: text2, scenarios: fresh });
7508
+ d.added.push({ name: bare(s.name), text: text4, scenarios: fresh });
6855
7509
  }
6856
7510
  }
6857
7511
  if (d.added.length + d.modified.length + d.removed.length)
@@ -6891,34 +7545,34 @@ function renderSpecDelta(d, mark = SPEC_MARK) {
6891
7545
  }
6892
7546
  var specFiles = (deltas) => Object.fromEntries(deltas.map((d) => [`specs/${d.capability}/spec.md`, renderSpecDelta(d)]));
6893
7547
  async function writeSpecFiles(dir, files, mark = SPEC_MARK) {
6894
- const specs = join13(dir, "specs");
7548
+ const specs = join14(dir, "specs");
6895
7549
  for (const c of await readdir8(specs).catch(() => [])) {
6896
- const f = join13(specs, c, "spec.md");
7550
+ const f = join14(specs, c, "spec.md");
6897
7551
  if (files[`specs/${c}/spec.md`] !== void 0)
6898
7552
  continue;
6899
- const t = await readFile11(f, "utf8").catch(() => void 0);
7553
+ const t = await readFile12(f, "utf8").catch(() => void 0);
6900
7554
  if (t?.startsWith(mark))
6901
- await rm4(join13(specs, c), { recursive: true, force: true });
7555
+ await rm4(join14(specs, c), { recursive: true, force: true });
6902
7556
  }
6903
7557
  const written = [];
6904
- for (const [rel, text2] of Object.entries(files)) {
6905
- const f = join13(dir, ...rel.split("/"));
6906
- const had = await readFile11(f, "utf8").catch(() => void 0);
7558
+ for (const [rel, text4] of Object.entries(files)) {
7559
+ const f = join14(dir, ...rel.split("/"));
7560
+ const had = await readFile12(f, "utf8").catch(() => void 0);
6907
7561
  if (had !== void 0 && !had.startsWith(mark))
6908
7562
  continue;
6909
7563
  await mkdir5(dirname3(f), { recursive: true });
6910
- await writeFileAtomic(f, text2);
7564
+ await writeFileAtomic(f, text4);
6911
7565
  written.push(rel);
6912
7566
  }
6913
7567
  await setSkipSpecs(dir, Object.keys(files).length === 0);
6914
7568
  return written;
6915
7569
  }
6916
7570
  async function setSkipSpecs(dir, skip) {
6917
- const f = join13(dir, ".openspec.yaml");
6918
- const text2 = await readFile11(f, "utf8").catch(() => void 0);
6919
- if (text2 === void 0)
7571
+ const f = join14(dir, ".openspec.yaml");
7572
+ const text4 = await readFile12(f, "utf8").catch(() => void 0);
7573
+ if (text4 === void 0)
6920
7574
  return;
6921
- const doc = (0, import_yaml11.parseDocument)(text2);
7575
+ const doc = (0, import_yaml12.parseDocument)(text4);
6922
7576
  if (Boolean(doc.get("skip_specs")) === skip)
6923
7577
  return;
6924
7578
  if (skip)
@@ -6928,9 +7582,9 @@ async function setSkipSpecs(dir, skip) {
6928
7582
  await writeFile3(f, doc.toString({ lineWidth: 0 }), "utf8");
6929
7583
  }
6930
7584
  function briefWhy(brief) {
6931
- const text2 = brief.replace(/\r\n/g, "\n").replace(/<!--[\s\S]*?-->/g, "");
6932
- const under = /^#{1,3}\s*(?:problem|why|the problem|background)\b[^\n]*\n+([\s\S]*?)(?=\n#{1,3}\s|$)/im.exec(text2)?.[1];
6933
- const paras = (under ?? text2.replace(/^#[^\n]*\n/gm, "")).split(/\n\s*\n/).map((p) => p.replace(/\s+/g, " ").trim()).filter((p) => p && !p.startsWith("|"));
7585
+ const text4 = brief.replace(/\r\n/g, "\n").replace(/<!--[\s\S]*?-->/g, "");
7586
+ const under = /^#{1,3}\s*(?:problem|why|the problem|background)\b[^\n]*\n+([\s\S]*?)(?=\n#{1,3}\s|$)/im.exec(text4)?.[1];
7587
+ const paras = (under ?? text4.replace(/^#[^\n]*\n/gm, "")).split(/\n\s*\n/).map((p) => p.replace(/\s+/g, " ").trim()).filter((p) => p && !p.startsWith("|"));
6934
7588
  return paras[0] ?? "";
6935
7589
  }
6936
7590
  function renderProposal(title, p, brief, o = {}) {
@@ -6975,12 +7629,12 @@ function renderProposal(title, p, brief, o = {}) {
6975
7629
  lines.push("## Impact", "", repos.length ? `- Repos: ${repos.join(", ")}.` : "- Repos: not placed yet.", ...p.flows.filter((f) => f.how === "read").map((f) => `- Reads flow \`${f.id}\`, unchanged.`), "", "## Open questions", "", ...p.gaps.length ? p.gaps.map((g) => `- ${sentence3(g.what.replace(/\s+/g, " "))}`) : ["- None found by the feature stage; see needs.md for what the change waits on."], "");
6976
7630
  return lines.join("\n");
6977
7631
  }
6978
- async function writeProposal(dir, text2) {
6979
- const f = join13(dir, "proposal.md");
6980
- const had = await readFile11(f, "utf8").catch(() => void 0);
7632
+ async function writeProposal(dir, text4) {
7633
+ const f = join14(dir, "proposal.md");
7634
+ const had = await readFile12(f, "utf8").catch(() => void 0);
6981
7635
  if (had !== void 0 && !had.startsWith(PROPOSAL_MARK))
6982
7636
  return false;
6983
- await writeFileAtomic(f, text2);
7637
+ await writeFileAtomic(f, text4);
6984
7638
  return true;
6985
7639
  }
6986
7640
  async function openspecFiles(root, change, stories, answer, estate) {
@@ -6994,11 +7648,11 @@ async function openspecFiles(root, change, stories, answer, estate) {
6994
7648
  const deltas = specDeltas(change, stories, answer, o);
6995
7649
  const capabilityOfFlow = new Map(stories.map((s) => [s.flow, capabilityOf(s.flow, s.repo, o)]).filter(([, c]) => deltas.some((d) => d.capability === c)));
6996
7650
  const dir = changeFolder(root, change);
6997
- const placed = await readFile11(join13(dir, "feature.yaml"), "utf8").then((t) => FeaturePlacement.safeParse((0, import_yaml11.parse)(t))).catch(() => void 0);
7651
+ const placed = await readFile12(join14(dir, "feature.yaml"), "utf8").then((t) => FeaturePlacement.safeParse((0, import_yaml12.parse)(t))).catch(() => void 0);
6998
7652
  let proposal;
6999
7653
  if (placed?.success) {
7000
7654
  const title = await changeStore(root).read(change).then((c) => c.title).catch(() => change);
7001
- const brief = await readFile11(join13(dir, "brief.md"), "utf8").catch(() => "");
7655
+ const brief = await readFile12(join14(dir, "brief.md"), "utf8").catch(() => "");
7002
7656
  proposal = renderProposal(title, placed.data, brief, {
7003
7657
  repoOf: (f) => estate?.flowById.get(f)?.repo,
7004
7658
  capabilities: deltas.map((d) => {
@@ -7064,21 +7718,21 @@ async function validateDraft(root, change, dir, files, o = {}) {
7064
7718
  const osRoot = openspecRootOf(root);
7065
7719
  if (!osRoot)
7066
7720
  return [];
7067
- const tmp = await mkdtemp(join13(tmpdir(), "atticus-validate-"));
7721
+ const tmp = await mkdtemp(join14(tmpdir(), "atticus-validate-"));
7068
7722
  try {
7069
- const os = join13(osRoot, "openspec");
7070
- await mkdir5(join13(tmp, "openspec", "changes", change), { recursive: true });
7723
+ const os = join14(osRoot, "openspec");
7724
+ await mkdir5(join14(tmp, "openspec", "changes", change), { recursive: true });
7071
7725
  for (const f of ["config.yaml", "schemas", "specs"])
7072
- if (existsSync(join13(os, f)))
7073
- await cp(join13(os, f), join13(tmp, "openspec", f), { recursive: true });
7074
- const target = join13(tmp, "openspec", "changes", change);
7726
+ if (existsSync(join14(os, f)))
7727
+ await cp(join14(os, f), join14(tmp, "openspec", f), { recursive: true });
7728
+ const target = join14(tmp, "openspec", "changes", change);
7075
7729
  for (const f of [".openspec.yaml", "proposal.md", "design.md", "stories.md", "tasks.md"])
7076
- if (existsSync(join13(dir, f)))
7077
- await cp(join13(dir, f), join13(target, f));
7078
- for (const [rel, text2] of Object.entries(files)) {
7079
- const f = join13(target, ...rel.split("/"));
7730
+ if (existsSync(join14(dir, f)))
7731
+ await cp(join14(dir, f), join14(target, f));
7732
+ for (const [rel, text4] of Object.entries(files)) {
7733
+ const f = join14(target, ...rel.split("/"));
7080
7734
  await mkdir5(dirname3(f), { recursive: true });
7081
- await writeFile3(f, text2, "utf8");
7735
+ await writeFile3(f, text4, "utf8");
7082
7736
  }
7083
7737
  await setSkipSpecs(target, !Object.keys(files).some((f) => f.startsWith("specs/")));
7084
7738
  const r = await createOpenSpec({ root: tmp, ...o.bin ? { bin: o.bin } : {} }).validate({
@@ -7157,15 +7811,15 @@ async function pastCorrectionsSource(root, q2) {
7157
7811
  }).catch(() => []);
7158
7812
  if (!past.length)
7159
7813
  return void 0;
7160
- const text2 = pastCorrectionsText(past, Math.floor(q2.budget * PAST_CORRECTIONS_SHARE));
7161
- if (!text2)
7814
+ const text4 = pastCorrectionsText(past, Math.floor(q2.budget * PAST_CORRECTIONS_SHARE));
7815
+ if (!text4)
7162
7816
  return void 0;
7163
7817
  return {
7164
7818
  source: {
7165
7819
  id: "past-corrections",
7166
7820
  kind: "artifact",
7167
7821
  origin: past.map((p) => `${p.folder}/stage/${p.stage}.drafted/ and .merged/`).join("; "),
7168
- text: text2,
7822
+ text: text4,
7169
7823
  classification: "internal",
7170
7824
  priority: 60
7171
7825
  },
@@ -7238,20 +7892,20 @@ Rules:
7238
7892
  var isTemplate = (brief) => brief.replace(/<!--[\s\S]*?-->/g, "").split("\n").filter((l) => l.trim() && !/^#/.test(l.trim())).length === 0;
7239
7893
  async function personStories(dir) {
7240
7894
  const out = [];
7241
- for (const f of (await readdir9(join14(dir, "stories")).catch(() => [])).sort()) {
7895
+ for (const f of (await readdir9(join15(dir, "stories")).catch(() => [])).sort()) {
7242
7896
  if (!f.endsWith(".md"))
7243
7897
  continue;
7244
- const text2 = await readFile12(join14(dir, "stories", f), "utf8");
7245
- if (!text2.startsWith(GENERATED_MARK))
7246
- out.push({ file: `stories/${f}`, text: text2 });
7898
+ const text4 = await readFile13(join15(dir, "stories", f), "utf8");
7899
+ if (!text4.startsWith(GENERATED_MARK))
7900
+ out.push({ file: `stories/${f}`, text: text4 });
7247
7901
  }
7248
7902
  return out;
7249
7903
  }
7250
7904
  async function readPlacement(dir) {
7251
- const text2 = await readFile12(join14(dir, FEATURE_FILE), "utf8").catch(() => void 0);
7252
- if (text2 === void 0)
7905
+ const text4 = await readFile13(join15(dir, FEATURE_FILE), "utf8").catch(() => void 0);
7906
+ if (text4 === void 0)
7253
7907
  return void 0;
7254
- const r = FeaturePlacement.safeParse((0, import_yaml12.parse)(text2));
7908
+ const r = FeaturePlacement.safeParse((0, import_yaml13.parse)(text4));
7255
7909
  return r.success ? r.data : void 0;
7256
7910
  }
7257
7911
  async function conflictsOf(root, change, p, e) {
@@ -7269,7 +7923,7 @@ async function conflictsOf(root, change, p, e) {
7269
7923
  continue;
7270
7924
  const dir = changeFolder(root, other.id);
7271
7925
  const theirs = new Set((await readPlacement(dir))?.flows.map((f) => f.id) ?? []);
7272
- for (const f of await readdir9(join14(dir, "target-flows")).catch(() => []))
7926
+ for (const f of await readdir9(join15(dir, "target-flows")).catch(() => []))
7273
7927
  if (f.endsWith(".yaml"))
7274
7928
  theirs.add(f.slice(0, -5));
7275
7929
  const flows = [...mine].filter((x) => theirs.has(x)).sort();
@@ -7294,10 +7948,10 @@ async function writePlacement(root, change, p, estate, by = "atticus change feat
7294
7948
  gaps.push(g);
7295
7949
  }
7296
7950
  const full = FeaturePlacement.parse({ ...p, gaps, conflicts, questions: [] });
7297
- await writeFileAtomic(join14(dir, FEATURE_FILE), `# The feature placed against the estate (ADR 0087). Drafted by ${by}; review it in the feature PR.
7298
- ${(0, import_yaml12.stringify)(full, { lineWidth: 0 })}`);
7299
- await writeFileAtomic(join14(dir, "feature.md"), renderFeature(full));
7300
- const brief = await readFile12(join14(dir, "brief.md"), "utf8").catch(() => "");
7951
+ await writeFileAtomic(join15(dir, FEATURE_FILE), `# The feature placed against the estate (ADR 0087). Drafted by ${by}; review it in the feature PR.
7952
+ ${(0, import_yaml13.stringify)(full, { lineWidth: 0 })}`);
7953
+ await writeFileAtomic(join15(dir, "feature.md"), renderFeature(full));
7954
+ const brief = await readFile13(join15(dir, "brief.md"), "utf8").catch(() => "");
7301
7955
  const proposal = renderProposal(change.title, full, brief, {
7302
7956
  repoOf: (f) => estate.flowById.get(f)?.repo
7303
7957
  });
@@ -7376,21 +8030,21 @@ async function featureStage(root, changeId, o) {
7376
8030
  let designRel = (await readPlacement(dir))?.design?.file;
7377
8031
  if (o.fromFigma) {
7378
8032
  designRel = `design/${basename2(o.fromFigma).replace(/[^\w.-]+/g, "-")}`;
7379
- await mkdir6(join14(dir, "design"), { recursive: true });
7380
- await copyFile2(o.fromFigma, join14(dir, ...designRel.split("/")));
8033
+ await mkdir6(join15(dir, "design"), { recursive: true });
8034
+ await copyFile2(o.fromFigma, join15(dir, ...designRel.split("/")));
7381
8035
  }
7382
8036
  let design;
7383
8037
  if (designRel) {
7384
- const text2 = await readFile12(join14(dir, ...designRel.split("/")), "utf8").catch(() => void 0);
7385
- if (text2 === void 0)
8038
+ const text4 = await readFile13(join15(dir, ...designRel.split("/")), "utf8").catch(() => void 0);
8039
+ if (text4 === void 0)
7386
8040
  throw new PlannerError("refused", `The design file ${rel}/${designRel} isn't there; run with --from-figma <file.json>.`);
7387
8041
  try {
7388
- design = readFigma(text2);
8042
+ design = readFigma(text4);
7389
8043
  } catch (e) {
7390
8044
  throw new PlannerError("refused", e.message);
7391
8045
  }
7392
8046
  }
7393
- const brief = await readFile12(join14(dir, "brief.md"), "utf8").catch(() => "");
8047
+ const brief = await readFile13(join15(dir, "brief.md"), "utf8").catch(() => "");
7394
8048
  const stories = await personStories(dir);
7395
8049
  const attached = await changeInputSources(dir, rel);
7396
8050
  const hasBrief = !!brief.trim() && !isTemplate(brief);
@@ -7530,9 +8184,9 @@ async function featureStage(root, changeId, o) {
7530
8184
  instructions: instructions3,
7531
8185
  ...context.gaps?.length ? { gaps: context.gaps } : {},
7532
8186
  sources,
7533
- parse: (text2) => {
8187
+ parse: (text4) => {
7534
8188
  let repairs = [];
7535
- const r = parseYamlAs(text2, FeaturePlacement, (d) => {
8189
+ const r = parseYamlAs(text4, FeaturePlacement, (d) => {
7536
8190
  repairs = dropUncitedQuestions(d);
7537
8191
  return fill(d);
7538
8192
  }, "feature.yaml");
@@ -7555,12 +8209,12 @@ async function featureStage(root, changeId, o) {
7555
8209
  }
7556
8210
 
7557
8211
  // packages/planner/dist/stages/architecture.js
7558
- var import_yaml13 = __toESM(require_dist(), 1);
7559
- import { readdir as readdir10, readFile as readFile14, rm as rm5 } from "node:fs/promises";
7560
- import { join as join15 } from "node:path";
8212
+ var import_yaml14 = __toESM(require_dist(), 1);
8213
+ import { readdir as readdir10, readFile as readFile15, rm as rm5 } from "node:fs/promises";
8214
+ import { join as join16 } from "node:path";
7561
8215
 
7562
8216
  // packages/planner/dist/stages/merge.js
7563
- import { createHash as createHash4 } from "node:crypto";
8217
+ import { createHash as createHash5 } from "node:crypto";
7564
8218
  function flowBase(flow) {
7565
8219
  const steps = flow.steps.map((s) => ({
7566
8220
  kind: s.kind,
@@ -7568,7 +8222,7 @@ function flowBase(flow) {
7568
8222
  to: s.to ? { component: s.to.component, entry: s.to.entry } : null,
7569
8223
  store: s.store ? { table: s.store.table, columns: s.store.columns } : null
7570
8224
  }));
7571
- return createHash4("sha256").update(JSON.stringify(steps)).digest("hex");
8225
+ return createHash5("sha256").update(JSON.stringify(steps)).digest("hex");
7572
8226
  }
7573
8227
  function deltaBase(d, today) {
7574
8228
  if (d.change === "new" || !today)
@@ -7657,14 +8311,14 @@ function mergeDelta(d, today) {
7657
8311
  steps.push(...addsAfter(0));
7658
8312
  today.steps.forEach((s, i) => {
7659
8313
  const n = i + 1;
7660
- const rm7 = removes.get(n);
8314
+ const rm8 = removes.get(n);
7661
8315
  const ch = changes.get(n);
7662
- if (rm7)
8316
+ if (rm8)
7663
8317
  steps.push({
7664
8318
  mark: "removed",
7665
8319
  today: n,
7666
8320
  ...fromToday(s),
7667
- ...rm7.why ? { why: rm7.why } : {}
8321
+ ...rm8.why ? { why: rm8.why } : {}
7668
8322
  });
7669
8323
  else {
7670
8324
  const m = ch ? {
@@ -7711,7 +8365,7 @@ function mergeDelta(d, today) {
7711
8365
  }
7712
8366
 
7713
8367
  // packages/planner/dist/stages/story-code.js
7714
- import { readFile as readFile13, realpath, stat as stat3 } from "node:fs/promises";
8368
+ import { readFile as readFile14, realpath, stat as stat3 } from "node:fs/promises";
7715
8369
  import { isAbsolute, relative as relative2, resolve, sep as sep2 } from "node:path";
7716
8370
  var STORY_CODE_AROUND = 3;
7717
8371
  var STORY_CODE_SHARE = 0.2;
@@ -7745,7 +8399,7 @@ async function readCheckoutFile(root, repo, path) {
7745
8399
  return void 0;
7746
8400
  const base = defaultCodeDir(root, repo);
7747
8401
  const file = base ? await inside(base, path) : void 0;
7748
- return file ? await readFile13(file, "utf8").catch(() => void 0) : void 0;
8402
+ return file ? await readFile14(file, "utf8").catch(() => void 0) : void 0;
7749
8403
  }
7750
8404
  async function checkoutHas(root, repo, path) {
7751
8405
  const base = defaultCodeDir(root, repo);
@@ -7796,12 +8450,12 @@ async function storyCodeSource(root, stories, budget, o = {}) {
7796
8450
  continue;
7797
8451
  const base = defaultCodeDir(root, f.repo);
7798
8452
  const file = base ? await inside(base, f.path) : void 0;
7799
- const text2 = file ? await readFile13(file, "utf8").catch(() => void 0) : void 0;
7800
- if (text2 === void 0) {
8453
+ const text4 = file ? await readFile14(file, "utf8").catch(() => void 0) : void 0;
8454
+ if (text4 === void 0) {
7801
8455
  missing.push(key3);
7802
8456
  continue;
7803
8457
  }
7804
- const all2 = text2.replace(/\r\n?/g, "\n").split("\n");
8458
+ const all2 = text4.replace(/\r\n?/g, "\n").split("\n");
7805
8459
  if (all2.length > 1 && all2.at(-1) === "")
7806
8460
  all2.pop();
7807
8461
  const lines = f.lines.filter((l) => l >= 1 && l <= all2.length).sort((a, b) => a - b);
@@ -8244,7 +8898,7 @@ function deployNeeds(deltas, e) {
8244
8898
  if (d.steps.some((s) => s.op !== "remove" && s.store?.table === table))
8245
8899
  add({ first: u.key, then: codeKey(d.repo), why: { kind: "migration", table } });
8246
8900
  }
8247
- const repoOf = (id) => {
8901
+ const repoOf2 = (id) => {
8248
8902
  const r = e.byId.get(id);
8249
8903
  return r?.kind === "Component" && typeof r.repo === "string" ? r.repo : void 0;
8250
8904
  };
@@ -8260,7 +8914,7 @@ function deployNeeds(deltas, e) {
8260
8914
  const to = s.to ?? (was?.to ? { ...was.to, new: false } : void 0);
8261
8915
  if (!to)
8262
8916
  continue;
8263
- const callee = repoOf(to.component);
8917
+ const callee = repoOf2(to.component);
8264
8918
  if (!callee || callee === d.repo || !units.has(codeKey(callee)))
8265
8919
  continue;
8266
8920
  const key3 = entryKey2(to.entry);
@@ -8413,7 +9067,7 @@ flows:
8413
9067
  trigger: <new flows only> { kind: endpoint | action | page | listener | job, what: <plain words>, entry: <the HTTP method and the path, such as "GET /orders/{id}", or a topic> }
8414
9068
  why: <one sentence: why this flow changes>
8415
9069
  consumers: [<when the change breaks the flow's entry: every component that calls it today>]
8416
- runsOn: <a new flow of a component that doesn't exist yet: where it runs> { component: <its id>, system: <the system it belongs to>, platform: <the platform it runs on>, cloud: <the cloud, or on-prem>, framework: <what it is built on> }
9070
+ runsOn: <a new flow of a component that doesn't exist yet: where it runs> { component: <its id>, system: <the system it belongs to>, platform: <the platform it runs on>, cloud: <the cloud, or on-prem>, framework: <what it is built on>, type: <what it is: one of the organisation's component types, by id> }
8417
9071
  steps:
8418
9072
  - op: add | change | remove
8419
9073
  step: <change and remove: the step's number in today's flow, as listed>
@@ -8477,19 +9131,20 @@ function splitAnswer(a) {
8477
9131
  reads.push({ flow: d.flow, why: d.why });
8478
9132
  return { deltas: a.flows.filter((d) => !isEmptyDelta(d)), reads };
8479
9133
  }
9134
+ var SKETCH_RULE = "A person drew some of this change on the canvas: the sketch block holds their steps (their kind, the flow they are on, where each call or publish goes, which table a step reads or writes). Keep every one of those as drawn, on its flow, and fill in what a sketch can't say: its fields, errors, place in the code (at, new, uses), and the steps around it the change needs. Add new flows or steps only where the feature asks for them; never remove or move a drawn step.";
8480
9135
  async function readDeltas(dir) {
8481
9136
  const deltas = [];
8482
9137
  const problems = [];
8483
- for (const f of (await readdir10(join15(dir, TARGET_DIR2)).catch(() => [])).sort()) {
9138
+ for (const f of (await readdir10(join16(dir, TARGET_DIR2)).catch(() => [])).sort()) {
8484
9139
  if (!f.endsWith(".yaml"))
8485
9140
  continue;
8486
9141
  try {
8487
- const text2 = await readFile14(join15(dir, TARGET_DIR2, f), "utf8");
8488
- const r = FlowDelta.safeParse((0, import_yaml13.parse)(text2));
9142
+ const text4 = await readFile15(join16(dir, TARGET_DIR2, f), "utf8");
9143
+ const r = FlowDelta.safeParse((0, import_yaml14.parse)(text4));
8489
9144
  if (r.success)
8490
9145
  deltas.push(r.data);
8491
9146
  else
8492
- problems.push(...schemaProblems(`${TARGET_DIR2}/${f}`, text2, r.error.issues));
9147
+ problems.push(...schemaProblems(`${TARGET_DIR2}/${f}`, text4, r.error.issues));
8493
9148
  } catch (e) {
8494
9149
  problems.push(`${TARGET_DIR2}/${f}: ${e.message.split("\n")[0]}`);
8495
9150
  }
@@ -8760,6 +9415,24 @@ function storeOfTable(e, table) {
8760
9415
  return prefix && e.byId.get(prefix)?.kind === "DataStore" ? prefix : void 0;
8761
9416
  }
8762
9417
  var NUMBERED = /^([A-Za-z]*)(\d+)([^A-Za-z0-9]+)([A-Za-z0-9].*?)(\.[A-Za-z0-9]+)$/;
9418
+ var VERSIONED = /^([A-Za-z]*)(\d+(?:[._]\d+)*)(__)([A-Za-z0-9].*?)(\.[A-Za-z0-9]+)$/;
9419
+ var versionOf = (v) => v.split(/[._]/).map(Number);
9420
+ var cmpVersion = (a, b) => {
9421
+ for (let i = 0; i < Math.max(a.length, b.length); i++) {
9422
+ const d = (a[i] ?? 0) - (b[i] ?? 0);
9423
+ if (d)
9424
+ return d;
9425
+ }
9426
+ return 0;
9427
+ };
9428
+ function versionIn(name, p) {
9429
+ if (p.vsep) {
9430
+ const v = VERSIONED.exec(name);
9431
+ return v && v[1] === p.prefix && v[5] === p.ext ? versionOf(v[2]) : void 0;
9432
+ }
9433
+ const x = NUMBERED.exec(name);
9434
+ return x && x[1] === p.prefix && x[3] === p.sep && x[5] === p.ext ? [Number(x[2])] : void 0;
9435
+ }
8763
9436
  function migrationsOf(e, store) {
8764
9437
  const recs = e.records.filter((r) => r.kind === "Migration" && r.dataStore === store && r.file);
8765
9438
  const nested = recs.length > 0 && recs.every((r) => r.file.split(/[\\/]/).length > 2 && fileOf2(r.file) === "migration.sql");
@@ -8769,7 +9442,22 @@ function migrationsOf(e, store) {
8769
9442
  const all2 = parts.every((p) => p) && parts.length > 0;
8770
9443
  const same = (k) => all2 && new Set(parts.map((p) => p[k])).size === 1;
8771
9444
  let pattern;
8772
- if (same(1) && same(3) && same(5)) {
9445
+ const vparts = names.map((n) => VERSIONED.exec(n));
9446
+ const dotted = vparts.map((v) => v?.[2]?.match(/[._]/)?.[0]).find((x) => x);
9447
+ if (dotted && vparts.every((v) => v) && new Set(vparts.map((v) => v[1])).size === 1 && new Set(vparts.map((v) => v[5])).size === 1) {
9448
+ const at2 = vparts.reduce((a, b) => cmpVersion(versionOf(a[2]), versionOf(b[2])) >= 0 ? a : b);
9449
+ const newest = versionOf(at2[2]);
9450
+ pattern = {
9451
+ prefix: vparts[0][1],
9452
+ sep: "__",
9453
+ ext: vparts[0][5],
9454
+ width: 0,
9455
+ newest: newest[0],
9456
+ version: newest,
9457
+ vtext: at2[2].split(/[._]/),
9458
+ vsep: dotted
9459
+ };
9460
+ } else if (same(1) && same(3) && same(5)) {
8773
9461
  const nums = parts.map((p) => p[2]);
8774
9462
  const padded = nums.some((x) => x.startsWith("0")) && new Set(nums.map((x) => x.length)).size === 1;
8775
9463
  pattern = {
@@ -8780,8 +9468,11 @@ function migrationsOf(e, store) {
8780
9468
  newest: Math.max(...nums.map(Number))
8781
9469
  };
8782
9470
  }
8783
- const num = (n) => Number(NUMBERED.exec(n)?.[2] ?? -1);
8784
- const files = [...names].sort((a, b) => pattern ? num(b) - num(a) || b.localeCompare(a) : b.localeCompare(a));
9471
+ const files = [...names].sort((a, b) => {
9472
+ if (!pattern)
9473
+ return b.localeCompare(a);
9474
+ return cmpVersion(versionIn(b, pattern) ?? [-1], versionIn(a, pattern) ?? [-1]) || b.localeCompare(a);
9475
+ });
8785
9476
  const folders = [
8786
9477
  ...new Set(recs.map((r) => unit(r.file).split(/[\\/]/).slice(0, -1).join("/")))
8787
9478
  ];
@@ -8804,6 +9495,12 @@ function nextMigration(m, words5) {
8804
9495
  const p = m.pattern;
8805
9496
  if (!p)
8806
9497
  return void 0;
9498
+ if (p.version && p.vsep) {
9499
+ const next = [...p.vtext ?? p.version.map(String)];
9500
+ const last = next.at(-1);
9501
+ next[next.length - 1] = String(Number(last) + 1).padStart(last.length, "0");
9502
+ return `${p.prefix}${next.join(p.vsep)}__${words5}${p.ext}`;
9503
+ }
8807
9504
  return `${p.prefix}${String(p.newest + 1).padStart(p.width, "0")}${p.sep}${words5}${p.ext}`;
8808
9505
  }
8809
9506
  function columnTypesOf(e, store) {
@@ -8833,17 +9530,17 @@ function migrationProblem(name, table, e, example, onFile = false) {
8833
9530
  return void 0;
8834
9531
  if (m?.files.includes(file)) {
8835
9532
  const where2 = m.folder ? `, in ${m.folder}` : "";
8836
- const stem4 = file.replace(/^[A-Za-z]*\d+[^A-Za-z0-9]+/, "").replace(/\.[A-Za-z0-9]+$/, "");
9533
+ const stem4 = file.replace(/^[A-Za-z]*\d+(?:[._]\d+)*(?:__|[^A-Za-z0-9]+)/, "").replace(/\.[A-Za-z0-9]+$/, "");
8837
9534
  const next = nextMigration(m, stem4);
8838
9535
  return `names the migration ${name}, which ${store} already has: a change to its tables is a new migration beside it${where2}: name the file ${next ?? `as its migrations are named (${m.files.slice(0, 3).join(", ")})`}.`;
8839
9536
  }
8840
9537
  const p = m?.pattern;
8841
9538
  if (m && p) {
8842
- const x = NUMBERED.exec(file);
8843
- const fits = x && x[1] === p.prefix && x[3] === p.sep && x[5] === p.ext && Number(x[2]) > p.newest;
9539
+ const v = versionIn(file, p);
9540
+ const fits = !!v && cmpVersion(v, p.version ?? [p.newest]) > 0;
8844
9541
  if (fits)
8845
9542
  return void 0;
8846
- const what = m.tool === "flyway" ? `Flyway, the newest ${p.prefix}${p.newest}` : `the newest ${m.files[0]}`;
9543
+ const what = m.tool === "flyway" ? `Flyway, the newest ${p.prefix}${p.vtext && p.vsep ? p.vtext.join(p.vsep) : p.newest}` : `the newest ${m.files[0]}`;
8847
9544
  const where2 = m.folder ? `, in ${m.folder}` : "";
8848
9545
  const wordy = /^[A-Za-z0-9][\w.-]*$/.test(name.trim());
8849
9546
  const stem4 = file.replace(/^[A-Za-z]*\d+[^A-Za-z0-9]+/, "").replace(/\.[A-Za-z0-9]+$/, "");
@@ -8892,15 +9589,15 @@ var PLACES_HOW = "at: path:line of the line it goes after, for code that changes
8892
9589
  async function placeIssues(deltas, e, root) {
8893
9590
  const out = [];
8894
9591
  const read = async (repo, path) => root ? await readCheckoutFile(root, repo, path) : void 0;
8895
- const linesOf2 = (text2) => text2.replace(/\r\n?/g, "\n").replace(/\n$/, "").split("\n");
9592
+ const linesOf2 = (text4) => text4.replace(/\r\n?/g, "\n").replace(/\n$/, "").split("\n");
8896
9593
  for (const d of deltas)
8897
9594
  for (const [i, s] of d.steps.entries()) {
8898
9595
  const change = `Change ${i + 1} to ${d.flow}`;
8899
9596
  const issue = (key3, rule, message, blocking = true) => out.push({ rule, target: `${d.flow}#${i + 1}:${key3}`, message, blocking, repo: d.repo });
8900
9597
  for (const u of usesOf(s)) {
8901
9598
  const p = parseAt(u);
8902
- const text2 = p ? await read(d.repo, p.path) : void 0;
8903
- if (!p || text2 === void 0 && !citesFile(e, d.repo, p.path)) {
9599
+ const text4 = p ? await read(d.repo, p.path) : void 0;
9600
+ if (!p || text4 === void 0 && !citesFile(e, d.repo, p.path)) {
8904
9601
  issue("uses", "delta.uses-unknown", `${change} uses ${u}, but ${d.repo} has no such file in its checkout or the catalog: give path:line of the code it calls as the repo has it.`);
8905
9602
  continue;
8906
9603
  }
@@ -8908,7 +9605,7 @@ async function placeIssues(deltas, e, root) {
8908
9605
  issue("uses", "delta.uses-no-line", `${change} uses ${u}, with no line: give path:line of the code it calls (the method's first line).`);
8909
9606
  continue;
8910
9607
  }
8911
- const n = text2 === void 0 ? void 0 : linesOf2(text2).length;
9608
+ const n = text4 === void 0 ? void 0 : linesOf2(text4).length;
8912
9609
  if (n !== void 0 && (p.line < 1 || p.line > n))
8913
9610
  issue("uses", "delta.uses-line", `${change} uses line ${p.line} of ${p.path}, which has ${n} lines in ${d.repo}'s checkout: give the line of the code it calls.`);
8914
9611
  }
@@ -8927,8 +9624,8 @@ async function placeIssues(deltas, e, root) {
8927
9624
  }
8928
9625
  if (s.at) {
8929
9626
  const p = parseAt(s.at);
8930
- const text2 = p ? await read(d.repo, p.path) : void 0;
8931
- if (!p || text2 === void 0 && !citesFile(e, d.repo, p.path)) {
9627
+ const text4 = p ? await read(d.repo, p.path) : void 0;
9628
+ if (!p || text4 === void 0 && !citesFile(e, d.repo, p.path)) {
8932
9629
  issue("at", "delta.at-unknown", `${change} puts the new step at ${s.at}, but ${d.repo} has no such file in its checkout or the catalog: give the path as the repo has it, path:line of the line the step goes after. For code in a file the change creates, write new: <path> instead.`);
8933
9630
  continue;
8934
9631
  }
@@ -8936,7 +9633,7 @@ async function placeIssues(deltas, e, root) {
8936
9633
  issue("at", "delta.at-no-line", `${change} puts the new step at ${s.at}, with no line. Say where its code goes: ${PLACES_HOW}.`);
8937
9634
  continue;
8938
9635
  }
8939
- const lines = text2 === void 0 ? void 0 : linesOf2(text2);
9636
+ const lines = text4 === void 0 ? void 0 : linesOf2(text4);
8940
9637
  if (lines && (p.line < 1 || p.line > lines.length)) {
8941
9638
  issue("at", "delta.at-line", `${change} puts the new step after line ${p.line} of ${p.path}, which has ${lines.length} lines in ${d.repo}'s checkout: give the line it goes after.`);
8942
9639
  continue;
@@ -9103,13 +9800,13 @@ function prototypeNewCallsSource(inputs) {
9103
9800
  ];
9104
9801
  }
9105
9802
  function storeFactsSource(e, p) {
9106
- const text2 = storeFacts(e, storesFor(e, p), touchedTables(e, p));
9107
- return text2 ? [
9803
+ const text4 = storeFacts(e, storesFor(e, p), touchedTables(e, p));
9804
+ return text4 ? [
9108
9805
  {
9109
9806
  id: "store-facts",
9110
9807
  kind: "catalog",
9111
9808
  origin: "the catalog's data stores, tables, columns and migrations (deterministic)",
9112
- text: text2,
9809
+ text: text4,
9113
9810
  classification: "internal",
9114
9811
  priority: 90
9115
9812
  }
@@ -9375,8 +10072,8 @@ function checkDeltas(deltas, e, p, reads = [], o = {}) {
9375
10072
  g.migration = true;
9376
10073
  }
9377
10074
  }
9378
- const named = fieldNames(s.fields).length + (s.store?.columns.length ?? 0);
9379
- if (kind && MOVES_DATA.has(kind) && named === 0)
10075
+ const named2 = fieldNames(s.fields).length + (s.store?.columns.length ?? 0);
10076
+ if (kind && MOVES_DATA.has(kind) && named2 === 0)
9380
10077
  issue("delta.no-fields", at2, `Change ${i + 1} to ${t} (${kind}) names no fields: say what it sends, returns, reads or stores.`, d.repo);
9381
10078
  });
9382
10079
  if (today && d.change === "modify")
@@ -9477,10 +10174,10 @@ var GRPC_CODES = [
9477
10174
  "UNAUTHENTICATED"
9478
10175
  ];
9479
10176
  var pascal = (c) => c.toLowerCase().split("_").map((w) => w[0].toUpperCase() + w.slice(1)).join("");
9480
- function grpcCodesIn(text2) {
10177
+ function grpcCodesIn(text4) {
9481
10178
  return GRPC_CODES.filter((c) => {
9482
10179
  const name = c.includes("_") ? pascal(c) : `(?:StatusCode|Status|codes|Code)\\.${pascal(c)}`;
9483
- return new RegExp(`(^|[^A-Za-z_])(${c}|${name})([^A-Za-z_]|$)`).test(text2);
10180
+ return new RegExp(`(^|[^A-Za-z_])(${c}|${name})([^A-Za-z_]|$)`).test(text4);
9484
10181
  });
9485
10182
  }
9486
10183
  function newRpcStatuses(d, e) {
@@ -9738,7 +10435,12 @@ function renderRollback(deltas, e) {
9738
10435
  rows.set(s.store.table, `| ${s.store.table} | writes ${s.store.columns.join(", ") || "rows"} (flow ${d.flow}) | A redeploy of the old code stops the new writes; no schema to undo | The rows the new code wrote stay; a person decides whether to keep them |`);
9739
10436
  }
9740
10437
  if (!rows.size)
9741
- return [];
10438
+ return [
10439
+ "## Rollout and rollback",
10440
+ "",
10441
+ "The change writes and alters no table, so it has nothing to migrate back: roll back by redeploying the previous version of each repo, in the reverse of the deploy order. Atticus wrote this from the target flows; a person confirms it in the architecture PR.",
10442
+ ""
10443
+ ];
9742
10444
  return [
9743
10445
  "## Rollout and rollback",
9744
10446
  "",
@@ -9751,14 +10453,14 @@ function renderRollback(deltas, e) {
9751
10453
  ];
9752
10454
  }
9753
10455
  async function writeTargetFlows(dir, answer, estate, by = "atticus change architecture") {
9754
- const readsFile = join15(dir, ...READS_FILE.split("/"));
9755
- const deployFile = join15(dir, ...DEPLOY_FILE.split("/"));
10456
+ const readsFile = join16(dir, ...READS_FILE.split("/"));
10457
+ const deployFile = join16(dir, ...DEPLOY_FILE.split("/"));
9756
10458
  const deploy = answer.deploy?.length ? answer.deploy : void 0;
9757
10459
  const a = splitAnswer(answer);
9758
10460
  const keep = new Set(a.deltas.map((d) => `${d.flow}.yaml`));
9759
- for (const f of await readdir10(join15(dir, TARGET_DIR2)).catch(() => []))
10461
+ for (const f of await readdir10(join16(dir, TARGET_DIR2)).catch(() => []))
9760
10462
  if (f.endsWith(".yaml") && !keep.has(f))
9761
- await rm5(join15(dir, TARGET_DIR2, f), { force: true });
10463
+ await rm5(join16(dir, TARGET_DIR2, f), { force: true });
9762
10464
  const files = [];
9763
10465
  for (const d of a.deltas) {
9764
10466
  const today = d.change === "new" ? void 0 : estate.flowById.get(d.flow)?.flow;
@@ -9767,48 +10469,48 @@ async function writeTargetFlows(dir, answer, estate, by = "atticus change archit
9767
10469
  schemaVersion: FLOW_DELTA_SCHEMA_VERSION,
9768
10470
  ...today ? { base: { steps: flowBase(today) } } : {}
9769
10471
  };
9770
- await writeFileAtomic(join15(dir, TARGET_DIR2, `${d.flow}.yaml`), `# The target of flow ${d.flow} (ADR 0087): the steps this change adds, changes or removes. Drafted by ${by}; base pins the version of today's flow it was written against (ADR 0111).
9771
- ${(0, import_yaml13.stringify)(FlowDelta.parse(pinned), { lineWidth: 0 })}`);
10472
+ await writeFileAtomic(join16(dir, TARGET_DIR2, `${d.flow}.yaml`), `# The target of flow ${d.flow} (ADR 0087): the steps this change adds, changes or removes. Drafted by ${by}; base pins the version of today's flow it was written against (ADR 0111).
10473
+ ${(0, import_yaml14.stringify)(FlowDelta.parse(pinned), { lineWidth: 0 })}`);
9772
10474
  files.push(`${TARGET_DIR2}/${d.flow}.yaml`);
9773
10475
  }
9774
- const own = await readFile14(join15(dir, "design.md"), "utf8").catch(() => void 0);
10476
+ const own = await readFile15(join16(dir, "design.md"), "utf8").catch(() => void 0);
9775
10477
  if (own === void 0 || own.startsWith(MARK)) {
9776
- await writeFileAtomic(join15(dir, "design.md"), renderArchitecture(a.deltas, estate, a.reads, deploy));
10478
+ await writeFileAtomic(join16(dir, "design.md"), renderArchitecture(a.deltas, estate, a.reads, deploy));
9777
10479
  files.push("design.md");
9778
10480
  }
9779
10481
  if (deploy)
9780
10482
  await writeFileAtomic(deployFile, `# The order the change deploys in across its repos (ADR 0124 addendum B-arch), as the architecture stage drafted it and its checks passed. Atticus writes this file.
9781
- ${(0, import_yaml13.stringify)({ deploy }, { lineWidth: 0 })}`);
10483
+ ${(0, import_yaml14.stringify)({ deploy }, { lineWidth: 0 })}`);
9782
10484
  else
9783
10485
  await rm5(deployFile, { force: true });
9784
10486
  if (a.reads.length)
9785
10487
  await writeFileAtomic(readsFile, `# The flows the change reads without changing a step (ADR 0087): context, not target flows. Atticus writes this file.
9786
- ${(0, import_yaml13.stringify)({ reads: a.reads }, { lineWidth: 0 })}`);
10488
+ ${(0, import_yaml14.stringify)({ reads: a.reads }, { lineWidth: 0 })}`);
9787
10489
  else
9788
10490
  await rm5(readsFile, { force: true });
9789
10491
  const data = renderData(a.deltas, estate);
9790
- const ownData = await readFile14(join15(dir, "data.md"), "utf8").catch(() => void 0);
10492
+ const ownData = await readFile15(join16(dir, "data.md"), "utf8").catch(() => void 0);
9791
10493
  if (data && (ownData === void 0 || ownData.startsWith(MARK))) {
9792
- await writeFileAtomic(join15(dir, "data.md"), data);
10494
+ await writeFileAtomic(join16(dir, "data.md"), data);
9793
10495
  files.push("data.md");
9794
10496
  } else if (!data && ownData?.startsWith(MARK))
9795
- await rm5(join15(dir, "data.md"), { force: true });
10497
+ await rm5(join16(dir, "data.md"), { force: true });
9796
10498
  return files;
9797
10499
  }
9798
10500
  async function locateInTargetFlows(dir, rel, target) {
9799
- const files = (await readdir10(join15(dir, TARGET_DIR2)).catch(() => [])).filter((f) => f.endsWith(".yaml")).sort();
10501
+ const files = (await readdir10(join16(dir, TARGET_DIR2)).catch(() => [])).filter((f) => f.endsWith(".yaml")).sort();
9800
10502
  const docs = [];
9801
10503
  for (const f of files) {
9802
- const text2 = await readFile14(join15(dir, TARGET_DIR2, f), "utf8").catch(() => void 0);
9803
- if (text2 === void 0)
10504
+ const text4 = await readFile15(join16(dir, TARGET_DIR2, f), "utf8").catch(() => void 0);
10505
+ if (text4 === void 0)
9804
10506
  continue;
9805
- const lc = new import_yaml13.LineCounter();
9806
- docs.push({ file: f, text: text2, doc: (0, import_yaml13.parseDocument)(text2, { lineCounter: lc }), lc });
10507
+ const lc = new import_yaml14.LineCounter();
10508
+ docs.push({ file: f, text: text4, doc: (0, import_yaml14.parseDocument)(text4, { lineCounter: lc }), lc });
9807
10509
  }
9808
10510
  const at2 = (d, offset) => `${rel}/${TARGET_DIR2}/${d.file}:${d.lc.linePos(offset).line}`;
9809
10511
  const steps = (d) => {
9810
10512
  const s = d.doc.get("steps");
9811
- return (0, import_yaml13.isSeq)(s) ? s.items : [];
10513
+ return (0, import_yaml14.isSeq)(s) ? s.items : [];
9812
10514
  };
9813
10515
  const m = /^(.+?)#(\d+)(?::.*)?$/.exec(target);
9814
10516
  const flow = m ? m[1] : target;
@@ -9823,8 +10525,8 @@ async function locateInTargetFlows(dir, rel, target) {
9823
10525
  let first;
9824
10526
  for (const d of docs)
9825
10527
  for (const item of steps(d)) {
9826
- const store = (0, import_yaml13.isMap)(item) ? item.get("store") : void 0;
9827
- if (!(0, import_yaml13.isMap)(store) || store.get("table") !== table)
10528
+ const store = (0, import_yaml14.isMap)(item) ? item.get("store") : void 0;
10529
+ if (!(0, import_yaml14.isMap)(store) || store.get("table") !== table)
9828
10530
  continue;
9829
10531
  const where2 = item.range;
9830
10532
  const here = where2 ? at2(d, where2[0]) : void 0;
@@ -9838,10 +10540,10 @@ async function readTargetFlows(dir) {
9838
10540
  const r = await readDeltas(dir);
9839
10541
  if (!r.deltas.length)
9840
10542
  return void 0;
9841
- const t = await readFile14(join15(dir, ...READS_FILE.split("/")), "utf8").catch(() => void 0);
9842
- const reads = t === void 0 ? void 0 : external_exports.array(ReadFlow).safeParse((0, import_yaml13.parse)(t)?.reads);
9843
- const dt = await readFile14(join15(dir, ...DEPLOY_FILE.split("/")), "utf8").catch(() => void 0);
9844
- const deploy = dt === void 0 ? void 0 : external_exports.array(DeployStep).safeParse((0, import_yaml13.parse)(dt)?.deploy);
10543
+ const t = await readFile15(join16(dir, ...READS_FILE.split("/")), "utf8").catch(() => void 0);
10544
+ const reads = t === void 0 ? void 0 : external_exports.array(ReadFlow).safeParse((0, import_yaml14.parse)(t)?.reads);
10545
+ const dt = await readFile15(join16(dir, ...DEPLOY_FILE.split("/")), "utf8").catch(() => void 0);
10546
+ const deploy = dt === void 0 ? void 0 : external_exports.array(DeployStep).safeParse((0, import_yaml14.parse)(dt)?.deploy);
9845
10547
  return {
9846
10548
  flows: r.deltas,
9847
10549
  reads: reads?.success ? reads.data : [],
@@ -9866,6 +10568,13 @@ async function architectureStage(root, changeId, o) {
9866
10568
  const flowsText = touched.map((f) => `# ${f.flow.id} (repo ${f.repo}). Steps are numbered from 1 in the order listed.
9867
10569
  ${serializeRecord(f.flow)}`).join("\n---\n");
9868
10570
  const osInstructions = await openspecInstructionSource(root, change.id, "architecture");
10571
+ const drawn = /drawn on the canvas by .+?, stroke [a-z0-9]+/i;
10572
+ const sketched = (await readDeltas(dir)).deltas.map((d) => ({ ...d, steps: d.steps.filter((s) => drawn.test(s.why ?? "")) })).filter((d) => d.steps.length);
10573
+ const componentTypes = effectiveComponentTypes((await loadOrgPack(orgPackDirOf(root)).catch(() => void 0))?.componentTypes ?? []);
10574
+ const typeRules = [
10575
+ `Component types (runsOn.type of a new flow): ${componentTypes.map((t) => t.id).join(", ")}.`,
10576
+ componentTypeRulesText(componentTypes, orgPackRelOf(root))
10577
+ ].filter(Boolean).join("\n");
9869
10578
  const attached = await changeInputSources(dir, rel);
9870
10579
  const newCalls = prototypeNewCallsSource(attached);
9871
10580
  const sources = [
@@ -9875,7 +10584,7 @@ ${serializeRecord(f.flow)}`).join("\n---\n");
9875
10584
  id: "feature.yaml",
9876
10585
  kind: "artifact",
9877
10586
  origin: `${rel}/feature.yaml`,
9878
- text: (await readFile14(join15(dir, "feature.yaml"), "utf8")).replace(/\r\n/g, "\n"),
10587
+ text: (await readFile15(join16(dir, "feature.yaml"), "utf8")).replace(/\r\n/g, "\n"),
9879
10588
  classification: "internal",
9880
10589
  priority: 100
9881
10590
  },
@@ -9887,6 +10596,30 @@ ${serializeRecord(f.flow)}`).join("\n---\n");
9887
10596
  classification: "internal",
9888
10597
  priority: 95
9889
10598
  },
10599
+ // ADR 0180: what the organisation's component types must and must not do, for a new
10600
+ // component the stage drafts (data, never instructions); nothing when no type says anything.
10601
+ ...typeRules ? [
10602
+ {
10603
+ id: "component-types",
10604
+ kind: "artifact",
10605
+ origin: `${orgPackRelOf(root)}/component-types/`,
10606
+ text: typeRules,
10607
+ classification: "internal",
10608
+ priority: 90
10609
+ }
10610
+ ] : [],
10611
+ // ADR 0179: what a person drew on the canvas is the frame the stage fills in, never redrawn.
10612
+ ...sketched.length ? [
10613
+ {
10614
+ id: "sketch",
10615
+ kind: "artifact",
10616
+ origin: `${rel}/${TARGET_DIR2}/ (drawn on the canvas)`,
10617
+ text: sketched.map((d) => `# ${d.flow}
10618
+ ${(0, import_yaml14.stringify)(d, { lineWidth: 0 })}`).join("\n---\n"),
10619
+ classification: "internal",
10620
+ priority: 98
10621
+ }
10622
+ ] : [],
9890
10623
  // ADR 0111 addendum: how the stores the change may write name their migrations and types.
9891
10624
  ...storeFactsSource(estate, placement),
9892
10625
  // S2: the values the code allows in the columns of the tables the change may store into.
@@ -9957,12 +10690,13 @@ ${serializeRecord(f.flow)}`).join("\n---\n");
9957
10690
  formatFor(classificationLevels(ctx)),
9958
10691
  "",
9959
10692
  SELF_CHECK_RULE,
10693
+ ...sketched.length ? ["", SKETCH_RULE] : [],
9960
10694
  ...newCalls.length ? ["", PROTOTYPE_NEW_RULE] : [],
9961
10695
  ...past ? ["", past.rule] : []
9962
10696
  ].join("\n");
9963
10697
  const patternById = new Map(patterns.map((i) => [i.id, i]));
9964
10698
  const selected = await estateContext(root, "architecture", {
9965
- text: [(0, import_yaml13.stringify)(placement, { lineWidth: 0 }), flowsText].join("\n\n"),
10699
+ text: [(0, import_yaml14.stringify)(placement, { lineWidth: 0 }), flowsText].join("\n\n"),
9966
10700
  title: change.title,
9967
10701
  ids: [...placement.flows.map((f) => f.id), ...placement.features.map((f) => f.id)]
9968
10702
  }, {
@@ -10008,8 +10742,8 @@ ${serializeRecord(f.flow)}`).join("\n---\n");
10008
10742
  instructions: instructions3,
10009
10743
  ...context.gaps?.length ? { gaps: context.gaps } : {},
10010
10744
  sources,
10011
- parse: (text2) => {
10012
- const r = parseYamlAs(text2, Answer3, (d) => ({
10745
+ parse: (text4) => {
10746
+ const r = parseYamlAs(text4, Answer3, (d) => ({
10013
10747
  // The version and the pin are Atticus's (ADR 0111), never the assistant's.
10014
10748
  flows: (Array.isArray(d.flows) ? d.flows : []).map((f) => {
10015
10749
  const { base: _base, schemaVersion: _v, ...rest } = f ?? {};
@@ -10038,7 +10772,7 @@ ${serializeRecord(f.flow)}`).join("\n---\n");
10038
10772
  // B-places: an added step's place is a file of its repo, a line it has, a function in it.
10039
10773
  ...await placeIssues(a.flows, estate, root),
10040
10774
  // W5: an endpoint the brief names whose flow the design leaves alone (a note, not blocking).
10041
- ...briefEndpointNotes(await readFile14(join15(dir, "brief.md"), "utf8").catch(() => ""), estate, a.flows)
10775
+ ...briefEndpointNotes(await readFile15(join16(dir, "brief.md"), "utf8").catch(() => ""), estate, a.flows)
10042
10776
  ],
10043
10777
  write: (answer) => writeTargetFlows(dir, answer, estate),
10044
10778
  reviewSources: (a) => [mergedFlowsSource(a.flows, estate, rel)],
@@ -10059,14 +10793,14 @@ ${serializeRecord(f.flow)}`).join("\n---\n");
10059
10793
  var classificationLevels = (ctx) => ctx.orgPack.pack?.classification ?? ["public", "internal", "confidential", "restricted"];
10060
10794
 
10061
10795
  // packages/planner/dist/stages/stories.js
10062
- var import_yaml17 = __toESM(require_dist(), 1);
10063
- import { mkdir as mkdir7, readdir as readdir14, readFile as readFile19, rm as rm6 } from "node:fs/promises";
10064
- import { join as join21 } from "node:path";
10796
+ var import_yaml18 = __toESM(require_dist(), 1);
10797
+ import { mkdir as mkdir7, readdir as readdir14, readFile as readFile20, rm as rm6 } from "node:fs/promises";
10798
+ import { join as join22 } from "node:path";
10065
10799
 
10066
10800
  // packages/planner/dist/needs.js
10067
- var import_yaml14 = __toESM(require_dist(), 1);
10068
- import { readdir as readdir11, readFile as readFile15 } from "node:fs/promises";
10069
- import { join as join16 } from "node:path";
10801
+ var import_yaml15 = __toESM(require_dist(), 1);
10802
+ import { readdir as readdir11, readFile as readFile16 } from "node:fs/promises";
10803
+ import { join as join17 } from "node:path";
10070
10804
  var NEEDS_FILE = "needs.yaml";
10071
10805
  var NEEDS_MD = "needs.md";
10072
10806
  var NEEDS_OUTPUT = "change-needs.yaml";
@@ -10147,11 +10881,34 @@ var daysOf = (root, r) => {
10147
10881
  var stamp = (d) => d.toISOString().replace(/\.\d{3}Z$/, "Z");
10148
10882
  var key = (n) => `${n.item}|${n.for}`;
10149
10883
  var fold4 = (s) => s.replace(/[‘’]/g, "'").replace(/[“”]/g, '"').replace(/\s+/g, " ").trim().toLowerCase();
10884
+ function lineAtFolded(text4, at2) {
10885
+ let count = 0;
10886
+ let pending = false;
10887
+ let line = 1;
10888
+ for (const ch of text4) {
10889
+ if (ch === "\n") {
10890
+ if (count < at2)
10891
+ line++;
10892
+ else
10893
+ break;
10894
+ }
10895
+ if (/\s/.test(ch)) {
10896
+ if (count > 0)
10897
+ pending = true;
10898
+ } else {
10899
+ if (pending)
10900
+ count++;
10901
+ pending = false;
10902
+ count += ch.toLowerCase().length;
10903
+ }
10904
+ }
10905
+ return line;
10906
+ }
10150
10907
  async function readNeeds(root, change) {
10151
- const text2 = await readFile15(join16(changeFolder(root, change), NEEDS_FILE), "utf8").catch(() => void 0);
10152
- if (text2 === void 0)
10908
+ const text4 = await readFile16(join17(changeFolder(root, change), NEEDS_FILE), "utf8").catch(() => void 0);
10909
+ if (text4 === void 0)
10153
10910
  return void 0;
10154
- const r = NeedsFile.safeParse((0, import_yaml14.parse)(text2));
10911
+ const r = NeedsFile.safeParse((0, import_yaml15.parse)(text4));
10155
10912
  return r.success ? r.data : void 0;
10156
10913
  }
10157
10914
  async function changeFiles(dir) {
@@ -10164,14 +10921,14 @@ async function changeFiles(dir) {
10164
10921
  "interfaces.md",
10165
10922
  "tasks.md"
10166
10923
  ]) {
10167
- const t = await readFile15(join16(dir, f), "utf8").catch(() => void 0);
10924
+ const t = await readFile16(join17(dir, f), "utf8").catch(() => void 0);
10168
10925
  if (t !== void 0)
10169
10926
  out.set(f, t.replace(/\r\n/g, "\n"));
10170
10927
  }
10171
10928
  for (const sub of ["stories", "target-flows"])
10172
- for (const f of (await readdir11(join16(dir, sub)).catch(() => [])).sort())
10929
+ for (const f of (await readdir11(join17(dir, sub)).catch(() => [])).sort())
10173
10930
  if (/\.(md|ya?ml)$/.test(f))
10174
- out.set(`${sub}/${f}`, (await readFile15(join16(dir, sub, f), "utf8")).replace(/\r\n/g, "\n"));
10931
+ out.set(`${sub}/${f}`, (await readFile16(join17(dir, sub, f), "utf8")).replace(/\r\n/g, "\n"));
10175
10932
  return out;
10176
10933
  }
10177
10934
  function changeFile(files, cited, change) {
@@ -10185,10 +10942,10 @@ function changeFile(files, cited, change) {
10185
10942
  }
10186
10943
  async function targetsOf(dir, files) {
10187
10944
  const out = /* @__PURE__ */ new Set(["change"]);
10188
- const feature = await readFile15(join16(dir, "feature.yaml"), "utf8").catch(() => void 0);
10945
+ const feature = await readFile16(join17(dir, "feature.yaml"), "utf8").catch(() => void 0);
10189
10946
  if (feature)
10190
10947
  try {
10191
- const f = (0, import_yaml14.parse)(feature);
10948
+ const f = (0, import_yaml15.parse)(feature);
10192
10949
  for (const x of f.features ?? [])
10193
10950
  if (x.id)
10194
10951
  out.add(`feature:${x.id}`);
@@ -10333,10 +11090,10 @@ function renderNeeds(f, reviews = {}) {
10333
11090
  }
10334
11091
  async function write2(root, f) {
10335
11092
  const dir = changeFolder(root, f.change);
10336
- await writeFileAtomic(join16(dir, NEEDS_FILE), `# What this change needs from the organisation (ADR 0101). atticus change needs writes it; status, note, by and at are a person's.
10337
- ${(0, import_yaml14.stringify)(NeedsFile.parse(f), { lineWidth: 0 })}`);
11093
+ await writeFileAtomic(join17(dir, NEEDS_FILE), `# What this change needs from the organisation (ADR 0101). atticus change needs writes it; status, note, by and at are a person's.
11094
+ ${(0, import_yaml15.stringify)(NeedsFile.parse(f), { lineWidth: 0 })}`);
10338
11095
  const k = await readKnowledge(root, { cacheRoot: root });
10339
- await writeFileAtomic(join16(dir, NEEDS_MD), renderNeeds(f, k.reviews));
11096
+ await writeFileAtomic(join17(dir, NEEDS_MD), renderNeeds(f, k.reviews));
10340
11097
  }
10341
11098
  var INSTRUCTIONS3 = (targets) => `# Task: what this change needs from the organisation
10342
11099
 
@@ -10409,11 +11166,11 @@ async function findNeeds(root, change, o) {
10409
11166
  classification: "public",
10410
11167
  trusted: true
10411
11168
  },
10412
- ...[...files].map(([f, text2], i) => ({
11169
+ ...[...files].map(([f, text4], i) => ({
10413
11170
  id: `change-${String(i + 1).padStart(3, "0")}`,
10414
11171
  kind: "document",
10415
11172
  origin: `${change}/${f}`,
10416
- text: text2,
11173
+ text: text4,
10417
11174
  classification: "internal",
10418
11175
  priority: 20
10419
11176
  }))
@@ -10551,7 +11308,7 @@ async function findNeeds(root, change, o) {
10551
11308
  problems.push(...run.errors.length ? run.errors : [`the run ended ${run.state}`]);
10552
11309
  else
10553
11310
  try {
10554
- const a = AnswerIn.safeParse((0, import_yaml14.parse)(unfence2(run.output)));
11311
+ const a = AnswerIn.safeParse((0, import_yaml15.parse)(unfence2(run.output)));
10555
11312
  if (a.success)
10556
11313
  answer = a.data;
10557
11314
  else
@@ -10577,23 +11334,17 @@ async function findNeeds(root, change, o) {
10577
11334
  continue;
10578
11335
  }
10579
11336
  const file = changeFile(files, n.because.file, change);
10580
- const text2 = file !== void 0 ? files.get(file) : void 0;
11337
+ const text4 = file !== void 0 ? files.get(file) : void 0;
10581
11338
  const words5 = fold4(n.because.quote).replace(/[\s.,;:!?]+$/, "");
10582
- const at2 = text2 && words5 ? fold4(text2).indexOf(words5) : -1;
10583
- if (!text2 || at2 < 0) {
10584
- problems.push(text2 ? `${n.item} quotes ${n.because.file}, but "${n.because.quote}" isn't in it` : `${n.item} quotes ${n.because.file}, which isn't one of the change's files`);
11339
+ const at2 = text4 && words5 ? fold4(text4).indexOf(words5) : -1;
11340
+ if (!text4 || at2 < 0) {
11341
+ problems.push(text4 ? `${n.item} quotes ${n.because.file}, but "${n.because.quote}" isn't in it` : `${n.item} quotes ${n.because.file}, which isn't one of the change's files`);
10585
11342
  continue;
10586
11343
  }
10587
11344
  if (seen.has(key(n)))
10588
11345
  continue;
10589
11346
  seen.add(key(n));
10590
- const lines = text2.split("\n");
10591
- let line = 1;
10592
- for (let j = 1; j < lines.length; j++)
10593
- if (fold4(lines.slice(0, j).join("\n")).length < at2)
10594
- line = j + 1;
10595
- else
10596
- break;
11347
+ const line = lineAtFolded(text4, at2);
10597
11348
  fresh.push(needFrom(r, {
10598
11349
  item: n.item,
10599
11350
  for: n.for,
@@ -10693,15 +11444,15 @@ async function mergeStoryRequests(root, change, requests, now) {
10693
11444
  }
10694
11445
 
10695
11446
  // packages/planner/dist/task-knowledge.js
10696
- var import_yaml15 = __toESM(require_dist(), 1);
10697
- import { readdir as readdir12, readFile as readFile16 } from "node:fs/promises";
10698
- import { join as join18 } from "node:path";
11447
+ var import_yaml16 = __toESM(require_dist(), 1);
11448
+ import { readdir as readdir12, readFile as readFile17 } from "node:fs/promises";
11449
+ import { join as join19 } from "node:path";
10699
11450
 
10700
11451
  // packages/planner/dist/knowledge-conflicts.js
10701
- import { join as join17 } from "node:path";
11452
+ import { join as join18 } from "node:path";
10702
11453
  var pageFile = (page) => /\.(md|markdown)$/i.test(page) ? page : `${page}.md`;
10703
11454
  async function codeFactRecords(root) {
10704
- return (await readCatalog(join17(catalogDirOf(root), "generated"), { cacheRoot: root }).catch(() => void 0))?.records ?? [];
11455
+ return (await readCatalog(join18(catalogDirOf(root), "generated"), { cacheRoot: root }).catch(() => void 0))?.records ?? [];
10705
11456
  }
10706
11457
  function conflictTouches(v, used) {
10707
11458
  if (v.conflict.about.some((id) => used.has(id)))
@@ -10808,9 +11559,9 @@ function sectionParts(id) {
10808
11559
  const slash = path.indexOf("/");
10809
11560
  return slash < 0 ? { source: "", page: path, heading } : { source: path.slice(0, slash), page: path.slice(slash + 1), heading };
10810
11561
  }
10811
- function listSteps(text2) {
11562
+ function listSteps(text4) {
10812
11563
  const out = [];
10813
- for (const l of text2.split("\n")) {
11564
+ for (const l of text4.split("\n")) {
10814
11565
  const m = /^\s*(?:\d+[.)]|[-*])\s+(.*)$/.exec(l);
10815
11566
  if (m && m[1].trim())
10816
11567
  out.push(m[1].trim());
@@ -10932,8 +11683,8 @@ function disagree(v, id, other) {
10932
11683
  const pages = itemPages(other);
10933
11684
  return v.conflict.pages.some((p) => pages.includes(`${p.source}/${pageFile2(p.page)}`));
10934
11685
  }
10935
- function placesNamed(text2, input) {
10936
- const t = text2.toLowerCase();
11686
+ function placesNamed(text4, input) {
11687
+ const t = text4.toLowerCase();
10937
11688
  const out = [];
10938
11689
  for (const id of [...input.targets ?? [], ...input.ids])
10939
11690
  if (id.length > 3 && t.includes(id.toLowerCase()) && !out.includes(id))
@@ -10978,13 +11729,13 @@ async function placesContext(root, records) {
10978
11729
  }
10979
11730
  }
10980
11731
  const repos = /* @__PURE__ */ new Map();
10981
- const profiles = join18(orgPackDirOf(root), "profiles");
11732
+ const profiles = join19(orgPackDirOf(root), "profiles");
10982
11733
  for (const d of await readdir12(profiles).catch(() => [])) {
10983
- const text2 = await readFile16(join18(profiles, d, "repos.manifest.yaml"), "utf8").catch(() => void 0);
10984
- if (text2 === void 0)
11734
+ const text4 = await readFile17(join19(profiles, d, "repos.manifest.yaml"), "utf8").catch(() => void 0);
11735
+ if (text4 === void 0)
10985
11736
  continue;
10986
11737
  try {
10987
- for (const r of (0, import_yaml15.parse)(text2)?.repos ?? []) {
11738
+ for (const r of (0, import_yaml16.parse)(text4)?.repos ?? []) {
10988
11739
  const id = str4(r.id);
10989
11740
  if (!id)
10990
11741
  continue;
@@ -11003,7 +11754,7 @@ async function placesContext(root, records) {
11003
11754
  const fw = /* @__PURE__ */ new Map();
11004
11755
  const frameworks = (repo) => {
11005
11756
  if (!fw.has(repo))
11006
- fw.set(repo, readFile16(join18(factsDirOf(root), `${repo}.jsonl`), "utf8").then((t) => {
11757
+ fw.set(repo, readFile17(join19(factsDirOf(root), `${repo}.jsonl`), "utf8").then((t) => {
11007
11758
  const out = /* @__PURE__ */ new Set();
11008
11759
  for (const l of t.split("\n")) {
11009
11760
  if (!l.includes('"framework"'))
@@ -11173,7 +11924,7 @@ async function selectTaskKnowledge(root, change, inputs, o = {}) {
11173
11924
  const said2 = wordsOf(input.changes);
11174
11925
  const changes = new Map([...said2].filter(([s]) => !context.has(s)));
11175
11926
  const all2 = new Map([...context, ...wordsOf([input.title]), ...said2]);
11176
- const named = namesOf(input.changes);
11927
+ const named2 = namesOf(input.changes);
11177
11928
  const kept = /* @__PURE__ */ new Map();
11178
11929
  const cands = candidatesOf2(sel);
11179
11930
  const near = o.keep === "words" ? void 0 : await ix.closeness([input.changes.join("; ")], cands.flatMap((c) => nodeKeys(c, items, itemOfPage)));
@@ -11233,7 +11984,7 @@ async function selectTaskKnowledge(root, change, inputs, o = {}) {
11233
11984
  const cos = cosOf(c, item);
11234
11985
  const always = c.named || target || isConfirmed && shared.length > 0;
11235
11986
  const close = cos === void 0 ? false : matched.length === 1 ? cos >= KEEP_ONE_WORD_CLOSE : matched.length === 0 && cos >= KEEP_BY_MEANING;
11236
- const byWords = cos === void 0 ? matched.length > 0 : matched.length >= KEEP_WORDS || matched.some((w) => named.has(w));
11987
+ const byWords = cos === void 0 ? matched.length > 0 : matched.length >= KEEP_WORDS || matched.some((w) => named2.has(w));
11237
11988
  const terms = (matched.length ? matched : shared).map((w) => all2.get(w) ?? w);
11238
11989
  const words5 = terms.slice(0, 4).map((w) => `"${w}"`);
11239
11990
  const where2 = places2.length ? ` It names ${and2(places2)}, where this task works.` : "";
@@ -11549,11 +12300,11 @@ function groupNearDuplicates(ks) {
11549
12300
 
11550
12301
  // packages/planner/dist/stages/openspec-seed.js
11551
12302
  import { existsSync as existsSync2 } from "node:fs";
11552
- import { readdir as readdir13, readFile as readFile17 } from "node:fs/promises";
11553
- import { join as join19 } from "node:path";
12303
+ import { readdir as readdir13, readFile as readFile18 } from "node:fs/promises";
12304
+ import { join as join20 } from "node:path";
11554
12305
  var SEED_MARK = "<!-- Generated by atticus spec seed from the repo's verified flows. Run it again to refresh it; delete this line to keep your edits. A person accepts the seed with atticus spec accept <repo>. -->";
11555
12306
  var MAX_REQUIREMENT2 = 480;
11556
- var slug3 = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
12307
+ var slug4 = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
11557
12308
  var bare3 = (s) => s.trim().replace(/\s+/g, " ").replace(/[.\s]+$/, "");
11558
12309
  var lowerFirst = (s) => /^[A-Z][a-z]*(\s|$)/.test(s) ? `${s[0].toLowerCase()}${s.slice(1)}` : s;
11559
12310
  var clip2 = (s, n) => {
@@ -11562,7 +12313,7 @@ var clip2 = (s, n) => {
11562
12313
  const cut = s.slice(0, n - 1);
11563
12314
  return `${cut.slice(0, Math.max(cut.lastIndexOf(" "), n / 2)).trimEnd()}\u2026`;
11564
12315
  };
11565
- var seedChangeId = (repo) => `seed-${slug3(repo)}`.slice(0, 80).replace(/-+$/, "");
12316
+ var seedChangeId = (repo) => `seed-${slug4(repo)}`.slice(0, 80).replace(/-+$/, "");
11566
12317
  function citeOf2(evidence, repo) {
11567
12318
  const seen = /* @__PURE__ */ new Set();
11568
12319
  for (const e of evidence) {
@@ -11692,8 +12443,8 @@ function renderSeedProposal(repo, deltas, skipped, flows) {
11692
12443
  ""
11693
12444
  ].join("\n");
11694
12445
  }
11695
- function addedFlowsOf(text2) {
11696
- const sections = text2.replace(/\r\n/g, "\n").split(/^## /m);
12446
+ function addedFlowsOf(text4) {
12447
+ const sections = text4.replace(/\r\n/g, "\n").split(/^## /m);
11697
12448
  const added = sections.find((x) => /^ADDED Requirements\b/.test(x));
11698
12449
  if (added === void 0)
11699
12450
  return [];
@@ -11707,12 +12458,12 @@ function addedFlowsOf(text2) {
11707
12458
  }
11708
12459
  async function addedIn(dir) {
11709
12460
  const out = [];
11710
- const caps = (await readdir13(join19(dir, "specs")).catch(() => [])).sort();
12461
+ const caps = (await readdir13(join20(dir, "specs")).catch(() => [])).sort();
11711
12462
  for (const c of caps) {
11712
- const text2 = await readFile17(join19(dir, "specs", c, "spec.md"), "utf8").catch(() => void 0);
11713
- if (text2 === void 0 || !/^## ADDED Requirements\b/m.test(text2))
12463
+ const text4 = await readFile18(join20(dir, "specs", c, "spec.md"), "utf8").catch(() => void 0);
12464
+ if (text4 === void 0 || !/^## ADDED Requirements\b/m.test(text4))
11714
12465
  continue;
11715
- out.push({ capability: c, flows: addedFlowsOf(text2) });
12466
+ out.push({ capability: c, flows: addedFlowsOf(text4) });
11716
12467
  }
11717
12468
  return out;
11718
12469
  }
@@ -11721,10 +12472,10 @@ async function openSeeds(root) {
11721
12472
  const out = [];
11722
12473
  const ids = (await readdir13(base, { withFileTypes: true }).catch(() => [])).filter((d) => d.isDirectory() && d.name.startsWith("seed-")).map((d) => d.name).sort();
11723
12474
  for (const id of ids) {
11724
- const dir = join19(base, id);
11725
- if (!existsSync2(join19(dir, ".openspec.yaml")))
12475
+ const dir = join20(base, id);
12476
+ if (!existsSync2(join20(dir, ".openspec.yaml")))
11726
12477
  continue;
11727
- const proposal = await readFile17(join19(dir, "proposal.md"), "utf8").catch(() => "");
12478
+ const proposal = await readFile18(join20(dir, "proposal.md"), "utf8").catch(() => "");
11728
12479
  const repo = /^# Proposal: Starting spec for (\S+)\s*$/m.exec(proposal)?.[1] ?? id.slice(5);
11729
12480
  out.push({ change: id, repo, adds: await addedIn(dir) });
11730
12481
  }
@@ -11740,7 +12491,7 @@ async function seedCollisions(root, change) {
11740
12491
  for (const id of ids) {
11741
12492
  if (id.startsWith("seed-"))
11742
12493
  continue;
11743
- for (const a of await addedIn(join19(base, id)))
12494
+ for (const a of await addedIn(join20(base, id)))
11744
12495
  for (const s of seeds)
11745
12496
  for (const b of s.adds) {
11746
12497
  const flow = a.flows.find((f) => b.flows.includes(f)) ?? (a.capability === b.capability ? a.flows[0] ?? b.flows[0] : void 0);
@@ -11800,11 +12551,11 @@ function storyWords2(stories) {
11800
12551
  }
11801
12552
  return out;
11802
12553
  }
11803
- function trimmed(id, text2, budget, about) {
11804
- if (estimateTokens(text2) <= budget)
11805
- return { text: text2 };
11806
- const lines = text2.split("\n");
11807
- const parts = citedParts(text2);
12554
+ function trimmed(id, text4, budget, about) {
12555
+ if (estimateTokens(text4) <= budget)
12556
+ return { text: text4 };
12557
+ const lines = text4.split("\n");
12558
+ const parts = citedParts(text4);
11808
12559
  const isPart = new Set(parts.map((p) => p.readLine));
11809
12560
  const score = (t) => words4(t).filter((w) => about.has(w)).length;
11810
12561
  const keep = new Set(lines.map((_, i) => i + 1).filter((n) => !isPart.has(n)));
@@ -12038,14 +12789,14 @@ function noteConflictLines(conflicts) {
12038
12789
  var fromLine = (c) => c.from && c.quote ? [` From ${c.from}, quoting: "${c.quote}"`] : [];
12039
12790
 
12040
12791
  // packages/planner/dist/task-context.js
12041
- var import_yaml16 = __toESM(require_dist(), 1);
12042
- import { readFile as readFile18 } from "node:fs/promises";
12043
- import { join as join20 } from "node:path";
12792
+ var import_yaml17 = __toESM(require_dist(), 1);
12793
+ import { readFile as readFile19 } from "node:fs/promises";
12794
+ import { join as join21 } from "node:path";
12044
12795
  var TASK_LINE = /^\s*[-*]\s+\[([ xX]?)\]\s+(\S+)\s+(.*)$/;
12045
- function readTasks2(text2) {
12796
+ function readTasks2(text4) {
12046
12797
  const out = [];
12047
12798
  let repo = "";
12048
- for (const l of text2.replace(/\r\n/g, "\n").split("\n")) {
12799
+ for (const l of text4.replace(/\r\n/g, "\n").split("\n")) {
12049
12800
  const h = /^##\s+(.+?)\s*$/.exec(l);
12050
12801
  if (h) {
12051
12802
  repo = h[1].replace(/[`*]/g, "").trim().split(/\s+/)[0].toLowerCase();
@@ -12065,8 +12816,8 @@ function readTasks2(text2) {
12065
12816
  }
12066
12817
  return out;
12067
12818
  }
12068
- function readStoryFile(text2) {
12069
- const lines = text2.replace(/\r\n/g, "\n").split("\n");
12819
+ function readStoryFile(text4) {
12820
+ const lines = text4.replace(/\r\n/g, "\n").split("\n");
12070
12821
  const title = lines.find((l) => /^#\s/.test(l))?.replace(/^#\s+/, "") ?? "";
12071
12822
  const start = lines.findIndex((l) => /^#\s/.test(l)) + 1;
12072
12823
  const end = lines.findIndex((l, i) => i >= start && /^##\s/.test(l));
@@ -12085,19 +12836,19 @@ function readStoryFile(text2) {
12085
12836
  }
12086
12837
  var cite2 = (repo, e) => e.filter((x) => x.path).map((x) => `${repo}:${x.path}${x.line ? `:${x.line}` : ""}`);
12087
12838
  async function worktreeOf(dir, repo) {
12088
- const t = await readFile18(join20(dir, "repos.yaml"), "utf8").catch(() => void 0);
12839
+ const t = await readFile19(join21(dir, "repos.yaml"), "utf8").catch(() => void 0);
12089
12840
  if (!t)
12090
12841
  return void 0;
12091
12842
  try {
12092
- const d = (0, import_yaml16.parse)(t);
12843
+ const d = (0, import_yaml17.parse)(t);
12093
12844
  return d.repos?.find((r) => r.id === repo)?.worktree;
12094
12845
  } catch {
12095
12846
  return void 0;
12096
12847
  }
12097
12848
  }
12098
- function migrationPath(named, table, estate) {
12099
- if (named.includes("/"))
12100
- return named;
12849
+ function migrationPath(named2, table, estate) {
12850
+ if (named2.includes("/"))
12851
+ return named2;
12101
12852
  const store = estate.byId.get(table)?.dataStore;
12102
12853
  const files = estate.records.filter((r) => r.kind === "Migration" && r.dataStore === store && r.file).map((r) => r.file);
12103
12854
  const nested = files.length > 0 && files.every((f) => f.split("/").length > 2 && f.endsWith("/migration.sql"));
@@ -12105,8 +12856,8 @@ function migrationPath(named, table, estate) {
12105
12856
  ...new Set(files.map((f) => f.split("/").slice(0, nested ? -2 : -1).join("/")))
12106
12857
  ];
12107
12858
  if (!(folders.length === 1 && folders[0]))
12108
- return named;
12109
- return nested && !/\.sql$/i.test(named) ? `${folders[0]}/${named}/migration.sql` : `${folders[0]}/${named}`;
12859
+ return named2;
12860
+ return nested && !/\.sql$/i.test(named2) ? `${folders[0]}/${named2}/migration.sql` : `${folders[0]}/${named2}`;
12110
12861
  }
12111
12862
  async function taskContext(root, changeId, taskId, o = {}) {
12112
12863
  const change = await changeStore(root).read(changeId).catch((e) => {
@@ -12115,18 +12866,18 @@ async function taskContext(root, changeId, taskId, o = {}) {
12115
12866
  const dir = changeFolder(root, change.id);
12116
12867
  const rel = `${changesRelOf(root)}/${change.id}`;
12117
12868
  const notes = [];
12118
- const tasks = readTasks2(await readFile18(join20(dir, "tasks.md"), "utf8").catch(() => ""));
12869
+ const tasks = readTasks2(await readFile19(join21(dir, "tasks.md"), "utf8").catch(() => ""));
12119
12870
  const ctx = await loadChangeContext(root);
12120
12871
  const estate = await loadEstate(root, ctx.records);
12121
12872
  const deltas = (await readDeltas(dir)).deltas.filter((d) => !isEmptyDelta(d));
12122
12873
  const stories = deltas.length ? storiesFromDeltas(deltas, estate) : [];
12123
12874
  const asked = taskId.toLowerCase();
12124
- const named = tasks.some((t) => t.id === asked) ? void 0 : findTask(stories, asked);
12125
- const line = tasks.find((t) => t.id === (named ? taskLabel(named.s, named.t) : asked) && (!named || !t.story || t.story === named.s.id));
12875
+ const named2 = tasks.some((t) => t.id === asked) ? void 0 : findTask(stories, asked);
12876
+ const line = tasks.find((t) => t.id === (named2 ? taskLabel(named2.s, named2.t) : asked) && (!named2 || !t.story || t.story === named2.s.id));
12126
12877
  if (!line)
12127
12878
  throw new PlannerError("not-found", tasks.length ? `There is no task ${taskId} in ${change.id}'s tasks.md. Tasks: ${tasks.map((t) => t.id).join(", ")}.` : `${change.id} has no tasks yet: plan its stories first (atticus next says how).`);
12128
12879
  const id = line.id;
12129
- const found0 = named ?? findTask(stories, id, line.story);
12880
+ const found0 = named2 ?? findTask(stories, id, line.story);
12130
12881
  const dt = found0?.t;
12131
12882
  const ds = found0?.s;
12132
12883
  const key3 = dt?.id ?? id;
@@ -12137,7 +12888,7 @@ async function taskContext(root, changeId, taskId, o = {}) {
12137
12888
  if (!dt)
12138
12889
  notes.push(`Task ${id} isn't one the target flows give, so it has no step or cited code here; read its story.`);
12139
12890
  const storyId = line.story ?? dt?.story;
12140
- const storyText = storyId ? await readFile18(join20(dir, "stories", `${storyId}.md`), "utf8").catch(() => void 0) : void 0;
12891
+ const storyText = storyId ? await readFile19(join21(dir, "stories", `${storyId}.md`), "utf8").catch(() => void 0) : void 0;
12141
12892
  const sf = storyText ? readStoryFile(storyText) : void 0;
12142
12893
  const answer = await readStoriesAnswer(dir);
12143
12894
  const found = answer?.stories.find((s) => s.id === storyId) ?? answer?.stories.find((s) => s.tasks.some((t) => t.id === key3));
@@ -12264,7 +13015,7 @@ async function taskContext(root, changeId, taskId, o = {}) {
12264
13015
  name: s.input.name,
12265
13016
  kind: s.input.kind
12266
13017
  }));
12267
- const worktree = await worktreeOf(dir, line.repo);
13018
+ const worktree = o.at?.worktree ?? await worktreeOf(dir, line.repo);
12268
13019
  if (!worktree)
12269
13020
  notes.push(`${line.repo} has no worktree for ${change.id} yet: open one with atticus workspace open ${change.id} --repos ${line.repo}.`);
12270
13021
  const apply = await metarepoOpenSpec(root)?.applyInstructions(change.id).catch(() => void 0);
@@ -12279,7 +13030,7 @@ async function taskContext(root, changeId, taskId, o = {}) {
12279
13030
  repo: line.repo,
12280
13031
  done: line.done,
12281
13032
  ...storyId ? { story: storyId } : {},
12282
- branch: taskBranch(change.id, id),
13033
+ branch: o.at?.branch ?? taskBranch(change.id, id),
12283
13034
  ...worktree ? { worktree } : {}
12284
13035
  },
12285
13036
  ...storyId && sf ? {
@@ -12337,7 +13088,7 @@ async function taskContext(root, changeId, taskId, o = {}) {
12337
13088
  inputs,
12338
13089
  files,
12339
13090
  uses,
12340
- prompt: dt && ds ? taskPrompt(change.id, ds, dt, own?.length ? ownCriteria : criteria.map((text2) => ({ text: text2, checks: [], outcome: "success" }))) : [
13091
+ prompt: dt && ds ? taskPrompt(change.id, ds, dt, own?.length ? ownCriteria : criteria.map((text4) => ({ text: text4, checks: [], outcome: "success" }))) : [
12341
13092
  `Implement task ${id} of the change ${change.id} in the repo ${line.repo}: ${line.title}.`,
12342
13093
  "Acceptance criteria:",
12343
13094
  ...criteria.map((c) => `- ${c}`),
@@ -12907,7 +13658,7 @@ function taskFiles(d, s, old, entry, evidence, e) {
12907
13658
  });
12908
13659
  }
12909
13660
  const takes = kind === "entry" || kind === "validate";
12910
- const listed = (k) => (s.fields?.[k] ?? []).some((f) => changed.has(foldField(f.split(".").at(-1))));
13661
+ const listed2 = (k) => (s.fields?.[k] ?? []).some((f) => changed.has(foldField(f.split(".").at(-1))));
12911
13662
  const hasCall = owners.some((o) => o.call);
12912
13663
  const today = e.flowById.get(d.flow)?.flow;
12913
13664
  const flowFiles = /* @__PURE__ */ new Set([
@@ -12926,7 +13677,7 @@ function taskFiles(d, s, old, entry, evidence, e) {
12926
13677
  const has = new Set(fields2.map((f) => foldField(f.name)));
12927
13678
  const fresh = [...changed].filter((f) => !has.has(f));
12928
13679
  const held = fields2.filter((f) => required.has(foldField(f.name)) && f.required !== true).map((f) => f.name).filter((x, i, xs) => xs.indexOf(x) === i);
12929
- const own = o.call ? (side2 === "request" || side2 === "message") && listed("sends") || side2 === "response" && listed("returns") : !hasCall && !calls && (takes && side2 !== "response" || kind === "respond" && (side2 === "response" || side2 === "screen"));
13680
+ const own = o.call ? (side2 === "request" || side2 === "message") && listed2("sends") || side2 === "response" && listed2("returns") : !hasCall && !calls && (takes && side2 !== "response" || kind === "respond" && (side2 === "response" || side2 === "screen"));
12930
13681
  if (!held.length && !(own && fresh.length))
12931
13682
  continue;
12932
13683
  const whose = o.label.endsWith("s") ? `${o.label}'` : `${o.label}'s`;
@@ -13252,7 +14003,7 @@ var CriterionObject = external_exports.strictObject({
13252
14003
  quote: Words.optional()
13253
14004
  });
13254
14005
  var StoryCriterion = external_exports.union([
13255
- Words.transform((text2) => CriterionObject.parse({ text: text2 })),
14006
+ Words.transform((text4) => CriterionObject.parse({ text: text4 })),
13256
14007
  CriterionObject
13257
14008
  ]);
13258
14009
  var NEEDS_ERROR_CASE = ["validate", "write", "call-out", "publish"];
@@ -13500,11 +14251,11 @@ function knownFieldNames(e, deltas) {
13500
14251
  }
13501
14252
  return new Set(names.map(foldName).filter(Boolean));
13502
14253
  }
13503
- function fieldLikeNames(text2) {
14254
+ function fieldLikeNames(text4) {
13504
14255
  const out = /* @__PURE__ */ new Set();
13505
- for (const m of text2.matchAll(/(?<![\w.$/{-])[a-z][a-z0-9]*(?:_[a-z0-9]+)+(?![\w(-])/g))
14256
+ for (const m of text4.matchAll(/(?<![\w.$/{-])[a-z][a-z0-9]*(?:_[a-z0-9]+)+(?![\w(-])/g))
13506
14257
  out.add(m[0]);
13507
- for (const m of text2.matchAll(/(?<![\w.$/{-])[a-z]+[A-Z][A-Za-z0-9]*(?![\w(-])/g))
14258
+ for (const m of text4.matchAll(/(?<![\w.$/{-])[a-z]+[A-Z][A-Za-z0-9]*(?![\w(-])/g))
13508
14259
  out.add(m[0]);
13509
14260
  return [...out];
13510
14261
  }
@@ -13549,7 +14300,7 @@ function checkSections(answer, stories, deltas, o = {}) {
13549
14300
  if (!w)
13550
14301
  continue;
13551
14302
  const fields2 = new Set(s.tasks.flatMap((t) => t.fields.map(foldName)));
13552
- const named = [...new Set(s.tasks.flatMap((t) => t.fields))];
14303
+ const named2 = [...new Set(s.tasks.flatMap((t) => t.fields))];
13553
14304
  const criteria = s.tasks.flatMap((t) => (w.tasks.find((x) => x.id === t.id)?.criteria ?? []).map((c) => ({
13554
14305
  t,
13555
14306
  c,
@@ -13576,11 +14327,11 @@ function checkSections(answer, stories, deltas, o = {}) {
13576
14327
  ...stepTasks(s).flatMap((t) => t.to?.new && t.op !== "remove" ? [t.to.entry] : [])
13577
14328
  ];
13578
14329
  if (w.interfaces !== void 0) {
13579
- const listed = Array.isArray(w.interfaces) ? w.interfaces : [];
14330
+ const listed2 = Array.isArray(w.interfaces) ? w.interfaces : [];
13580
14331
  for (const x of [...new Set(added)])
13581
- if (!listed.some((i) => endpointKey(i.entry) === endpointKey(x)))
14332
+ if (!listed2.some((i) => endpointKey(i.entry) === endpointKey(x)))
13582
14333
  push("stories.interface-missing", `${s.id}:${x}`, `Story ${s.id}: the target flow adds ${x}, but ${Array.isArray(w.interfaces) ? "the interface changes don't list it" : `the interface changes say none ("${w.interfaces.none}")`}: list it with its fields (side, type, required, default), whether it is breaking, and its consumers.`, true, s.repo);
13583
- for (const i of listed) {
14334
+ for (const i of listed2) {
13584
14335
  if (!entries.has(endpointKey(i.entry)))
13585
14336
  push("stories.unknown-entry", `${s.id}:${i.entry}`, `Story ${s.id}: the interface change names ${i.entry}, which neither the target flows nor the catalog have. Name an entry of the flow, or add it in the target flows (atticus change architecture).`, true, s.repo);
13586
14337
  if (e)
@@ -13591,7 +14342,7 @@ function checkSections(answer, stories, deltas, o = {}) {
13591
14342
  const target = `${s.id}:${i.entry}:${f.name}`;
13592
14343
  const isNew = f.change !== "removed";
13593
14344
  if (!(isNew ? fields2 : known).has(foldName(f.name)))
13594
- push("stories.invented-field", target, isNew ? `Story ${s.id}: ${i.entry} lists ${f.name} as ${f.change}, but no step of the target flow carries it (the story's fields: ${named.join(", ") || "none"}). Name the field as the target flow does, or add it to the step in the target flows (atticus change architecture).` : `Story ${s.id}: ${i.entry} lists ${f.name} as removed, but neither the target flows nor the catalog know a field of that name.`, true, s.repo);
14345
+ push("stories.invented-field", target, isNew ? `Story ${s.id}: ${i.entry} lists ${f.name} as ${f.change}, but no step of the target flow carries it (the story's fields: ${named2.join(", ") || "none"}). Name the field as the target flow does, or add it to the step in the target flows (atticus change architecture).` : `Story ${s.id}: ${i.entry} lists ${f.name} as removed, but neither the target flows nor the catalog know a field of that name.`, true, s.repo);
13595
14346
  if (!inbound || !isNew || f.side === "response" || !fields2.has(foldName(f.name)))
13596
14347
  continue;
13597
14348
  if (!f.validation)
@@ -13820,11 +14571,11 @@ var oldForm = (question2) => /^Does the criterion ".*" of task \S+ check \S+\? A
13820
14571
  async function settleCoversAnswers(root, change, dir, j) {
13821
14572
  const notes = [];
13822
14573
  const decided = /* @__PURE__ */ new Map();
13823
- const file = join21(dir, "stage", "stories.yaml");
13824
- const text2 = await readFile19(file, "utf8").catch(() => void 0);
13825
- if (text2 === void 0)
14574
+ const file = join22(dir, "stage", "stories.yaml");
14575
+ const text4 = await readFile20(file, "utf8").catch(() => void 0);
14576
+ if (text4 === void 0)
13826
14577
  return { notes, decided };
13827
- const parsed = StageLoop.safeParse((0, import_yaml17.parse)(text2));
14578
+ const parsed = StageLoop.safeParse((0, import_yaml18.parse)(text4));
13828
14579
  if (!parsed.success)
13829
14580
  return { notes, decided };
13830
14581
  const loop = parsed.data;
@@ -13880,7 +14631,7 @@ async function settleCoversAnswers(root, change, dir, j) {
13880
14631
  });
13881
14632
  });
13882
14633
  }
13883
- for (const { c, q: q2, v } of verdicts)
14634
+ for (const { c, q: q2, v } of verdicts.filter((x) => !isAiBy(x.q.answer.by)))
13884
14635
  for (const field of c.fields)
13885
14636
  await personVerdict(j.store, coversInputs(field, c.criterion), {
13886
14637
  verdict: v,
@@ -13959,7 +14710,7 @@ ${s.c.criterion}`;
13959
14710
  loop.questions = loop.questions.filter((id) => !retiredIds.has(id));
13960
14711
  loop.findings.sort((a, b) => a.fingerprint.localeCompare(b.fingerprint));
13961
14712
  await writeFileAtomic(file, `# The stories stage's loop (ADR 0087): drafts, the findings each one had, and the questions it raised. Atticus writes this file.
13962
- ${(0, import_yaml17.stringify)(StageLoop.parse(loop), { lineWidth: 0 })}`);
14713
+ ${(0, import_yaml18.stringify)(StageLoop.parse(loop), { lineWidth: 0 })}`);
13963
14714
  }
13964
14715
  }
13965
14716
  return { notes, decided };
@@ -14354,9 +15105,17 @@ function renderStoryFile(change, s, words5, unjudged = [], x = {}) {
14354
15105
  ...waits.length ? [`A person has raised ${andList2(waits.map((r) => `${r.id} ${r.title}`))} (requests.md)`] : []
14355
15106
  ].map((d) => `- ${sentence5(d)}`), "");
14356
15107
  lines.push("## Traces to", "", `target-flows/${s.flow}.yaml (${s.change} flow ${s.flow}: ${s.name})${x.capability ? `; specs/${x.capability}/spec.md` : ""}`, "");
14357
- lines.push("## Systems and repos", "", s.repo, "", command ? `Build and test with \`${command}\`.` : `repos.manifest.yaml names no build command for ${s.repo}: use the repo's own build and tests.`, "");
15108
+ lines.push("## Systems and repos", "", s.repo, "", buildLine(s.repo, command), "");
14358
15109
  return lines.join("\n");
14359
15110
  }
15111
+ function buildLine(repo, command) {
15112
+ return command ? `Build and test with \`${command}\`.` : `repos.manifest.yaml names no build command for ${repo}: use the repo's own build and tests.`;
15113
+ }
15114
+ function withBuildLine(story, repo, command) {
15115
+ const esc = repo.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
15116
+ const line = new RegExp(`^(?:Build and test with \`.*\`\\.|repos\\.manifest\\.yaml names no build command for ${esc}: use the repo's own build and tests\\.)$`, "gm");
15117
+ return story.replace(line, () => buildLine(repo, command));
15118
+ }
14360
15119
  function renderIndex2(stories, a) {
14361
15120
  return [
14362
15121
  `${GENERATED_MARK} stories from the target flows. -->`,
@@ -14377,9 +15136,9 @@ function renderIndex2(stories, a) {
14377
15136
  ""
14378
15137
  ].join("\n");
14379
15138
  }
14380
- function tickedTasks(text2) {
15139
+ function tickedTasks(text4) {
14381
15140
  const out = /* @__PURE__ */ new Set();
14382
- for (const m of text2.matchAll(/^\s*[-*]\s+\[[xX]\]\s+(.+?)\s*$/gm))
15141
+ for (const m of text4.matchAll(/^\s*[-*]\s+\[[xX]\]\s+(.+?)\s*$/gm))
14383
15142
  out.add(m[1]);
14384
15143
  return out;
14385
15144
  }
@@ -14437,11 +15196,11 @@ async function writeStories(root, change, stories, answer, requests, now, unjudg
14437
15196
  const a = foldAnswer(answer, stories);
14438
15197
  const dir = changeFolder(root, change);
14439
15198
  const keep = new Set(stories.map((s) => `${s.id}.md`));
14440
- for (const f of await readdir14(join21(dir, "stories")).catch(() => [])) {
15199
+ for (const f of await readdir14(join22(dir, "stories")).catch(() => [])) {
14441
15200
  if (keep.has(f) || !f.endsWith(".md"))
14442
15201
  continue;
14443
- if ((await readFile19(join21(dir, "stories", f), "utf8")).startsWith(GENERATED_MARK))
14444
- await rm6(join21(dir, "stories", f));
15202
+ if ((await readFile20(join22(dir, "stories", f), "utf8")).startsWith(GENERATED_MARK))
15203
+ await rm6(join22(dir, "stories", f));
14445
15204
  }
14446
15205
  const { byTask: knowledge } = await storiesKnowledge(root, change, stories, {
14447
15206
  ...extras.estate ? { estate: extras.estate } : {},
@@ -14449,9 +15208,9 @@ async function writeStories(root, change, stories, answer, requests, now, unjudg
14449
15208
  ...extras.asker ? { asker: extras.asker } : {},
14450
15209
  ...extras.knowledge ? { selected: extras.knowledge } : {}
14451
15210
  });
14452
- for (const p of await readdir14(join21(dir, "tasks")).catch(() => []))
14453
- if (p.endsWith(".prompt.md") && (await readFile19(join21(dir, "tasks", p), "utf8")).startsWith(GENERATED_MARK))
14454
- await rm6(join21(dir, "tasks", p));
15211
+ for (const p of await readdir14(join22(dir, "tasks")).catch(() => []))
15212
+ if (p.endsWith(".prompt.md") && (await readFile20(join22(dir, "tasks", p), "utf8")).startsWith(GENERATED_MARK))
15213
+ await rm6(join22(dir, "tasks", p));
14455
15214
  const spec = await openspecFiles(root, change, stories, a, extras.estate);
14456
15215
  const files = [];
14457
15216
  for (const s of stories) {
@@ -14470,24 +15229,24 @@ async function writeStories(root, change, stories, answer, requests, now, unjudg
14470
15229
  knowledge,
14471
15230
  ...spec.capabilityOf.get(s.flow) ? { capability: spec.capabilityOf.get(s.flow) } : {}
14472
15231
  };
14473
- await writeFileAtomic(join21(dir, "stories", `${s.id}.md`), renderStoryFile(change, s, w, unjudged, x));
15232
+ await writeFileAtomic(join22(dir, "stories", `${s.id}.md`), renderStoryFile(change, s, w, unjudged, x));
14474
15233
  files.push(`stories/${s.id}.md`);
14475
- for (const [rel, text2] of Object.entries(taskPromptFiles(change, s, w, x))) {
14476
- await mkdir7(join21(dir, ...rel.split("/").slice(0, -1)), { recursive: true });
14477
- await writeFileAtomic(join21(dir, ...rel.split("/")), text2);
15234
+ for (const [rel, text4] of Object.entries(taskPromptFiles(change, s, w, x))) {
15235
+ await mkdir7(join22(dir, ...rel.split("/").slice(0, -1)), { recursive: true });
15236
+ await writeFileAtomic(join22(dir, ...rel.split("/")), text4);
14478
15237
  files.push(rel);
14479
15238
  }
14480
15239
  }
14481
- await writeFileAtomic(join21(dir, "stories.md"), renderIndex2(stories, a));
14482
- const ticked = tickedTasks(await readFile19(join21(dir, "tasks.md"), "utf8").catch(() => ""));
14483
- await writeFileAtomic(join21(dir, "tasks.md"), renderTasks2(stories, requests, ticked));
15240
+ await writeFileAtomic(join22(dir, "stories.md"), renderIndex2(stories, a));
15241
+ const ticked = tickedTasks(await readFile20(join22(dir, "tasks.md"), "utf8").catch(() => ""));
15242
+ await writeFileAtomic(join22(dir, "tasks.md"), renderTasks2(stories, requests, ticked));
14484
15243
  files.push(...await writeSpecFiles(dir, spec.files));
14485
15244
  if (spec.proposal && await writeProposal(dir, spec.proposal))
14486
15245
  files.push("proposal.md");
14487
- await writeFileAtomic(join21(dir, "requests.md"), renderRequests(requests));
15246
+ await writeFileAtomic(join22(dir, "requests.md"), renderRequests(requests));
14488
15247
  await mergeStoryRequests(root, change, requests, now);
14489
- await writeFileAtomic(join21(dir, ...ANSWER_FILE.split("/")), `# The story writer's last answer (ADR 0087).
14490
- ${(0, import_yaml17.stringify)(a, { lineWidth: 0 })}`);
15248
+ await writeFileAtomic(join22(dir, ...ANSWER_FILE.split("/")), `# The story writer's last answer (ADR 0087).
15249
+ ${(0, import_yaml18.stringify)(a, { lineWidth: 0 })}`);
14491
15250
  return [...files, "stories.md", "tasks.md", "requests.md", "needs.yaml", "needs.md"];
14492
15251
  }
14493
15252
  async function selectStoriesKnowledge(root, change, stories, estate) {
@@ -14535,16 +15294,16 @@ async function storiesKnowledge(root, change, stories, o) {
14535
15294
  return r;
14536
15295
  }
14537
15296
  async function readStoriesAnswer(dir) {
14538
- const t = await readFile19(join21(dir, ...ANSWER_FILE.split("/")), "utf8").catch(() => void 0);
15297
+ const t = await readFile20(join22(dir, ...ANSWER_FILE.split("/")), "utf8").catch(() => void 0);
14539
15298
  if (t === void 0)
14540
15299
  return void 0;
14541
15300
  return parseYamlAs(t, Answer4, (d) => d, "stories").value;
14542
15301
  }
14543
15302
  async function locateInStories(dir, rel, words5) {
14544
- for (const f of (await readdir14(join21(dir, "stories")).catch(() => [])).sort()) {
15303
+ for (const f of (await readdir14(join22(dir, "stories")).catch(() => [])).sort()) {
14545
15304
  if (!f.endsWith(".md"))
14546
15305
  continue;
14547
- const lines = (await readFile19(join21(dir, "stories", f), "utf8")).split(/\r?\n/);
15306
+ const lines = (await readFile20(join22(dir, "stories", f), "utf8")).split(/\r?\n/);
14548
15307
  const n = lines.findIndex((l) => l.includes(words5));
14549
15308
  if (n >= 0)
14550
15309
  return `${rel}/stories/${f}:${n + 1}`;
@@ -14644,7 +15403,7 @@ async function storiesStage(root, changeId, o) {
14644
15403
  if (!o.force) {
14645
15404
  const own = [];
14646
15405
  for (const f of ["stories.md", "tasks.md", "requests.md"]) {
14647
- const t = await readFile19(join21(dir, f), "utf8").catch(() => void 0);
15406
+ const t = await readFile20(join22(dir, f), "utf8").catch(() => void 0);
14648
15407
  if (t !== void 0 && !t.startsWith(GENERATED_MARK))
14649
15408
  own.push(f);
14650
15409
  }
@@ -14690,7 +15449,7 @@ async function storiesStage(root, changeId, o) {
14690
15449
  id: "deltas",
14691
15450
  kind: "artifact",
14692
15451
  origin: `${rel}/target-flows/`,
14693
- text: (0, import_yaml17.stringify)(deltas, { lineWidth: 0 }),
15452
+ text: (0, import_yaml18.stringify)(deltas, { lineWidth: 0 }),
14694
15453
  classification: "internal",
14695
15454
  priority: 90
14696
15455
  },
@@ -14814,11 +15573,11 @@ Rules:
14814
15573
  classification: "internal",
14815
15574
  priority: 80
14816
15575
  };
14817
- const brief = await readFile19(join21(dir, "brief.md"), "utf8").catch(() => "");
14818
- const design = await readFile19(join21(dir, "design.md"), "utf8").catch(() => "");
15576
+ const brief = await readFile20(join22(dir, "brief.md"), "utf8").catch(() => "");
15577
+ const design = await readFile20(join22(dir, "design.md"), "utf8").catch(() => "");
14819
15578
  const scope = scopeRepos(estate, deltas.map((d) => d.repo));
14820
15579
  const selected = await estateContext(root, "stories", {
14821
- text: [brief, skeletonText, (0, import_yaml17.stringify)(deltas, { lineWidth: 0 }), design].filter((x) => x.trim()).join("\n\n"),
15580
+ text: [brief, skeletonText, (0, import_yaml18.stringify)(deltas, { lineWidth: 0 }), design].filter((x) => x.trim()).join("\n\n"),
14822
15581
  title: change.title,
14823
15582
  ids: [...new Set(deltas.map((d) => d.flow))]
14824
15583
  }, {
@@ -14895,8 +15654,8 @@ Rules:
14895
15654
  instructions: instructions3,
14896
15655
  ...context.gaps?.length ? { gaps: context.gaps } : {},
14897
15656
  sources,
14898
- parse: (text2) => {
14899
- const r = parseYamlAs(text2, Answer4, (d) => d, "stories");
15657
+ parse: (text4) => {
15658
+ const r = parseYamlAs(text4, Answer4, (d) => d, "stories");
14900
15659
  const value = r.value && noted ? notesCitedInFolder(r.value, rel) : r.value;
14901
15660
  return { ...value ? { answer: foldAnswer(value, stories) } : {}, issues: r.issues };
14902
15661
  },
@@ -14952,33 +15711,33 @@ Rules:
14952
15711
  }
14953
15712
 
14954
15713
  // packages/planner/dist/stages/small.js
14955
- var import_yaml18 = __toESM(require_dist(), 1);
14956
- import { readFile as readFile20 } from "node:fs/promises";
14957
- import { join as join22 } from "node:path";
15714
+ var import_yaml19 = __toESM(require_dist(), 1);
15715
+ import { readFile as readFile21 } from "node:fs/promises";
15716
+ import { join as join23 } from "node:path";
14958
15717
  var SMALL_AGENT = "small-change-planner";
14959
15718
  var SMALL_ANSWER_FILE = "stage/small-answer.yaml";
14960
15719
  var MARK2 = "<!-- Generated by atticus change small";
14961
- var text = () => external_exports.string().trim().min(1);
14962
- var Id = external_exports.string().regex(/^[a-z0-9][a-z0-9._-]*$/, "an id is lower case: letters, digits, . _ -");
15720
+ var text2 = () => external_exports.string().trim().min(1);
15721
+ var Id2 = external_exports.string().regex(/^[a-z0-9][a-z0-9._-]*$/, "an id is lower case: letters, digits, . _ -");
14963
15722
  var SmallAnswer = external_exports.strictObject({
14964
- summary: text(),
14965
- features: external_exports.array(external_exports.strictObject({ id: Id, why: text() })).default([]),
15723
+ summary: text2(),
15724
+ features: external_exports.array(external_exports.strictObject({ id: Id2, why: text2() })).default([]),
14966
15725
  flows: external_exports.array(external_exports.strictObject({
14967
- id: Id,
14968
- why: text(),
15726
+ id: Id2,
15727
+ why: text2(),
14969
15728
  how: external_exports.enum(["changed", "read"]).default("changed")
14970
15729
  })).default([]),
14971
- newFlows: external_exports.array(external_exports.strictObject({ name: text(), repo: Id, why: text() })).default([]),
14972
- gaps: external_exports.array(external_exports.strictObject({ what: text(), why: text(), from: external_exports.string().optional() })).default([]),
15730
+ newFlows: external_exports.array(external_exports.strictObject({ name: text2(), repo: Id2, why: text2() })).default([]),
15731
+ gaps: external_exports.array(external_exports.strictObject({ what: text2(), why: text2(), from: external_exports.string().optional() })).default([]),
14973
15732
  targetFlows: external_exports.array(FlowDelta).default([]),
14974
15733
  reads: external_exports.array(ReadFlow).default([]),
14975
15734
  stories: external_exports.array(external_exports.strictObject({
14976
15735
  /** The target flow the story is for. */
14977
- flow: Id,
14978
- title: text(),
14979
- asA: text(),
14980
- iWant: text(),
14981
- soThat: text(),
15736
+ flow: Id2,
15737
+ title: text2(),
15738
+ asA: text2(),
15739
+ iWant: text2(),
15740
+ soThat: text2(),
14982
15741
  tasks: external_exports.array(external_exports.strictObject({
14983
15742
  /** The step's item number in the flow's target steps, from 1. */
14984
15743
  step: external_exports.number().int().positive(),
@@ -15123,7 +15882,7 @@ async function smallStage(root, changeId, o) {
15123
15882
  throw new PlannerError("refused", `${size.words} Run atticus change feature ${change.id} (or /atticus-feature ${change.id}).`);
15124
15883
  const dir = changeFolder(root, change.id);
15125
15884
  const rel = `${changesRelOf(root)}/${change.id}`;
15126
- const brief = await readFile20(join22(dir, "brief.md"), "utf8").catch(() => "");
15885
+ const brief = await readFile21(join23(dir, "brief.md"), "utf8").catch(() => "");
15127
15886
  if (!isFilled(brief))
15128
15887
  throw new PlannerError("refused", `${change.id} has no brief yet: write brief.md (what changes, where, and when it is done), then run atticus change small ${change.id} again.`);
15129
15888
  const estate = await loadEstate(root, ctx.records);
@@ -15327,20 +16086,20 @@ ${storeRules(classificationLevels(ctx)).join("\n")}
15327
16086
  }
15328
16087
  )
15329
16088
  ];
15330
- const own = await readFile20(join22(dir, "proposal.md"), "utf8").catch(() => void 0);
16089
+ const own = await readFile21(join23(dir, "proposal.md"), "utf8").catch(() => void 0);
15331
16090
  if (own === void 0 || !isFilled(own) || own.startsWith(MARK2)) {
15332
- await writeFileAtomic(join22(dir, "proposal.md"), renderProposal2(change.title, a, d2.deltas));
16091
+ await writeFileAtomic(join23(dir, "proposal.md"), renderProposal2(change.title, a, d2.deltas));
15333
16092
  files.push("proposal.md");
15334
16093
  }
15335
- await writeFileAtomic(join22(dir, ...SMALL_ANSWER_FILE.split("/")), `# The fast path's last answer (ADR 0113). Atticus writes this file.
15336
- ${(0, import_yaml18.stringify)(a, { lineWidth: 0 })}`);
16094
+ await writeFileAtomic(join23(dir, ...SMALL_ANSWER_FILE.split("/")), `# The fast path's last answer (ADR 0113). Atticus writes this file.
16095
+ ${(0, import_yaml19.stringify)(a, { lineWidth: 0 })}`);
15337
16096
  return files;
15338
16097
  },
15339
16098
  current: async () => {
15340
- const t = await readFile20(join22(dir, ...SMALL_ANSWER_FILE.split("/")), "utf8").catch(() => void 0);
16099
+ const t = await readFile21(join23(dir, ...SMALL_ANSWER_FILE.split("/")), "utf8").catch(() => void 0);
15341
16100
  if (t === void 0)
15342
16101
  return void 0;
15343
- const r = SmallAnswer.safeParse((0, import_yaml18.parse)(t));
16102
+ const r = SmallAnswer.safeParse((0, import_yaml19.parse)(t));
15344
16103
  return r.success ? r.data : void 0;
15345
16104
  },
15346
16105
  questions: (a) => a.questions
@@ -15359,10 +16118,10 @@ function stageOfOutput(output) {
15359
16118
  const m = /^(feature|architecture|stories|small)-answer\.yaml$/.exec(output);
15360
16119
  return m ? m[1] : void 0;
15361
16120
  }
15362
- async function checkStageDraft(root, change, stage, text2) {
16121
+ async function checkStageDraft(root, change, stage, text4) {
15363
16122
  const o = {
15364
16123
  runtime: new HandoffRuntime({ root, validate: async () => [] }),
15365
- checkDraft: text2,
16124
+ checkDraft: text4,
15366
16125
  context: { engine: false }
15367
16126
  };
15368
16127
  const r = stage === "feature" ? await featureStage(root, change, o) : stage === "architecture" ? await architectureStage(root, change, o) : stage === "stories" ? await storiesStage(root, change, o) : await smallStage(root, change, o);
@@ -15400,13 +16159,13 @@ function draftCheckBox(root, request) {
15400
16159
  async call(name, input) {
15401
16160
  if (name !== CHECK_DRAFT_TOOL)
15402
16161
  throw new ToolError(`There is no tool ${name}.`);
15403
- const text2 = typeof input.draft === "string" ? input.draft : "";
15404
- if (!text2.trim())
16162
+ const text4 = typeof input.draft === "string" ? input.draft : "";
16163
+ if (!text4.trim())
15405
16164
  throw new ToolError("Give your whole answer under draft.");
15406
16165
  if (used >= limit)
15407
16166
  return `You have used the ${limit} checks a draft may have: hand your answer in as it is.`;
15408
16167
  used++;
15409
- return formatDraftProblems(await checkStageDraft(root, request.change, stage, text2));
16168
+ return formatDraftProblems(await checkStageDraft(root, request.change, stage, text4));
15410
16169
  }
15411
16170
  };
15412
16171
  }
@@ -15423,12 +16182,12 @@ function withTools(a, b) {
15423
16182
  }
15424
16183
 
15425
16184
  // packages/planner/dist/stages/comments.js
15426
- import { join as join23 } from "node:path";
16185
+ import { join as join24 } from "node:path";
15427
16186
  var COMMENT_COMMAND = "atticus ui: a comment on a critic finding";
15428
16187
  var isComment = (q2, stage) => q2.askedBy.step === `change-${stage}` && q2.askedBy.command === COMMENT_COMMAND;
15429
16188
  var aboutOf = (q2) => q2.evidence[0].replace(/:\d+$/, "");
15430
16189
  async function stageComments(root, change, stage) {
15431
- const dir = join23(changesDirOf(root), change);
16190
+ const dir = join24(changesDirOf(root), change);
15432
16191
  const { loop, present } = await readStageLoop(dir, change, stage);
15433
16192
  const comments = [];
15434
16193
  const others = [];
@@ -15440,7 +16199,7 @@ async function stageComments(root, change, stage) {
15440
16199
  comments.push({
15441
16200
  question: q2.id,
15442
16201
  text: q2.answer.text,
15443
- by: q2.answer.by,
16202
+ by: answerByWords(q2.answer.by),
15444
16203
  at: q2.answer.at,
15445
16204
  read: loop.read.includes(q2.id),
15446
16205
  about: aboutOf(q2)
@@ -15452,10 +16211,10 @@ async function stageComments(root, change, stage) {
15452
16211
  return { comments, others, present };
15453
16212
  }
15454
16213
  async function commentOnFinding(root, o) {
15455
- const text2 = o.text.trim();
15456
- if (!text2)
16214
+ const text4 = o.text.trim();
16215
+ if (!text4)
15457
16216
  throw new PlannerError("invalid", "Write the comment first.");
15458
- const dir = join23(changesDirOf(root), o.change);
16217
+ const dir = join24(changesDirOf(root), o.change);
15459
16218
  const { loop, present, file } = await readStageLoop(dir, o.change, o.stage);
15460
16219
  if (!present)
15461
16220
  throw new PlannerError("refused", `The ${o.stage} stage of ${o.change} hasn't run yet, so nothing would read a comment: run ${rerunOf(o.stage, o.change)} first.`);
@@ -15476,7 +16235,7 @@ async function commentOnFinding(root, o) {
15476
16235
  scope: { level: "repo", id: o.change },
15477
16236
  state: "answered",
15478
16237
  rerun: rerunOf(o.stage, o.change),
15479
- answer: { text: text2, by: o.by, at: o.at, scope: { level: "repo", id: o.change } }
16238
+ answer: { text: text4, by: o.by, at: o.at, scope: { level: "repo", id: o.change } }
15480
16239
  });
15481
16240
  await writeQuestion(root, q2);
15482
16241
  loop.questions = [.../* @__PURE__ */ new Set([...loop.questions, q2.id])].sort();
@@ -15517,7 +16276,7 @@ function callersOfComponent(e, x, entries) {
15517
16276
  out.set(id, l);
15518
16277
  return l;
15519
16278
  };
15520
- const repoOf = (c) => str5(e.byId.get(c)?.repo);
16279
+ const repoOf2 = (c) => str5(e.byId.get(c)?.repo);
15521
16280
  const componentOfEndpoint = (id) => str5(e.byId.get(id)?.component);
15522
16281
  for (const r of e.records) {
15523
16282
  if (r.kind !== "Interface" || typeof r.from !== "string" || r.from === x)
@@ -15532,7 +16291,7 @@ function callersOfComponent(e, x, entries) {
15532
16291
  if (typeof r.route === "string")
15533
16292
  l.routes.add(r.route);
15534
16293
  for (const ev2 of r.evidence)
15535
- if (ev2.side === "caller" || !ev2.side && ev2.repo === repoOf(r.from)) {
16294
+ if (ev2.side === "caller" || !ev2.side && ev2.repo === repoOf2(r.from)) {
15536
16295
  const w = at(ev2);
15537
16296
  if (w)
15538
16297
  l.at.add(w);
@@ -15553,15 +16312,15 @@ function callersOfComponent(e, x, entries) {
15553
16312
  for (const s of f.flow.steps) {
15554
16313
  if (s.kind !== "call-out" && s.kind !== "publish")
15555
16314
  continue;
15556
- const named = [...s.refs, ...s.to?.entry ? [s.to.entry] : []].filter((id) => componentOfEndpoint(id) === x && (!entries || entries.has(id)));
15557
- const hit = named.length > 0 || !entries && s.to?.component === x;
16315
+ const named2 = [...s.refs, ...s.to?.entry ? [s.to.entry] : []].filter((id) => componentOfEndpoint(id) === x && (!entries || entries.has(id)));
16316
+ const hit = named2.length > 0 || !entries && s.to?.component === x;
15558
16317
  if (!hit)
15559
16318
  continue;
15560
16319
  const l = got(f.flow.component);
15561
16320
  l.flows.add(f.flow.id);
15562
- for (const n of named)
16321
+ for (const n of named2)
15563
16322
  l.entries.add(n);
15564
- if (!named.length && s.to?.entry)
16323
+ if (!named2.length && s.to?.entry)
15565
16324
  l.entries.add(s.to.entry);
15566
16325
  }
15567
16326
  }
@@ -15576,7 +16335,7 @@ function entriesLeadingTo(e, flows) {
15576
16335
  }
15577
16336
  function changeImpact(change, deltas, e, o) {
15578
16337
  const ownerOf = (repo, who) => repo ? ownerOfQuestion({ repo }, { owners: o.owners, teams: o.teams }) : { none: `the catalog names no repo for ${who}, so no team owns it` };
15579
- const repoOf = (component) => str5(e.byId.get(component)?.repo);
16338
+ const repoOf2 = (component) => str5(e.byId.get(component)?.repo);
15580
16339
  const items = breakingChanges(deltas, e).map((c) => {
15581
16340
  const own = e.flowById.get(c.flow)?.flow.component;
15582
16341
  const callers2 = /* @__PURE__ */ new Map();
@@ -15608,7 +16367,7 @@ function changeImpact(change, deltas, e, o) {
15608
16367
  if (typeof r.route === "string")
15609
16368
  got.routes.add(r.route);
15610
16369
  for (const ev2 of r.evidence)
15611
- if (ev2.side === "caller" || !ev2.side && ev2.repo === repoOf(r.from)) {
16370
+ if (ev2.side === "caller" || !ev2.side && ev2.repo === repoOf2(r.from)) {
15612
16371
  const w = at(ev2);
15613
16372
  if (w)
15614
16373
  got.at.add(w);
@@ -15637,12 +16396,14 @@ function changeImpact(change, deltas, e, o) {
15637
16396
  const got = callers2.get(id);
15638
16397
  const guesses = got.ifaces.map((r) => r.confidence === "inferred" ? GUESSES.find((g) => (r.flags ?? []).includes(g)) : void 0);
15639
16398
  const guessed = got.ifaces.length > 0 && got.flows.size === 0 && guesses.every((g) => g !== void 0) ? guesses[0] : void 0;
15640
- const repo = repoOf(id);
16399
+ const repo = repoOf2(id);
16400
+ const byAi = !guessed && got.ifaces.length > 0 && got.flows.size === 0 && got.ifaces.every((r) => (r.flags ?? []).includes(JOINED_BY_AI));
15641
16401
  return {
15642
16402
  component: id,
15643
16403
  ...repo ? { repo } : {},
15644
16404
  verdict: guessed ? "may-break" : "breaks",
15645
16405
  ...guessed ? { guessed } : {},
16406
+ ...byAi ? { decidedByAi: true } : {},
15646
16407
  interfaces: sorted2(got.ifaces.map((r) => r.id)),
15647
16408
  flows: sorted2(got.flows),
15648
16409
  routes: sorted2(got.routes),
@@ -15703,7 +16464,7 @@ function changeImpact(change, deltas, e, o) {
15703
16464
  const ownGuess = guessedLinks(first.l);
15704
16465
  const guessed = sure ? void 0 : ownGuess ?? first.down.guessed;
15705
16466
  const restsOn = sure || ownGuess ? void 0 : first.down.restsOn;
15706
- const repo = repoOf(id);
16467
+ const repo = repoOf2(id);
15707
16468
  const flows = sorted2(xs.flatMap((x) => [...x.l.flows]));
15708
16469
  const hop = {
15709
16470
  component: id,
@@ -15775,6 +16536,7 @@ function changeImpact(change, deltas, e, o) {
15775
16536
  change,
15776
16537
  breaks: callers.filter((c) => c.verdict === "breaks").length,
15777
16538
  mayBreak: callers.filter((c) => c.verdict === "may-break").length,
16539
+ byAi: callers.filter((c) => c.decidedByAi).length,
15778
16540
  routes: new Set(items.flatMap((i) => i.routes.map((r) => r.id))).size,
15779
16541
  upstream: {
15780
16542
  breaks: hops.filter((h) => h.verdict === "breaks").length,
@@ -15786,6 +16548,394 @@ function changeImpact(change, deltas, e, o) {
15786
16548
  };
15787
16549
  }
15788
16550
 
16551
+ // packages/planner/dist/stages/sketch.js
16552
+ var import_yaml20 = __toESM(require_dist(), 1);
16553
+ import { createHash as createHash6, randomBytes } from "node:crypto";
16554
+ import { readdir as readdir15, readFile as readFile22, rm as rm7 } from "node:fs/promises";
16555
+ import { join as join25 } from "node:path";
16556
+ var import_yaml21 = __toESM(require_dist(), 1);
16557
+ var Id3 = external_exports.string().regex(/^[a-z0-9][a-z0-9._-]*$/, "an id is lower case: letters, digits, . _ -");
16558
+ var text3 = () => external_exports.string().trim().min(1).max(500);
16559
+ var From = external_exports.object({
16560
+ component: Id3,
16561
+ /** The flow the step goes on; left out when the component has one flow, or a new one is named. */
16562
+ flow: Id3.optional(),
16563
+ /** A new flow (the component has none fitting, or is new): what a person calls it, and what starts it. */
16564
+ newFlow: external_exports.object({
16565
+ id: Id3,
16566
+ name: text3(),
16567
+ trigger: external_exports.object({
16568
+ kind: external_exports.enum(["endpoint", "action", "page", "listener", "job"]),
16569
+ what: text3(),
16570
+ entry: external_exports.string().trim().min(1).optional()
16571
+ }),
16572
+ /** A component the catalog doesn't have yet: where it runs (ADR 0132), drawn as a new box. */
16573
+ runsOn: external_exports.object({
16574
+ system: Id3.optional(),
16575
+ platform: external_exports.string().trim().min(1).optional(),
16576
+ cloud: external_exports.string().trim().min(1).optional(),
16577
+ framework: external_exports.string().trim().min(1).optional(),
16578
+ /** What the new box is (ADR 0180): one of the organisation's component types. */
16579
+ type: Id3.optional()
16580
+ }).optional()
16581
+ }).optional()
16582
+ });
16583
+ var Stroke = external_exports.discriminatedUnion("kind", [
16584
+ /** A line from a component to another's API: a call-out step. */
16585
+ external_exports.object({
16586
+ kind: external_exports.literal("call"),
16587
+ from: From,
16588
+ to: external_exports.object({
16589
+ component: Id3,
16590
+ /** An Endpoint id, "METHOD /path" or a topic; `new` when the API doesn't exist yet. */
16591
+ entry: text3(),
16592
+ new: external_exports.boolean().optional()
16593
+ }),
16594
+ what: text3().optional(),
16595
+ /** The step it goes after (0: first); left out, the end of the flow. */
16596
+ after: external_exports.number().int().min(0).optional()
16597
+ }),
16598
+ /** A topic a component publishes, and (optionally) a component that listens. */
16599
+ external_exports.object({
16600
+ kind: external_exports.literal("topic"),
16601
+ from: From,
16602
+ topic: text3(),
16603
+ listener: From.optional(),
16604
+ what: text3().optional()
16605
+ }),
16606
+ /** A table a step reads or writes. */
16607
+ external_exports.object({
16608
+ kind: external_exports.literal("table"),
16609
+ from: From,
16610
+ op: external_exports.enum(["read", "write", "lookup"]),
16611
+ table: Id3,
16612
+ columns: external_exports.array(external_exports.string().trim().min(1)).default([]),
16613
+ new: external_exports.boolean().optional(),
16614
+ what: text3().optional()
16615
+ }),
16616
+ /** A step of today's flow taken out (a line deleted). */
16617
+ external_exports.object({ kind: external_exports.literal("remove"), flow: Id3, step: external_exports.number().int().positive(), why: text3() }),
16618
+ /** A step of today's flow sent elsewhere (a line's end dragged): removed, and added to the new target. */
16619
+ external_exports.object({
16620
+ kind: external_exports.literal("retarget"),
16621
+ flow: Id3,
16622
+ step: external_exports.number().int().positive(),
16623
+ to: external_exports.object({ component: Id3, entry: text3(), new: external_exports.boolean().optional() }),
16624
+ why: text3()
16625
+ }),
16626
+ /** A stroke taken back: a drawn step, by flow and the step's index in the delta (its whole stroke goes). */
16627
+ external_exports.object({ kind: external_exports.literal("undo"), flow: Id3, index: external_exports.number().int().min(0) })
16628
+ ]);
16629
+ var slug5 = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60);
16630
+ var DRAWN = "drawn on the canvas";
16631
+ var MARK_RE = /drawn on the canvas by (.+?), stroke ([a-z0-9]+)\)?$/i;
16632
+ var deltaWhy = (by) => `Drawn on the canvas by ${by}.`;
16633
+ var stepWhy = (by, id, reason) => `${reason ? `${reason} (` : ""}${DRAWN} by ${by}, stroke ${id}${reason ? ")" : ""}`;
16634
+ var isDrawnDelta = (d) => d.why.toLowerCase().startsWith(DRAWN);
16635
+ var strokeIdOf = (s) => MARK_RE.exec(s.why ?? "")?.[2];
16636
+ var isDrawnStep = (s) => strokeIdOf(s) !== void 0;
16637
+ var drawnBy = (s) => MARK_RE.exec(s.why ?? "")?.[1];
16638
+ var newStrokeId = () => randomBytes(4).toString("hex");
16639
+ function flowsOf(deltas, component, estate) {
16640
+ const out = /* @__PURE__ */ new Map();
16641
+ for (const f of estate.flows)
16642
+ if (f.flow.component === component)
16643
+ out.set(f.flow.id, f.flow.name);
16644
+ for (const d of deltas)
16645
+ if (!out.has(d.flow) && d.change !== "remove" && (d.runsOn?.component === component || d.change === "new" && d.flow.startsWith(`${component}.`)))
16646
+ out.set(d.flow, d.name ?? d.flow);
16647
+ return [...out].map(([id, name]) => ({ id, name }));
16648
+ }
16649
+ function deltaFor(deltas, from, estate, by) {
16650
+ const own = flowsOf(deltas, from.component, estate);
16651
+ if (from.newFlow) {
16652
+ const id = from.newFlow.id;
16653
+ const had2 = deltas.find((d3) => d3.flow === id);
16654
+ if (had2?.change === "remove")
16655
+ return {
16656
+ problem: `${id} is removed in this change: take the removal back, or draw on another flow.`
16657
+ };
16658
+ if (had2)
16659
+ return { delta: had2, deltas };
16660
+ if (estate.flows.some((f) => f.flow.id === id))
16661
+ return { problem: `${id} is a flow of today's map already: draw on it, not a new one.` };
16662
+ const d2 = {
16663
+ schemaVersion: FLOW_DELTA_SCHEMA_VERSION,
16664
+ flow: id,
16665
+ repo: repoOf(estate, from.component) ?? from.component,
16666
+ change: "new",
16667
+ name: from.newFlow.name,
16668
+ trigger: from.newFlow.trigger,
16669
+ why: deltaWhy(by),
16670
+ ...from.newFlow.runsOn || !estate.ids.has(from.component) ? { runsOn: { component: from.component, ...from.newFlow.runsOn ?? {} } } : {},
16671
+ steps: []
16672
+ };
16673
+ return { delta: d2, deltas: [...deltas, d2] };
16674
+ }
16675
+ const flowId = from.flow ?? (own.length === 1 ? own[0].id : void 0);
16676
+ if (!flowId) {
16677
+ if (!own.length)
16678
+ return {
16679
+ problem: `${from.component} has no flow on the map yet: name a new one for the stroke to start.`
16680
+ };
16681
+ return {
16682
+ problem: `${from.component} has ${own.length} flows: say which the stroke is on.`,
16683
+ pick: { flows: own }
16684
+ };
16685
+ }
16686
+ if (from.flow && !own.some((f) => f.id === from.flow))
16687
+ return { problem: `${from.flow} isn't a flow of ${from.component}.` };
16688
+ const had = deltas.find((d2) => d2.flow === flowId);
16689
+ if (had?.change === "remove")
16690
+ return {
16691
+ problem: `${flowId} is removed in this change: take the removal back, or draw on another flow.`
16692
+ };
16693
+ if (had)
16694
+ return { delta: had, deltas };
16695
+ const today = estate.flows.find((f) => f.flow.id === flowId);
16696
+ if (!today)
16697
+ return { problem: `No flow of today's map is ${flowId}.` };
16698
+ const d = {
16699
+ schemaVersion: FLOW_DELTA_SCHEMA_VERSION,
16700
+ flow: flowId,
16701
+ repo: today.repo,
16702
+ change: "modify",
16703
+ why: deltaWhy(by),
16704
+ steps: []
16705
+ };
16706
+ return { delta: d, deltas: [...deltas, d] };
16707
+ }
16708
+ var repoOf = (estate, component) => {
16709
+ const fromFlows = estate.flows.find((f) => f.flow.component === component)?.repo;
16710
+ if (fromFlows)
16711
+ return fromFlows;
16712
+ const r = estate.byId?.get(component);
16713
+ return r?.kind === "Component" && typeof r.repo === "string" && r.repo ? r.repo : void 0;
16714
+ };
16715
+ function noTodayFlow(estate, flow) {
16716
+ const component = flow.split(".")[0] ?? flow;
16717
+ const repo = repoOf(estate, component);
16718
+ if (repo && !estate.flows.some((f) => f.repo === repo))
16719
+ return `No flow of today's map is ${flow}: ${repo}'s spec has no flows yet, so there is no step to take out. Its spec step writes them: atticus spec draft ${repo}`;
16720
+ return `No flow of today's map is ${flow}.`;
16721
+ }
16722
+ function applyStroke(deltas, stroke, estate, by, strokeId = newStrokeId()) {
16723
+ const before = deltas.map((d) => ({ ...d, steps: [...d.steps] }));
16724
+ const replace = (all2, d, step) => all2.map((x) => x.flow === d.flow ? { ...x, steps: [...x.steps, step] } : x);
16725
+ const stepWhat = (what, fallback) => what ?? fallback;
16726
+ const why = (reason) => stepWhy(by, strokeId, reason);
16727
+ switch (stroke.kind) {
16728
+ case "call": {
16729
+ const r = deltaFor(before, stroke.from, estate, by);
16730
+ if ("problem" in r)
16731
+ return { deltas: before, changed: [], ...r };
16732
+ const step = {
16733
+ op: "add",
16734
+ ...stroke.after !== void 0 ? { after: stroke.after } : {},
16735
+ kind: "call-out",
16736
+ what: stepWhat(stroke.what, `Calls ${stroke.to.component}'s ${stroke.to.entry}.`),
16737
+ to: { component: stroke.to.component, entry: stroke.to.entry, new: !!stroke.to.new },
16738
+ why: why()
16739
+ };
16740
+ return { deltas: replace(r.deltas, r.delta, step), changed: [r.delta.flow] };
16741
+ }
16742
+ case "topic": {
16743
+ const r = deltaFor(before, stroke.from, estate, by);
16744
+ if ("problem" in r)
16745
+ return { deltas: before, changed: [], ...r };
16746
+ const pub = {
16747
+ op: "add",
16748
+ kind: "publish",
16749
+ what: stepWhat(stroke.what, `Publishes ${stroke.topic}.`),
16750
+ ...stroke.listener && stroke.listener.component !== stroke.from.component ? { to: { component: stroke.listener.component, entry: stroke.topic, new: false } } : {},
16751
+ why: why()
16752
+ };
16753
+ let all2 = replace(r.deltas, r.delta, pub);
16754
+ const changed = [r.delta.flow];
16755
+ if (stroke.listener) {
16756
+ const l = deltaFor(all2, stroke.listener, estate, by);
16757
+ if ("problem" in l)
16758
+ return { deltas: before, changed: [], ...l };
16759
+ const listen = {
16760
+ op: "add",
16761
+ kind: "entry",
16762
+ what: `Listens on ${stroke.topic}.`,
16763
+ why: why()
16764
+ };
16765
+ all2 = replace(l.deltas, l.delta, listen);
16766
+ changed.push(l.delta.flow);
16767
+ }
16768
+ return { deltas: all2, changed };
16769
+ }
16770
+ case "table": {
16771
+ const r = deltaFor(before, stroke.from, estate, by);
16772
+ if ("problem" in r)
16773
+ return { deltas: before, changed: [], ...r };
16774
+ const step = {
16775
+ op: "add",
16776
+ kind: stroke.op,
16777
+ what: stepWhat(stroke.what, `${stroke.op === "write" ? "Writes" : "Reads"} ${stroke.table}${stroke.columns.length ? ` (${stroke.columns.join(", ")})` : ""}.`),
16778
+ store: { table: stroke.table, columns: stroke.columns, new: !!stroke.new },
16779
+ why: why()
16780
+ };
16781
+ return { deltas: replace(r.deltas, r.delta, step), changed: [r.delta.flow] };
16782
+ }
16783
+ case "remove":
16784
+ case "retarget": {
16785
+ const today = estate.flows.find((f) => f.flow.id === stroke.flow);
16786
+ if (!today)
16787
+ return {
16788
+ deltas: before,
16789
+ changed: [],
16790
+ problem: noTodayFlow(estate, stroke.flow)
16791
+ };
16792
+ const was = today.flow.steps[stroke.step - 1];
16793
+ if (!was)
16794
+ return {
16795
+ deltas: before,
16796
+ changed: [],
16797
+ problem: `${stroke.flow} has no step ${stroke.step}.`
16798
+ };
16799
+ let all2 = before;
16800
+ let d = all2.find((x) => x.flow === stroke.flow);
16801
+ if (d?.change === "remove")
16802
+ return {
16803
+ deltas: before,
16804
+ changed: [],
16805
+ problem: `${stroke.flow} is removed in this change: take the removal back, or draw on another flow.`
16806
+ };
16807
+ if (!d) {
16808
+ d = {
16809
+ schemaVersion: FLOW_DELTA_SCHEMA_VERSION,
16810
+ flow: stroke.flow,
16811
+ repo: today.repo,
16812
+ change: "modify",
16813
+ why: deltaWhy(by),
16814
+ steps: []
16815
+ };
16816
+ all2 = [...all2, d];
16817
+ }
16818
+ if (d.steps.some((s) => s.op !== "add" && s.step === stroke.step))
16819
+ return {
16820
+ deltas: before,
16821
+ changed: [],
16822
+ problem: `Step ${stroke.step} of ${stroke.flow} is changed already in this change.`
16823
+ };
16824
+ all2 = replace(all2, d, { op: "remove", step: stroke.step, why: why(stroke.why) });
16825
+ if (stroke.kind === "retarget")
16826
+ all2 = replace(all2, d, {
16827
+ op: "add",
16828
+ after: stroke.step,
16829
+ kind: was.kind,
16830
+ what: was.what,
16831
+ to: { component: stroke.to.component, entry: stroke.to.entry, new: !!stroke.to.new },
16832
+ why: why(stroke.why)
16833
+ });
16834
+ return { deltas: all2, changed: [stroke.flow] };
16835
+ }
16836
+ case "undo": {
16837
+ const d = before.find((x) => x.flow === stroke.flow);
16838
+ const s = d?.steps[stroke.index];
16839
+ const id = s ? strokeIdOf(s) : void 0;
16840
+ if (!d || !s || !id)
16841
+ return {
16842
+ deltas: before,
16843
+ changed: [],
16844
+ problem: s ? `Step ${stroke.index} of ${stroke.flow} wasn't drawn on the canvas: the stage wrote it, so edit the file or run the stage again.` : `${stroke.flow} has no step ${stroke.index} to take back.`
16845
+ };
16846
+ const all2 = before.map((x) => ({ ...x, steps: x.steps.filter((y) => strokeIdOf(y) !== id) })).filter((x) => !(x.steps.length === 0 && isDrawnDelta(x)));
16847
+ const changed = before.filter((x) => x.steps.some((y) => strokeIdOf(y) === id)).map((x) => x.flow);
16848
+ return { deltas: all2, changed };
16849
+ }
16850
+ }
16851
+ }
16852
+ function drawnStrokes(deltas) {
16853
+ const out = [];
16854
+ const seen = /* @__PURE__ */ new Set();
16855
+ for (const d of deltas)
16856
+ d.steps.forEach((s, index) => {
16857
+ const id = strokeIdOf(s);
16858
+ if (!id || seen.has(id))
16859
+ return;
16860
+ seen.add(id);
16861
+ const pair = d.steps.find((y, j) => j !== index && strokeIdOf(y) === id);
16862
+ const what = s.op === "remove" ? pair?.to ? `step ${s.step} sent to ${pair.to.component} ${pair.to.entry}` : `step ${s.step} removed` : `${s.kind ?? s.op}${s.to ? ` \u2192 ${s.to.component} ${s.to.entry}` : ""}${s.store ? ` ${s.store.table}` : ""}`;
16863
+ const by = drawnBy(s);
16864
+ out.push({ flow: d.flow, index, what, ...by ? { by } : {} });
16865
+ });
16866
+ return out;
16867
+ }
16868
+ function sketchOf(deltas) {
16869
+ return deltas.map((d) => ({ ...d, steps: d.steps.filter(isDrawnStep) })).filter((d) => d.steps.length || isDrawnDelta(d));
16870
+ }
16871
+ function renderSketchDelta(d, estate, by) {
16872
+ const today = d.change === "new" ? void 0 : estate.flowById.get(d.flow)?.flow;
16873
+ const pinned = {
16874
+ ...d,
16875
+ schemaVersion: FLOW_DELTA_SCHEMA_VERSION,
16876
+ ...today ? { base: { steps: flowBase(today) } } : {}
16877
+ };
16878
+ return `# The target of flow ${d.flow} (ADR 0087): the steps this change adds, changes or removes. Drafted by ${by}; base pins the version of today's flow it was written against (ADR 0111).
16879
+ ${(0, import_yaml20.stringify)(FlowDelta.parse(pinned), { lineWidth: 0 })}`;
16880
+ }
16881
+ var MARK3 = "<!-- Generated by atticus change architecture";
16882
+ async function planSketchWrite(dir, deltas, changed, estate, by) {
16883
+ const out = [];
16884
+ for (const flow of [...new Set(changed)]) {
16885
+ const d = deltas.find((x) => x.flow === flow);
16886
+ out.push({
16887
+ file: `${TARGET_DIR2}/${flow}.yaml`,
16888
+ ...d ? { now: renderSketchDelta(d, estate, by) } : {}
16889
+ });
16890
+ }
16891
+ const read = async (f) => readFile22(join25(dir, f), "utf8").catch(() => void 0);
16892
+ const design = await read("design.md");
16893
+ if (design === void 0 || design.startsWith(MARK3)) {
16894
+ const t = await read(READS_FILE);
16895
+ const reads = t === void 0 ? void 0 : external_exports.array(ReadFlow).safeParse((0, import_yaml21.parse)(t)?.reads);
16896
+ const dt = await read(DEPLOY_FILE);
16897
+ const deploy = dt === void 0 ? void 0 : external_exports.array(DeployStep).safeParse((0, import_yaml21.parse)(dt)?.deploy);
16898
+ const all2 = deltas.filter((d) => d.steps.length || d.change !== "modify");
16899
+ const now = renderArchitecture(all2, estate, reads?.success ? reads.data : [], deploy?.success && deploy.data.length ? deploy.data : void 0);
16900
+ if (now !== design)
16901
+ out.push({ file: "design.md", now });
16902
+ }
16903
+ const data = await read("data.md");
16904
+ if (data === void 0 || data.startsWith(MARK3)) {
16905
+ const now = renderData([...deltas], estate);
16906
+ if (now && now !== data)
16907
+ out.push({ file: "data.md", now });
16908
+ else if (!now && data?.startsWith(MARK3))
16909
+ out.push({ file: "data.md" });
16910
+ }
16911
+ return out;
16912
+ }
16913
+ async function writeSketch(dir, files) {
16914
+ for (const f of files) {
16915
+ const path = join25(dir, ...f.file.split("/"));
16916
+ if (f.now === void 0)
16917
+ await rm7(path, { force: true });
16918
+ else
16919
+ await writeFileAtomic(path, f.now);
16920
+ }
16921
+ }
16922
+ async function sketchFingerprint(dir) {
16923
+ const h = createHash6("sha256");
16924
+ const take = async (rel) => {
16925
+ h.update(rel);
16926
+ h.update("\0");
16927
+ h.update(await readFile22(join25(dir, ...rel.split("/")), "utf8").catch(() => ""));
16928
+ h.update("\0");
16929
+ };
16930
+ for (const f of (await readdir15(join25(dir, TARGET_DIR2)).catch(() => [])).sort())
16931
+ if (f.endsWith(".yaml"))
16932
+ await take(`${TARGET_DIR2}/${f}`);
16933
+ for (const f of [READS_FILE, DEPLOY_FILE, "design.md", "data.md"])
16934
+ await take(f);
16935
+ return h.digest("hex").slice(0, 16);
16936
+ }
16937
+ var sketchSlug = slug5;
16938
+
15789
16939
  // packages/planner/dist/export-work-items.js
15790
16940
  var sentence6 = (s) => /[.!?]$/.test(s.trim()) ? s.trim() : `${s.trim()}.`;
15791
16941
  var VERB = { add: "Add", change: "Change", remove: "Remove" };
@@ -15824,9 +16974,9 @@ function workItemsOf(d) {
15824
16974
  }
15825
16975
 
15826
16976
  // packages/planner/dist/conform.js
15827
- var import_yaml19 = __toESM(require_dist(), 1);
15828
- import { readdir as readdir15, readFile as readFile21 } from "node:fs/promises";
15829
- import { join as join24, posix as posix2 } from "node:path";
16977
+ var import_yaml22 = __toESM(require_dist(), 1);
16978
+ import { readdir as readdir16, readFile as readFile23 } from "node:fs/promises";
16979
+ import { join as join26, posix as posix2 } from "node:path";
15830
16980
  var str6 = (v) => typeof v === "string" ? v : void 0;
15831
16981
  function httpEntry(entry) {
15832
16982
  const m = /^([A-Za-z]+)\s+(\S+)$/.exec(entry.trim());
@@ -15878,6 +17028,17 @@ function conform(input) {
15878
17028
  out.push(...records.filter((r) => r.kind === "Unresolved" && r.unresolvedKind === "event-publish" && r.owner === from && r.raw === to.entry.trim()));
15879
17029
  return out;
15880
17030
  };
17031
+ const unsettledCall = (records, from, entry) => {
17032
+ const http = httpEntry(entry);
17033
+ if (!http)
17034
+ return void 0;
17035
+ return records.find((r) => {
17036
+ if (r.kind !== "Unresolved" || r.unresolvedKind !== "call" || r.owner !== from)
17037
+ return false;
17038
+ const m = /^(\S+)\s+\S+\s+(\S+)$/.exec(str6(r.raw) ?? "");
17039
+ return !!m && m[1].toUpperCase() === http.method && normalizePath(m[2]) === http.path;
17040
+ });
17041
+ };
15881
17042
  const endpointsFor2 = (records, component, entry) => {
15882
17043
  const http = httpEntry(entry);
15883
17044
  return records.filter((r) => {
@@ -15894,29 +17055,29 @@ function conform(input) {
15894
17055
  };
15895
17056
  const columnOf = (records, table, name) => records.find((r) => r.kind === "Column" && r.table === table && r.name === name);
15896
17057
  const tableOf = (records, table) => records.find((r) => r.kind === "Table" && r.id === table);
15897
- const named = /* @__PURE__ */ new Set();
17058
+ const named2 = /* @__PURE__ */ new Set();
15898
17059
  for (const d of input.deltas) {
15899
17060
  const from = componentOf(d);
15900
17061
  if (d.delta.change === "new" && d.delta.trigger) {
15901
17062
  const entry = d.delta.trigger.entry ?? d.delta.trigger.what;
15902
17063
  for (const r of endpointsFor2(after2, from, entry))
15903
- named.add(key2(r));
17064
+ named2.add(key2(r));
15904
17065
  }
15905
17066
  for (const s of d.delta.steps) {
15906
17067
  if (s.op === "remove")
15907
17068
  continue;
15908
17069
  if (s.to) {
15909
17070
  for (const r of callsTo(after2, from, s.to))
15910
- named.add(key2(r));
17071
+ named2.add(key2(r));
15911
17072
  for (const r of endpointsFor2(after2, s.to.component, s.to.entry))
15912
- named.add(key2(r));
17073
+ named2.add(key2(r));
15913
17074
  }
15914
17075
  if (s.store) {
15915
- named.add(`Table/${s.store.table}`);
17076
+ named2.add(`Table/${s.store.table}`);
15916
17077
  for (const c of s.store.columns) {
15917
17078
  const col = columnOf(after2, s.store.table, c);
15918
17079
  if (col)
15919
- named.add(key2(col));
17080
+ named2.add(key2(col));
15920
17081
  }
15921
17082
  }
15922
17083
  }
@@ -15928,12 +17089,32 @@ function conform(input) {
15928
17089
  const designed = (d, line) => [
15929
17090
  { source: "artifact", path: d.file, line }
15930
17091
  ];
17092
+ const basis = input.basis;
17093
+ const decisive = !basis || basis.from === "facts" && basis.unread.length === 0;
17094
+ const readAgain = `/atticus-map ${repo}, then atticus verify ${repo} --write`;
17095
+ const unconfirmed = (claim) => {
17096
+ if (basis?.from === "facts") {
17097
+ const shown = basis.unread.slice(0, 3).join(", ");
17098
+ const more = basis.unread.length > 3 ? ` and ${basis.unread.length - 3} more` : "";
17099
+ return `No fact covers what changed in ${shown}${more} since ${repo} was read, so Atticus couldn't confirm ${claim} from the code; check it by hand, or read those files again (${readAgain}) and check the code again.`;
17100
+ }
17101
+ return `${repo} hasn't been read into facts at this commit, and the built-in readers alone are only a cross-check, so Atticus couldn't confirm ${claim} from the code; check it by hand, or read the repo (${readAgain}) and check the code again.`;
17102
+ };
15931
17103
  for (const d of input.deltas) {
15932
17104
  let stillThere2 = function(d2, item, to, line, verb) {
15933
17105
  const kept = input.deltas.some((o) => o.delta.steps.some((x) => x.op !== "remove" && componentOf(o) === from && x.to?.component === to.component && x.to.entry === to.entry));
15934
17106
  if (kept)
15935
17107
  return;
15936
17108
  const left = callsTo(after2, from, to)[0];
17109
+ if (!left && !decisive) {
17110
+ unchecked.push({
17111
+ flow: d2.delta.flow,
17112
+ item,
17113
+ kind: "call",
17114
+ why: unconfirmed(`that ${from} no longer calls ${to.component} ${to.entry}`)
17115
+ });
17116
+ return;
17117
+ }
15937
17118
  push(left ? {
15938
17119
  status: "still-there",
15939
17120
  check: "call",
@@ -15986,7 +17167,18 @@ function conform(input) {
15986
17167
  why: `The scans read no column of ${st.table} (its columns are kept where the reader doesn't look), so ${st.columns.join(", ")} can't be checked here.`
15987
17168
  });
15988
17169
  } else if (st.new || table) {
15989
- if (st.new)
17170
+ const absent = [
17171
+ ...st.new && !table ? [`the table ${st.table}`] : [],
17172
+ ...st.columns.filter((c) => !columnOf(after2, st.table, c)).map((c) => `${st.table}.${c}`)
17173
+ ];
17174
+ if (absent.length && !decisive)
17175
+ unchecked.push({
17176
+ flow: d2.delta.flow,
17177
+ item,
17178
+ kind: s.kind ?? "step",
17179
+ why: unconfirmed(`that the code has ${absent.join(", ")}`)
17180
+ });
17181
+ if (st.new && (table || decisive))
15990
17182
  push(table ? {
15991
17183
  status: "found",
15992
17184
  check: "table",
@@ -16003,6 +17195,8 @@ function conform(input) {
16003
17195
  });
16004
17196
  for (const c of st.columns) {
16005
17197
  const col = columnOf(after2, st.table, c);
17198
+ if (!col && !decisive)
17199
+ continue;
16006
17200
  push(col ? {
16007
17201
  status: "found",
16008
17202
  check: "column",
@@ -16043,18 +17237,25 @@ function conform(input) {
16043
17237
  const entry = t?.entry ?? t?.what ?? "";
16044
17238
  if (t && (t.kind === "endpoint" || t.kind === "listener") && (httpEntry(entry) || t.entry)) {
16045
17239
  const found = endpointsFor2(after2, from, entry)[0];
16046
- push(found ? {
16047
- status: "found",
16048
- check: "trigger",
16049
- what: `${from} serves ${entry}, the trigger of ${flow}.`,
16050
- record: found.id,
16051
- at: citesIn(found, repo)
16052
- } : {
16053
- status: "missing",
16054
- check: "trigger",
16055
- what: `${from} serves no ${entry}, the trigger of new flow ${flow} (${d.file}).`,
16056
- at: designed(d, d.triggerLine)
16057
- });
17240
+ if (!found && !decisive)
17241
+ unchecked.push({
17242
+ flow,
17243
+ kind: t.kind,
17244
+ why: unconfirmed(`that ${from} serves ${entry}, the trigger of ${flow}`)
17245
+ });
17246
+ else
17247
+ push(found ? {
17248
+ status: "found",
17249
+ check: "trigger",
17250
+ what: `${from} serves ${entry}, the trigger of ${flow}.`,
17251
+ record: found.id,
17252
+ at: citesIn(found, repo)
17253
+ } : {
17254
+ status: "missing",
17255
+ check: "trigger",
17256
+ what: `${from} serves no ${entry}, the trigger of new flow ${flow} (${d.file}).`,
17257
+ at: designed(d, d.triggerLine)
17258
+ });
16058
17259
  } else
16059
17260
  unchecked.push({
16060
17261
  flow,
@@ -16097,20 +17298,36 @@ function conform(input) {
16097
17298
  const found = callsTo(after2, from, s.to)[0];
16098
17299
  const publish = kind === "publish";
16099
17300
  const verb = publish ? "publishes to" : "calls";
16100
- push(found ? {
16101
- status: "found",
16102
- check: publish ? "publish" : "call",
16103
- item,
16104
- what: `${from} ${verb} ${s.to.component} ${s.to.entry}.`,
16105
- record: found.id,
16106
- at: citesIn(found, repo)
16107
- } : {
16108
- status: "missing",
16109
- check: publish ? "publish" : "call",
16110
- item,
16111
- what: `${from} makes no ${publish ? "publish to" : "call to"} ${s.to.component} ${s.to.entry} (${where2(item)}).`,
16112
- at: designed(d, line)
16113
- });
17301
+ const unsettled = found ? void 0 : unsettledCall(after2, from, s.to.entry);
17302
+ if (!found && !decisive)
17303
+ unchecked.push({
17304
+ flow,
17305
+ item,
17306
+ kind,
17307
+ why: unconfirmed(`that ${from} ${verb} ${s.to.component} ${s.to.entry}`)
17308
+ });
17309
+ else if (unsettled)
17310
+ unchecked.push({
17311
+ flow,
17312
+ item,
17313
+ kind,
17314
+ why: `${from} calls ${unsettled.raw}, but where it goes isn't settled yet, so Atticus couldn't confirm it reaches ${s.to.component}; answer its map question on Questions, then check the code again.`
17315
+ });
17316
+ else
17317
+ push(found ? {
17318
+ status: "found",
17319
+ check: publish ? "publish" : "call",
17320
+ item,
17321
+ what: `${from} ${verb} ${s.to.component} ${s.to.entry}.`,
17322
+ record: found.id,
17323
+ at: citesIn(found, repo)
17324
+ } : {
17325
+ status: "missing",
17326
+ check: publish ? "publish" : "call",
17327
+ item,
17328
+ what: `${from} makes no ${publish ? "publish to" : "call to"} ${s.to.component} ${s.to.entry} (${where2(item)}).`,
17329
+ at: designed(d, line)
17330
+ });
16114
17331
  if (s.op === "change" && old?.to && (old.to.component !== s.to.component || old.to.entry !== s.to.entry))
16115
17332
  stillThere2(d, item, old.to, line, "sends elsewhere");
16116
17333
  }
@@ -16128,7 +17345,7 @@ function conform(input) {
16128
17345
  });
16129
17346
  });
16130
17347
  }
16131
- const added = (r) => !beforeKeys.has(key2(r)) && !named.has(key2(r));
17348
+ const added = (r) => !beforeKeys.has(key2(r)) && !named2.has(key2(r));
16132
17349
  const scope = [];
16133
17350
  const unexpectedTables = /* @__PURE__ */ new Set();
16134
17351
  for (const r of mine(after2)) {
@@ -16193,13 +17410,13 @@ function componentOf(d) {
16193
17410
  async function readConformDeltas(changeDir) {
16194
17411
  const deltas = [];
16195
17412
  const problems = [];
16196
- const names = (await readdir15(join24(changeDir, TARGET_DIR2)).catch(() => [])).filter((f) => f.endsWith(".yaml")).sort();
17413
+ const names = (await readdir16(join26(changeDir, TARGET_DIR2)).catch(() => [])).filter((f) => f.endsWith(".yaml")).sort();
16197
17414
  for (const f of names) {
16198
17415
  const file = `${TARGET_DIR2}/${f}`;
16199
17416
  try {
16200
- const text2 = await readFile21(join24(changeDir, TARGET_DIR2, f), "utf8");
16201
- const lines = new import_yaml19.LineCounter();
16202
- const doc = (0, import_yaml19.parseDocument)(text2, { lineCounter: lines });
17417
+ const text4 = await readFile23(join26(changeDir, TARGET_DIR2, f), "utf8");
17418
+ const lines = new import_yaml22.LineCounter();
17419
+ const doc = (0, import_yaml22.parseDocument)(text4, { lineCounter: lines });
16203
17420
  const r = FlowDelta.safeParse(doc.toJS());
16204
17421
  if (!r.success) {
16205
17422
  problems.push(`${file}: ${r.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")}`);
@@ -16210,10 +17427,10 @@ async function readConformDeltas(changeDir) {
16210
17427
  return range ? lines.linePos(range[0]).line : 1;
16211
17428
  };
16212
17429
  const steps = doc.get("steps", true);
16213
- const stepLines = (0, import_yaml19.isSeq)(steps) ? steps.items.map(lineOf2) : [];
17430
+ const stepLines = (0, import_yaml22.isSeq)(steps) ? steps.items.map(lineOf2) : [];
16214
17431
  const root = doc.contents;
16215
- const trigger = (0, import_yaml19.isMap)(root) ? root.items.find((p) => String(p.key) === "trigger") : void 0;
16216
- const flowKey = (0, import_yaml19.isMap)(root) ? root.items.find((p) => String(p.key) === "flow") : void 0;
17432
+ const trigger = (0, import_yaml22.isMap)(root) ? root.items.find((p) => String(p.key) === "trigger") : void 0;
17433
+ const flowKey = (0, import_yaml22.isMap)(root) ? root.items.find((p) => String(p.key) === "flow") : void 0;
16217
17434
  deltas.push({
16218
17435
  file,
16219
17436
  delta: r.data,
@@ -16228,13 +17445,13 @@ async function readConformDeltas(changeDir) {
16228
17445
  }
16229
17446
  async function readTaskSteps(changeDir) {
16230
17447
  const out = /* @__PURE__ */ new Map();
16231
- const dir = join24(changeDir, "stories");
16232
- for (const f of (await readdir15(dir).catch(() => [])).sort()) {
17448
+ const dir = join26(changeDir, "stories");
17449
+ for (const f of (await readdir16(dir).catch(() => [])).sort()) {
16233
17450
  if (!f.endsWith(".md"))
16234
17451
  continue;
16235
- const text2 = await readFile21(join24(dir, f), "utf8").catch(() => "");
17452
+ const text4 = await readFile23(join26(dir, f), "utf8").catch(() => "");
16236
17453
  let task;
16237
- for (const line of text2.split(/\r?\n/)) {
17454
+ for (const line of text4.split(/\r?\n/)) {
16238
17455
  const t = /^###\s+Task\s+(\S+?):/.exec(line);
16239
17456
  if (t) {
16240
17457
  task = t[1].toLowerCase();
@@ -16276,6 +17493,8 @@ export {
16276
17493
  draftFor,
16277
17494
  renderSnow,
16278
17495
  readDrafts,
17496
+ SnowFillError,
17497
+ fillSnowFields,
16279
17498
  planStories,
16280
17499
  snowDraft,
16281
17500
  StoryEval,
@@ -16348,12 +17567,38 @@ export {
16348
17567
  questionsOnFile,
16349
17568
  stepQuestionJudge,
16350
17569
  stepJudges,
16351
- import_yaml7 as import_yaml,
17570
+ DRAFTS_SCHEMA_VERSION,
17571
+ DRAFTED_FILE,
17572
+ CORRECTIONS_FILE,
17573
+ DraftedRecord,
17574
+ DraftedFile,
17575
+ CorrectionScope,
17576
+ CorrectionState,
17577
+ Correction,
17578
+ CorrectionsFile,
17579
+ draftSha,
17580
+ readDrafted,
17581
+ recordAiDraft,
17582
+ recordPersonSave,
17583
+ draftProvenance,
17584
+ DRAFT_ITEMS_MAX,
17585
+ draftItems,
17586
+ draftItemText,
17587
+ readCorrections,
17588
+ addCorrection,
17589
+ removeCorrection,
17590
+ CORRECTIONS_RULE,
17591
+ correctionsText,
17592
+ stageCorrections,
17593
+ snapshotFiles,
17594
+ settleCorrections,
17595
+ import_yaml8 as import_yaml,
16352
17596
  filePending,
16353
17597
  judged,
16354
17598
  yamlOf,
16355
17599
  EXPAND_AGENT,
16356
17600
  DIGEST_KINDS,
17601
+ titledText,
16357
17602
  checkExpansion,
16358
17603
  expansionInput,
16359
17604
  expandFor,
@@ -16411,6 +17656,8 @@ export {
16411
17656
  PERSON_DRAFT,
16412
17657
  HAND_BACKS,
16413
17658
  lineExists,
17659
+ retireSettledEscalations,
17660
+ retireChangeEscalations,
16414
17661
  stageRetrievalAi,
16415
17662
  retrievalWaiting,
16416
17663
  withoutResume,
@@ -16497,6 +17744,7 @@ export {
16497
17744
  isEmptyDelta,
16498
17745
  emptyDeltaIssues,
16499
17746
  splitAnswer,
17747
+ SKETCH_RULE,
16500
17748
  readDeltas,
16501
17749
  entriesOf,
16502
17750
  mergedFlowsText,
@@ -16536,6 +17784,7 @@ export {
16536
17784
  NeedsFile,
16537
17785
  needQuoteProblem,
16538
17786
  whenFromLeadTime,
17787
+ lineAtFolded,
16539
17788
  readNeeds,
16540
17789
  renderNeeds,
16541
17790
  findNeeds,
@@ -16633,6 +17882,8 @@ export {
16633
17882
  promptFileOf,
16634
17883
  taskPromptFiles,
16635
17884
  renderStoryFile,
17885
+ buildLine,
17886
+ withBuildLine,
16636
17887
  tickedTasks,
16637
17888
  mergeRequests,
16638
17889
  writeStories,
@@ -16660,6 +17911,21 @@ export {
16660
17911
  MAX_CHAIN_DEPTH,
16661
17912
  GUESSED_LINK_RULE,
16662
17913
  changeImpact,
17914
+ Stroke,
17915
+ DRAWN,
17916
+ isDrawnDelta,
17917
+ strokeIdOf,
17918
+ isDrawnStep,
17919
+ drawnBy,
17920
+ newStrokeId,
17921
+ applyStroke,
17922
+ drawnStrokes,
17923
+ sketchOf,
17924
+ renderSketchDelta,
17925
+ planSketchWrite,
17926
+ writeSketch,
17927
+ sketchFingerprint,
17928
+ sketchSlug,
16663
17929
  workItemsOf,
16664
17930
  conform,
16665
17931
  readConformDeltas,