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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (273) hide show
  1. package/dist/bin.js +8 -4
  2. package/dist/chunk-265LWMOA.js +91 -0
  3. package/dist/chunk-26WBY34Y.js +37 -0
  4. package/dist/{chunk-UBBC2XSG.js → chunk-26Z7OGQW.js} +518 -93
  5. package/dist/chunk-2D7PWSOK.js +172 -0
  6. package/dist/{chunk-JCZIKA4C.js → chunk-2HH3UCZM.js} +4 -4
  7. package/dist/{chunk-GDLULHTC.js → chunk-2HTEMYTB.js} +12 -26
  8. package/dist/chunk-2UCRFXFZ.js +130 -0
  9. package/dist/{chunk-MVZVGYXH.js → chunk-2V2ZPQQU.js} +81 -13
  10. package/dist/{chunk-BOHJZTEQ.js → chunk-36RY7ZXM.js} +130 -71
  11. package/dist/{chunk-I3E7CX4H.js → chunk-37WE3LS2.js} +3 -3
  12. package/dist/{chunk-IPKHE5D6.js → chunk-3AQ2R5NL.js} +1520 -871
  13. package/dist/chunk-3G6S7LAA.js +43 -0
  14. package/dist/chunk-3J5I7ZOU.js +58 -0
  15. package/dist/{chunk-65NYPGRC.js → chunk-3LZ6P5EP.js} +1 -1
  16. package/dist/chunk-3SSTNB4E.js +191 -0
  17. package/dist/chunk-3TD6S4TC.js +49 -0
  18. package/dist/{chunk-3C7EYRFZ.js → chunk-3UTYPHN3.js} +1 -1
  19. package/dist/{chunk-G2B4ZQGH.js → chunk-3WON4G3M.js} +2 -2
  20. package/dist/{chunk-S3QNRWYR.js → chunk-42EPP7OB.js} +12 -12
  21. package/dist/chunk-455SYIIE.js +113 -0
  22. package/dist/{chunk-YISXLPHN.js → chunk-4B6BT75O.js} +166 -23
  23. package/dist/{chunk-C6EKFH7J.js → chunk-4CLK2RJO.js} +7 -7
  24. package/dist/chunk-4CYTJ5TZ.js +38 -0
  25. package/dist/{chunk-EYWIDJFU.js → chunk-4D62IZII.js} +5 -5
  26. package/dist/chunk-4FOAYLI4.js +328 -0
  27. package/dist/{chunk-7JUKDRSZ.js → chunk-4KNOJC6J.js} +294 -215
  28. package/dist/{chunk-YMZ3TNYB.js → chunk-4PYC2UGC.js} +2 -2
  29. package/dist/{chunk-27NKUS3X.js → chunk-4SLBCEPD.js} +2104 -726
  30. package/dist/chunk-4TKVU2I2.js +122 -0
  31. package/dist/{chunk-RUVFXE2L.js → chunk-4WXRPQDE.js} +448 -47
  32. package/dist/{chunk-7ZUIMTOU.js → chunk-5CMPVC3N.js} +24 -24
  33. package/dist/chunk-5G7ILBUG.js +66 -0
  34. package/dist/{chunk-HFQCFSJR.js → chunk-5PV7S2ZP.js} +3 -3
  35. package/dist/{chunk-FITWGOJ2.js → chunk-5QUOPNLN.js} +4 -4
  36. package/dist/{chunk-Z6LF7HQN.js → chunk-5TDRBOAV.js} +5 -5
  37. package/dist/{chunk-T6AIP7H5.js → chunk-5WERBKLE.js} +199 -63
  38. package/dist/{chunk-XPCM366L.js → chunk-62DLJSLV.js} +8 -8
  39. package/dist/{chunk-L24C55VN.js → chunk-64AADMTW.js} +99 -25
  40. package/dist/{chunk-K65BJIMT.js → chunk-67BP7HXL.js} +17 -17
  41. package/dist/chunk-6DQYZU2W.js +379 -0
  42. package/dist/{chunk-EDS2L2LD.js → chunk-6EXDI5GI.js} +52 -26
  43. package/dist/chunk-6FNAKCET.js +38 -0
  44. package/dist/{chunk-6IU67AEK.js → chunk-75ASLHBT.js} +21 -15
  45. package/dist/{chunk-A3GY6SOA.js → chunk-75ZOZFGH.js} +18 -18
  46. package/dist/{chunk-MGYZM27B.js → chunk-77O633L5.js} +49 -23
  47. package/dist/{chunk-GQ4N4WKF.js → chunk-7AB6OWTV.js} +3 -3
  48. package/dist/{chunk-M766ZJ6W.js → chunk-7CZHUZBL.js} +25 -25
  49. package/dist/{chunk-A7UPUQFY.js → chunk-7FHNFCHK.js} +643 -124
  50. package/dist/chunk-7FUIVIBG.js +415 -0
  51. package/dist/{chunk-6QSERWXH.js → chunk-7QPCGTPS.js} +21 -21
  52. package/dist/{chunk-LR3R6Z2A.js → chunk-A2G72UI4.js} +7 -7
  53. package/dist/{chunk-I4JRCD6M.js → chunk-AE4P57KK.js} +23 -17
  54. package/dist/{chunk-AVJDSJ2F.js → chunk-AHW3RY3X.js} +7 -7
  55. package/dist/chunk-AM6FP6JW.js +43 -0
  56. package/dist/{chunk-CSZ4C5N6.js → chunk-AOCWFA7T.js} +93 -10
  57. package/dist/{chunk-EPTBZ3GP.js → chunk-API5TOUB.js} +1 -1
  58. package/dist/{chunk-652QWTLZ.js → chunk-AZDITBCT.js} +3 -3
  59. package/dist/{chunk-DPUYDDQG.js → chunk-BA2HUMDN.js} +25 -3
  60. package/dist/chunk-BA3U7ALS.js +99 -0
  61. package/dist/{chunk-HVUPKCGJ.js → chunk-BC6KKZ67.js} +5 -5
  62. package/dist/{chunk-CKSITVS4.js → chunk-BEPCKDCP.js} +2554 -722
  63. package/dist/{chunk-3ALD2W2A.js → chunk-BPPU3PW7.js} +4 -4
  64. package/dist/{chunk-QRAOANID.js → chunk-BVWWPBKD.js} +21 -21
  65. package/dist/{chunk-ZDPAS56H.js → chunk-BXBCMGIQ.js} +7 -1
  66. package/dist/chunk-BXZ4PKIT.js +233 -0
  67. package/dist/chunk-C7CEK3KW.js +247 -0
  68. package/dist/chunk-CFH3QH26.js +29 -0
  69. package/dist/chunk-CG6CULP6.js +170 -0
  70. package/dist/chunk-CLD4RVTA.js +45 -0
  71. package/dist/{chunk-DKJA76CJ.js → chunk-CNPY4NTZ.js} +13 -13
  72. package/dist/chunk-CNU5Q2D5.js +575 -0
  73. package/dist/{chunk-T2UYZSCQ.js → chunk-COYN3UAN.js} +126 -23
  74. package/dist/{chunk-23W7VWKR.js → chunk-CPK7QSBG.js} +1360 -113
  75. package/dist/{chunk-4AK7IBVP.js → chunk-D2WSW4GU.js} +1 -1
  76. package/dist/{chunk-ABWHYWNI.js → chunk-D57MFJPP.js} +3 -3
  77. package/dist/chunk-D5KV5PSK.js +45 -0
  78. package/dist/chunk-D5MASS4I.js +48 -0
  79. package/dist/{chunk-7EOBHUMC.js → chunk-DCWN3HFV.js} +2 -2
  80. package/dist/{chunk-QSXK2RGH.js → chunk-DDJPM5YX.js} +2 -2
  81. package/dist/chunk-DN4A2Q2O.js +57 -0
  82. package/dist/{chunk-IHMLFRVV.js → chunk-DUPZPDCJ.js} +58 -16
  83. package/dist/{chunk-PRPPGYZG.js → chunk-E2VXBSIR.js} +4 -4
  84. package/dist/chunk-E57AOFYN.js +32 -0
  85. package/dist/{chunk-UGLN7QHV.js → chunk-ECLXZWAG.js} +12 -12
  86. package/dist/{chunk-5WG5YP4T.js → chunk-EDZ45WIR.js} +14 -14
  87. package/dist/chunk-EFCQSMXB.js +37 -0
  88. package/dist/{chunk-2ANOROX3.js → chunk-EZIN5E5B.js} +8 -8
  89. package/dist/{chunk-BWZ77RQT.js → chunk-F6H2RJ3N.js} +1 -1
  90. package/dist/{chunk-OLQJTJJU.js → chunk-FAJDKNSD.js} +7 -3
  91. package/dist/chunk-FGAGWYZB.js +47 -0
  92. package/dist/{chunk-4WBVUHXN.js → chunk-FLVZFTLQ.js} +18 -18
  93. package/dist/chunk-FQ6WH6PB.js +140 -0
  94. package/dist/{chunk-T6XQL73F.js → chunk-FRHD22WM.js} +61 -23
  95. package/dist/{chunk-XAKWMCXI.js → chunk-FVMPFCDS.js} +4 -4
  96. package/dist/{chunk-B2WLAWNJ.js → chunk-FY3QIRTL.js} +3 -3
  97. package/dist/{chunk-CPA6HPVR.js → chunk-FYBF5MB2.js} +15 -11
  98. package/dist/{chunk-V2YTAIQO.js → chunk-G5YJBNDZ.js} +21 -6
  99. package/dist/chunk-G6IQYM25.js +339 -0
  100. package/dist/chunk-G6X4YL4D.js +161 -0
  101. package/dist/{chunk-SJRGMG6A.js → chunk-GC2VK3JT.js} +25 -25
  102. package/dist/{chunk-SP2I63OO.js → chunk-GCS3FZOB.js} +77 -3
  103. package/dist/{chunk-6TACQN3N.js → chunk-GDZB57IU.js} +5 -5
  104. package/dist/chunk-GIFMBCTW.js +221 -0
  105. package/dist/{chunk-YY6ZQQBP.js → chunk-GK4PYVEC.js} +18 -8
  106. package/dist/{chunk-TDD55CJH.js → chunk-GNWAOVFQ.js} +6 -5
  107. package/dist/chunk-GS76RXQE.js +226 -0
  108. package/dist/{chunk-YYQN46FY.js → chunk-GSGIZ2OO.js} +23 -23
  109. package/dist/chunk-GUYL7RMK.js +60 -0
  110. package/dist/chunk-GX74YWR7.js +321 -0
  111. package/dist/chunk-HE3HRDNZ.js +122 -0
  112. package/dist/{chunk-AIOQPLA5.js → chunk-HGHRSF3E.js} +30 -12
  113. package/dist/{chunk-2RMYG36Q.js → chunk-HK3VOHIE.js} +198 -336
  114. package/dist/{chunk-QZSX3F7C.js → chunk-HS4BGB4G.js} +24 -22
  115. package/dist/{chunk-XY3P7JW7.js → chunk-HSZMPH5T.js} +834 -149
  116. package/dist/{chunk-L6SGQIR3.js → chunk-HWD4JPU7.js} +4 -2
  117. package/dist/{chunk-PCVDV3UZ.js → chunk-HXXP2NTE.js} +6 -6
  118. package/dist/{chunk-AA6YULYB.js → chunk-I2A3Z5AK.js} +7 -7
  119. package/dist/{chunk-N7IXEHK3.js → chunk-ICHDSWEY.js} +7 -7
  120. package/dist/chunk-IFFRGC3N.js +31 -0
  121. package/dist/{chunk-4BN53TG3.js → chunk-IFHDV6KQ.js} +31 -23
  122. package/dist/chunk-J2O4LFVI.js +80 -0
  123. package/dist/chunk-J5AAZ6OJ.js +161 -0
  124. package/dist/{chunk-ZMFPW66A.js → chunk-JAABT3DA.js} +3 -3
  125. package/dist/chunk-JITJP64K.js +34 -0
  126. package/dist/chunk-JKIGL3XQ.js +162 -0
  127. package/dist/chunk-JRVNXLZP.js +615 -0
  128. package/dist/chunk-JRXHON4E.js +41 -0
  129. package/dist/{chunk-DHPACDG7.js → chunk-JS6WMC3J.js} +8 -8
  130. package/dist/chunk-JU4QNJSR.js +62 -0
  131. package/dist/{chunk-PAMPPUR6.js → chunk-JYMIB5XY.js} +1697 -172
  132. package/dist/{chunk-RRTKKSLQ.js → chunk-JYTIHTIK.js} +8 -6
  133. package/dist/{chunk-3HI6Z55Y.js → chunk-K332XW7K.js} +324 -39
  134. package/dist/{chunk-GLUX4EEP.js → chunk-K6JB6XTX.js} +9 -9
  135. package/dist/chunk-KDAHXQIE.js +294 -0
  136. package/dist/chunk-KYZMRSPS.js +93 -0
  137. package/dist/chunk-L4CESDPB.js +22 -0
  138. package/dist/{chunk-NXFPS2LR.js → chunk-LEBR5NHW.js} +14 -14
  139. package/dist/chunk-LIJJCBQI.js +223 -0
  140. package/dist/{chunk-DE76JROX.js → chunk-LIRVU3HW.js} +16 -16
  141. package/dist/{chunk-JMSCMUCA.js → chunk-LN77DXBM.js} +145 -21
  142. package/dist/{chunk-JIITQXFB.js → chunk-LNWEWYCM.js} +2969 -788
  143. package/dist/{chunk-TANFV3MZ.js → chunk-LO3BLMVG.js} +3 -3
  144. package/dist/chunk-LPV6T362.js +250 -0
  145. package/dist/{chunk-2VHF7OXM.js → chunk-LQH4P45K.js} +1 -1
  146. package/dist/chunk-LU4MQVAL.js +41 -0
  147. package/dist/chunk-LWDNKE4X.js +273 -0
  148. package/dist/chunk-M4H2UG4R.js +218 -0
  149. package/dist/{chunk-6BVONWJ7.js → chunk-M6D2U3YM.js} +3 -3
  150. package/dist/chunk-MD7KUEJ3.js +514 -0
  151. package/dist/{chunk-66YVAGGM.js → chunk-MMZUN7ZO.js} +20 -20
  152. package/dist/chunk-MZGRDONP.js +916 -0
  153. package/dist/chunk-N3RUGTGV.js +39 -0
  154. package/dist/{chunk-JGZCG5VM.js → chunk-NCB6656Z.js} +7 -7
  155. package/dist/{chunk-4NDAAFQT.js → chunk-NRQ65LTQ.js} +7 -5
  156. package/dist/{chunk-UNM6ZOKP.js → chunk-NTAVOWYD.js} +145 -18
  157. package/dist/{chunk-3USYZGRF.js → chunk-NYOABJNK.js} +36 -27
  158. package/dist/{chunk-WVPJSRRJ.js → chunk-O4IKAIZ2.js} +24 -20
  159. package/dist/{chunk-D3PO4DOZ.js → chunk-ODMBPDDV.js} +51 -14
  160. package/dist/chunk-OKUB6RJA.js +533 -0
  161. package/dist/chunk-OMPENNWY.js +39 -0
  162. package/dist/chunk-OPQZISG3.js +25 -0
  163. package/dist/{chunk-5GGOLAKC.js → chunk-OV57AHCG.js} +26 -26
  164. package/dist/chunk-OZJ3JLYI.js +58 -0
  165. package/dist/{chunk-KEM4SDDP.js → chunk-P2VBXX7N.js} +11 -10
  166. package/dist/{chunk-S2A7YC3Q.js → chunk-PAINMSID.js} +10150 -1720
  167. package/dist/chunk-PKJAV25S.js +43 -0
  168. package/dist/{chunk-EBEW3XXQ.js → chunk-PNQLCRSS.js} +4 -4
  169. package/dist/{chunk-WY6PP6AA.js → chunk-PQH56YF6.js} +37 -5
  170. package/dist/chunk-PRTBPXYA.js +50 -0
  171. package/dist/{chunk-YEDDOYSC.js → chunk-PRW3HXM3.js} +2 -2
  172. package/dist/chunk-PRZ7KMWL.js +42 -0
  173. package/dist/{chunk-6M5NK3K3.js → chunk-PTQ3ZALZ.js} +42 -4
  174. package/dist/{chunk-5GDRCDQB.js → chunk-PUEWDYGP.js} +2 -2
  175. package/dist/chunk-Q26LDIUV.js +236 -0
  176. package/dist/{chunk-CR3MTH32.js → chunk-Q46QDDMF.js} +77 -7
  177. package/dist/chunk-QBNWF5ZU.js +19 -0
  178. package/dist/chunk-QE527XRY.js +342 -0
  179. package/dist/{chunk-CNUKCS2T.js → chunk-QHJJMZLX.js} +1518 -54
  180. package/dist/chunk-QJCJOMW3.js +425 -0
  181. package/dist/{chunk-VYDBJ73M.js → chunk-QKMRFNMA.js} +15 -15
  182. package/dist/chunk-QOOEYDNJ.js +317 -0
  183. package/dist/chunk-QSIDVFB7.js +45 -0
  184. package/dist/{chunk-R77KLRB3.js → chunk-RA33EYS4.js} +19 -18
  185. package/dist/{chunk-DFBCSHVT.js → chunk-RALY3E5U.js} +4 -4
  186. package/dist/{chunk-ZCJDOK2S.js → chunk-S2RY3BOD.js} +35 -21
  187. package/dist/chunk-S5LSDXV4.js +40 -0
  188. package/dist/chunk-SB5GRUFM.js +54 -0
  189. package/dist/chunk-SH27FYPF.js +150 -0
  190. package/dist/chunk-T74WQQGS.js +40 -0
  191. package/dist/{chunk-O3WI5KWW.js → chunk-TGVVUKTG.js} +2 -2
  192. package/dist/chunk-THN26IEF.js +48 -0
  193. package/dist/chunk-TJH2EUAZ.js +47 -0
  194. package/dist/{chunk-ZNCEKR4J.js → chunk-TQ4TG57V.js} +12 -8
  195. package/dist/{chunk-7FBGJFEE.js → chunk-TQTRWENN.js} +12 -12
  196. package/dist/{chunk-TYAID7B7.js → chunk-TSM6T6GQ.js} +23 -23
  197. package/dist/{chunk-DPZHZQKE.js → chunk-TW3KOSQK.js} +43 -10
  198. package/dist/{chunk-MC3H7736.js → chunk-TWQRPM76.js} +37 -4
  199. package/dist/chunk-TYD6I4CZ.js +109 -0
  200. package/dist/chunk-U3NBK4EU.js +420 -0
  201. package/dist/{chunk-6JCPI55B.js → chunk-UDXWIPCB.js} +47 -7
  202. package/dist/{chunk-MMOSZPWJ.js → chunk-UEVZZZU2.js} +4 -4
  203. package/dist/{chunk-JBTK5OP2.js → chunk-UF4UHCIG.js} +2 -2
  204. package/dist/{chunk-6JUBZBMW.js → chunk-UFE36WJH.js} +4 -4
  205. package/dist/{chunk-ONLS5QA5.js → chunk-URIKUID7.js} +12 -12
  206. package/dist/chunk-UYLBRRFK.js +1106 -0
  207. package/dist/chunk-UZACEG2C.js +515 -0
  208. package/dist/{chunk-7SW2QWLU.js → chunk-VAMHOHUU.js} +12 -12
  209. package/dist/{chunk-RZPYKDWS.js → chunk-VUVGX5L2.js} +1 -1
  210. package/dist/{chunk-Q4D5X7QB.js → chunk-VYOCPTUB.js} +44 -24
  211. package/dist/{chunk-CAQZUNOH.js → chunk-WAZEXEJH.js} +567 -3
  212. package/dist/chunk-WCEF7H6L.js +221 -0
  213. package/dist/{chunk-2DIQQJCB.js → chunk-WEOMJHPV.js} +12 -12
  214. package/dist/{chunk-FHH4I4ZV.js → chunk-WFU77T3A.js} +12 -6
  215. package/dist/chunk-WIWTJMMR.js +292 -0
  216. package/dist/{chunk-2BFCZ3RK.js → chunk-WNTIOBOG.js} +18 -18
  217. package/dist/{chunk-7WD63HU2.js → chunk-X2CUXTWU.js} +6 -6
  218. package/dist/chunk-X7EMBM5X.js +34 -0
  219. package/dist/chunk-XEQCQO66.js +33 -0
  220. package/dist/{chunk-F7HYOUFF.js → chunk-XG4WJHLQ.js} +20 -4
  221. package/dist/{chunk-EFHQ3TWP.js → chunk-XOL3GFW2.js} +171 -31
  222. package/dist/{chunk-NE2W3OO7.js → chunk-XQ3YZ4QW.js} +22 -22
  223. package/dist/{chunk-CEXSU4S6.js → chunk-Y6K3CJBU.js} +723 -85
  224. package/dist/{chunk-4RS5KZQD.js → chunk-YAH5IQAL.js} +5 -5
  225. package/dist/{chunk-64EQYWCA.js → chunk-YBOVNMHC.js} +51 -14
  226. package/dist/chunk-YF5DZCPN.js +1212 -0
  227. package/dist/chunk-YIGBJNH4.js +37 -0
  228. package/dist/{chunk-BKVWTZ6A.js → chunk-YJ3TBOTK.js} +25 -25
  229. package/dist/{chunk-Q5AMFEWD.js → chunk-YKTE2RU3.js} +2 -2
  230. package/dist/{chunk-7EKXJZ2Y.js → chunk-YO2PKTXT.js} +296 -37
  231. package/dist/{chunk-6J5OKA4Y.js → chunk-YTTNTQOX.js} +15 -15
  232. package/dist/{chunk-OT2O76OS.js → chunk-YWAKDNMH.js} +183 -33
  233. package/dist/{chunk-R6XF6UR3.js → chunk-YZDZOEV4.js} +4 -4
  234. package/dist/chunk-Z3QFH7YG.js +17 -0
  235. package/dist/{chunk-W5BVQF44.js → chunk-Z6VLC5X4.js} +17 -17
  236. package/dist/{chunk-HLJAXMM4.js → chunk-ZFQMMU5Y.js} +2 -2
  237. package/dist/{chunk-W3BQAZ3Q.js → chunk-ZKYRDV7A.js} +12 -12
  238. package/dist/{chunk-ZMAINWBW.js → chunk-ZLS6PU2O.js} +3 -3
  239. package/dist/{chunk-ZVM6AKKM.js → chunk-ZOSD4FXV.js} +3 -3
  240. package/dist/chunk-ZPDDEVQX.js +47 -0
  241. package/dist/{chunk-TXLH6WHQ.js → chunk-ZQLRCA2V.js} +11 -11
  242. package/dist/{chunk-HITVBIJS.js → chunk-ZX7235KR.js} +128 -54
  243. package/dist/{chunk-GT4I4ZWM.js → chunk-ZZFHLRQW.js} +18 -16
  244. package/dist/files.json +636 -364
  245. package/dist/packages/naysayer/constitution/constitution.yaml +19 -0
  246. package/dist/packages/sdk/npm/index.js +5614 -2343
  247. package/dist/packages/ui/dist/assets/{elk-api-tma0MCuh.js → elk-api-CowTnhEJ.js} +2 -2
  248. package/dist/packages/ui/dist/assets/index-Dw20IRhQ.css +1 -0
  249. package/dist/packages/ui/dist/assets/index-z2wv0d5d.js +47 -0
  250. package/dist/packages/ui/dist/index.html +2 -2
  251. package/package.json +4 -4
  252. package/dist/chunk-2BF2EYJV.js +0 -174
  253. package/dist/chunk-42EVSKME.js +0 -37
  254. package/dist/chunk-4BKGMUIK.js +0 -53
  255. package/dist/chunk-5KLGBXG5.js +0 -48
  256. package/dist/chunk-6BNC6EQ4.js +0 -41
  257. package/dist/chunk-6D2CQ5II.js +0 -41
  258. package/dist/chunk-7AVHLZGD.js +0 -45
  259. package/dist/chunk-D46WUV3T.js +0 -33
  260. package/dist/chunk-E36QOJMD.js +0 -47
  261. package/dist/chunk-FQXYPF7E.js +0 -147
  262. package/dist/chunk-GBRFVT63.js +0 -45
  263. package/dist/chunk-ICY36XCH.js +0 -34
  264. package/dist/chunk-J4K3KVLD.js +0 -131
  265. package/dist/chunk-JZU3H6UQ.js +0 -54
  266. package/dist/chunk-NN2ZQEB4.js +0 -38
  267. package/dist/chunk-PCOCACY3.js +0 -385
  268. package/dist/chunk-RTEYOSJY.js +0 -29
  269. package/dist/chunk-RVH5SUW5.js +0 -69
  270. package/dist/chunk-VN3DP4LQ.js +0 -43
  271. package/dist/chunk-XPTOBS2D.js +0 -48
  272. package/dist/packages/ui/dist/assets/index-B54fm9FQ.js +0 -29
  273. package/dist/packages/ui/dist/assets/index-DD_hufQz.css +0 -1
@@ -3,27 +3,47 @@ import { fileURLToPath as __atticusFileURLToPath } from "node:url";
3
3
  var require = __atticusCreateRequire(import.meta.url);
4
4
  var __atticusUrl = (p) => new URL("./" + p, import.meta.url).href;
5
5
  var __atticusPath = (p) => __atticusFileURLToPath(__atticusUrl(p));
6
+ import {
7
+ readOpenApiDocument
8
+ } from "./chunk-TWQRPM76.js";
6
9
  import {
7
10
  globToRegExp
8
- } from "./chunk-GQ4N4WKF.js";
11
+ } from "./chunk-7AB6OWTV.js";
9
12
  import {
10
- APIGEE_BUNDLE,
13
+ DECLARED_TOPIC,
14
+ DEFAULT_CONVENTIONS,
11
15
  Fact,
16
+ FactId,
17
+ JOINED_BY_AI,
18
+ JOINED_BY_PERSON,
19
+ JOINED_TO_SHARED_MAP,
20
+ JOIN_HOW_FLAGS,
21
+ LINK_EVIDENCE_NOT_GAPS,
12
22
  MOCK_FILE,
13
23
  NEVER_CHECKED_STEPS,
24
+ SHARED_MAP_EVIDENCE,
14
25
  citedFiles,
15
26
  claimOnLines,
27
+ clusterServiceAddress,
28
+ factIdPart,
29
+ forwardSlashes,
30
+ gatewayBundleTest,
31
+ isPatternPath,
16
32
  isSecretPath,
17
33
  lineNamesClaim,
34
+ linksFor,
18
35
  normalizePath,
36
+ noteProblem,
19
37
  partsBuildWord,
20
38
  partsNamePath,
39
+ plainPathPrefix,
21
40
  quoteMatches,
22
41
  quoteNotRequired,
23
42
  redactSecrets,
24
43
  staleCite,
25
- toCanonicalYaml
26
- } from "./chunk-S2A7YC3Q.js";
44
+ toCanonicalYaml,
45
+ underFolders
46
+ } from "./chunk-PAINMSID.js";
27
47
 
28
48
  // packages/verify/dist/source.js
29
49
  import { readdirSync, readFileSync, statSync } from "node:fs";
@@ -38,6 +58,13 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
38
58
  ".vscode",
39
59
  ".gradle",
40
60
  ".mvn",
61
+ "__pycache__",
62
+ ".venv",
63
+ "venv",
64
+ ".next",
65
+ ".nuxt"
66
+ ]);
67
+ var BUILD_OUTPUT = /* @__PURE__ */ new Set([
41
68
  "target",
42
69
  "build",
43
70
  "dist",
@@ -45,13 +72,32 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
45
72
  "bin",
46
73
  "obj",
47
74
  "vendor",
48
- "__pycache__",
49
- ".venv",
50
- "venv",
51
- ".next",
52
- ".nuxt",
53
75
  "coverage"
54
76
  ]);
77
+ var DOTNET = /\.(csproj|fsproj|vbproj|sln)$/i;
78
+ var NODE = /^package\.json$/;
79
+ var MADE_BESIDE = {
80
+ bin: DOTNET,
81
+ obj: DOTNET,
82
+ vendor: /^(go\.mod|composer\.json|Gemfile)$/,
83
+ target: /^(pom\.xml|build\.sbt|Cargo\.toml)$/,
84
+ build: /^(build\.gradle(\.kts)?|settings\.gradle(\.kts)?|package\.json|CMakeLists\.txt)$/,
85
+ dist: NODE,
86
+ coverage: NODE,
87
+ out: /^(tsconfig\.json|package\.json)$|\.iml$/
88
+ };
89
+ var ONLY_BESIDE = /* @__PURE__ */ new Set(["bin", "obj", "vendor"]);
90
+ function skippedFolder(above, name, siblings) {
91
+ if (SKIP_DIRS.has(name))
92
+ return true;
93
+ if (!BUILD_OUTPUT.has(name))
94
+ return false;
95
+ if (siblings.some((x) => MADE_BESIDE[name].test(x)))
96
+ return true;
97
+ if (above.some((a) => a.toLowerCase() === "src"))
98
+ return false;
99
+ return !ONLY_BESIDE.has(name);
100
+ }
55
101
  function insideRepo(path) {
56
102
  if (path === "" || isAbsolute(path) || /^[A-Za-z]:/.test(path))
57
103
  return false;
@@ -60,7 +106,7 @@ function insideRepo(path) {
60
106
  }
61
107
  function dirSource(root) {
62
108
  let cache;
63
- const walk = (dir, out) => {
109
+ const walk = (dir, above, out) => {
64
110
  let names;
65
111
  try {
66
112
  names = readdirSync(dir).sort();
@@ -76,8 +122,8 @@ function dirSource(root) {
76
122
  continue;
77
123
  }
78
124
  if (st.isDirectory()) {
79
- if (!SKIP_DIRS.has(name))
80
- walk(full, out);
125
+ if (!skippedFolder(above, name, names))
126
+ walk(full, [...above, name], out);
81
127
  } else if (st.isFile())
82
128
  out.push(relative(root, full).split(sep).join("/"));
83
129
  }
@@ -86,7 +132,7 @@ function dirSource(root) {
86
132
  list() {
87
133
  if (!cache) {
88
134
  cache = [];
89
- walk(root, cache);
135
+ walk(root, [], cache);
90
136
  }
91
137
  return cache;
92
138
  },
@@ -121,6 +167,11 @@ var FRAMEWORKS = [
121
167
  var KNOWN = [
122
168
  // Java and Kotlin
123
169
  ["spring-boot*", { name: "spring-boot", topic: "spring", shape: "rest" }],
170
+ // the annotations a Spring Boot app's main class carries, which a reader may cite instead
171
+ // (matched by `annotationKey`, so bare, qualified or with arguments)
172
+ ["@springbootapplication", { name: "spring-boot", topic: "spring", shape: "rest" }],
173
+ ["@enableautoconfiguration", { name: "spring-boot", topic: "spring", shape: "rest" }],
174
+ ["@quarkusmain", { name: "quarkus", topic: "quarkus" }],
124
175
  ["spring-webmvc", { name: "spring", topic: "spring", shape: "rest" }],
125
176
  ["spring-webflux", { name: "spring", topic: "spring", shape: "rest" }],
126
177
  ["spring-web", { name: "spring", topic: "spring", shape: "rest" }],
@@ -172,23 +223,45 @@ function declarationKey(declared) {
172
223
  const name = colon.split(/[=<>~!,\s[]/)[0] ?? colon;
173
224
  return name.replace(/^['"]|['"]$/g, "");
174
225
  }
175
- function frameworkOf(declared) {
226
+ function annotationKey(declared) {
227
+ const m = /^@?(?:[a-z_$][\w$]*\.)*([A-Z][\w$]*)\s*(?:\(.*)?$/s.exec(declared.trim());
228
+ return m ? `@${m[1].toLowerCase()}` : void 0;
229
+ }
230
+ function houseKnown(extra) {
231
+ return extra.map((e) => [
232
+ e.declares.toLowerCase(),
233
+ {
234
+ name: e.name,
235
+ ...e.topic && FRAMEWORKS.includes(e.topic) ? { topic: e.topic } : {},
236
+ ...e.shape ? { shape: e.shape } : {}
237
+ }
238
+ ]);
239
+ }
240
+ function frameworkOf(declared, extra = []) {
176
241
  const key = declarationKey(declared);
177
242
  if (!key)
178
243
  return void 0;
179
- for (const [pattern, known] of KNOWN) {
244
+ const table = extra.length ? [...houseKnown(extra), ...KNOWN] : KNOWN;
245
+ for (const [pattern, known] of table) {
180
246
  const star = pattern.endsWith("*");
181
247
  const p = star ? pattern.slice(0, -1) : pattern;
182
248
  if (star ? key.startsWith(p) : key === p)
183
249
  return known;
184
250
  }
251
+ const ann = annotationKey(declared);
252
+ if (ann) {
253
+ for (const [pattern, known] of table)
254
+ if (pattern === ann)
255
+ return known;
256
+ }
185
257
  return void 0;
186
258
  }
187
- function frameworkNameProblem(f) {
188
- const known = frameworkOf(f.cite.value);
259
+ function frameworkNameProblem(f, extra = []) {
260
+ const known = frameworkOf(f.cite.value, extra);
189
261
  const name = f.name.trim().toLowerCase();
190
262
  if (known) {
191
- return name === known.name ? void 0 : `names the framework "${f.name}", but ${f.cite.file}:${f.cite.line} declares ${f.cite.value}, which is ${known.name}`;
263
+ const legacy = annotationKey(f.cite.value) === `@${name}`;
264
+ return name === known.name.toLowerCase() || legacy ? void 0 : `names the framework "${f.name}", but ${f.cite.file}:${f.cite.line} declares ${f.cite.value}, which is ${known.name}`;
192
265
  }
193
266
  const key = declarationKey(f.cite.value);
194
267
  if (declarationKey(name) === key || name === key)
@@ -196,13 +269,13 @@ function frameworkNameProblem(f) {
196
269
  return `names the framework "${f.name}", which ${f.cite.file}:${f.cite.line} doesn't declare: it declares ${f.cite.value}, and Atticus doesn't know that one, so name it as the build file writes it (${key})`;
197
270
  }
198
271
  var frameworkFacts = (facts) => facts.filter((f) => f.kind === "framework").sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
199
- function frameworkTopics(facts) {
272
+ function frameworkTopics(facts, extra = []) {
200
273
  const fws = frameworkFacts(facts);
201
274
  if (!fws.length)
202
275
  return void 0;
203
276
  const topics = /* @__PURE__ */ new Set();
204
277
  for (const f of fws) {
205
- const known = frameworkOf(f.cite.value);
278
+ const known = frameworkOf(f.cite.value, extra);
206
279
  if (!known?.topic)
207
280
  topics.add("house");
208
281
  else
@@ -228,7 +301,7 @@ var SHAPE_ASK = {
228
301
  store: "which tables it reads and writes"
229
302
  };
230
303
  var holdsShape = (facts, shape2) => facts.some((f) => shape2 === "store" ? f.kind === "store" : f.kind === "entry" && f.entryType === (shape2 === "rest" ? "rest" : shape2));
231
- function frameworkGaps(facts) {
304
+ function frameworkGaps(facts, extra = []) {
232
305
  const fws = frameworkFacts(facts);
233
306
  if (!fws.length)
234
307
  return [];
@@ -236,7 +309,7 @@ function frameworkGaps(facts) {
236
309
  return [];
237
310
  const out = [];
238
311
  for (const f of fws) {
239
- const shape2 = frameworkOf(f.cite.value)?.shape;
312
+ const shape2 = frameworkOf(f.cite.value, extra)?.shape;
240
313
  if (!shape2 || holdsShape(facts, shape2))
241
314
  continue;
242
315
  out.push({
@@ -249,9 +322,14 @@ function frameworkGaps(facts) {
249
322
  }
250
323
  return out.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
251
324
  }
252
- function frameworksToLearn(facts, known = []) {
325
+ function isApigeeBundle(f) {
326
+ const file = f.cite.file.replace(/\\/g, "/").toLowerCase();
327
+ const said = `${f.cite.value} ${f.cite.quote ?? ""}`;
328
+ return /<\s*(APIProxy|SharedFlowBundle)\b/i.test(said) || /^(apiproxy|sharedflowbundle)$/i.test(f.cite.value.trim()) || /(^|\/)(apiproxy|sharedflowbundle|sharedflows)\//.test(file);
329
+ }
330
+ function frameworksToLearn(facts, known = [], extra = []) {
253
331
  const have = new Set(known.map(declarationKey));
254
- return frameworkFacts(facts).filter((f) => !frameworkOf(f.cite.value) && !have.has(declarationKey(f.name)) && !have.has(declarationKey(f.cite.value))).map((f) => ({
332
+ return frameworkFacts(facts).filter((f) => !isApigeeBundle(f) && !frameworkOf(f.cite.value, extra) && !have.has(declarationKey(f.name)) && !have.has(declarationKey(f.cite.value))).map((f) => ({
255
333
  id: f.id,
256
334
  framework: f.name,
257
335
  cite: `${f.cite.file}:${f.cite.line}`,
@@ -260,6 +338,88 @@ function frameworksToLearn(facts, known = []) {
260
338
  }));
261
339
  }
262
340
 
341
+ // packages/verify/dist/reviewed.js
342
+ var LINE_RANGE = /^(.+)#L(\d+)-L(\d+)$/;
343
+ function lineRangeOf(path) {
344
+ const m = LINE_RANGE.exec(path);
345
+ return m ? { file: m[1], from: Number(m[2]), to: Number(m[3]) } : void 0;
346
+ }
347
+ var SHAPE = `{"path": "<file or glob>", "why": "..."}`;
348
+ function parseReviewed(text) {
349
+ const entries = [];
350
+ const bad = [];
351
+ text.split(/\r?\n/).forEach((raw, i) => {
352
+ if (!raw.trim())
353
+ return;
354
+ const line = i + 1;
355
+ let json2;
356
+ try {
357
+ json2 = JSON.parse(raw);
358
+ } catch (e) {
359
+ bad.push({ line, raw, message: `line ${line} is not JSON: ${e.message}` });
360
+ return;
361
+ }
362
+ const o = json2;
363
+ const issues = [];
364
+ if (!o || typeof o !== "object" || Array.isArray(o))
365
+ issues.push("not an object");
366
+ else {
367
+ if (typeof o.path !== "string" || !o.path.trim())
368
+ issues.push("path: a file or glob");
369
+ if (typeof o.why !== "string" || !o.why.trim())
370
+ issues.push("why: why it holds nothing");
371
+ const extra = Object.keys(o).filter((k) => k !== "path" && k !== "why");
372
+ if (extra.length)
373
+ issues.push(`unknown keys: ${extra.join(", ")}`);
374
+ }
375
+ if (issues.length) {
376
+ bad.push({ line, raw, message: `line ${line} isn't ${SHAPE}: ${issues.join("; ")}` });
377
+ return;
378
+ }
379
+ const path = forwardSlashes(o.path.trim());
380
+ entries.push({ line, path, why: o.why.trim() });
381
+ });
382
+ return { entries, bad };
383
+ }
384
+ var isGlob = (path) => /[*?]/.test(path);
385
+
386
+ // packages/verify/dist/conventions.js
387
+ var SPEC_NAME = /(^|\/)[^/]*(openapi|swagger|asyncapi|api-docs)[^/]*\.(ya?ml|json|xml)$|\.oas\.(ya?ml|json)$/i;
388
+ function notReadTest(folders) {
389
+ const multi = folders.filter((f) => f.includes("/"));
390
+ const multiRe = underFolders(multi);
391
+ const names = new Set(folders.filter((f) => !f.includes("/")).map((f) => f.toLowerCase()));
392
+ return {
393
+ test(path) {
394
+ if (multiRe.test(path))
395
+ return true;
396
+ const parts = path.split("/").slice(0, -1).map((x) => x.toLowerCase());
397
+ const src = parts.indexOf("src");
398
+ return parts.some((x, j) => names.has(x) && !(src >= 0 && j > src && BUILD_OUTPUT.has(x)));
399
+ }
400
+ };
401
+ }
402
+ function rulesOf(c) {
403
+ const folders = underFolders(c.apiSpecFolders, { tail: "(.+/)?[^/]+\\.(ya?ml|json)$" });
404
+ return {
405
+ apiSpec: new RegExp(`${SPEC_NAME.source}|${folders.source}`, "i"),
406
+ notProduct: underFolders(c.notProductFolders),
407
+ notRead: notReadTest(c.notReadFolders),
408
+ gatewayBundle: gatewayBundleTest(c.gatewayBundleFolders),
409
+ frameworks: c.frameworks
410
+ };
411
+ }
412
+ var DEFAULT_READ_RULES = rulesOf(DEFAULT_CONVENTIONS);
413
+ var made = /* @__PURE__ */ new WeakMap();
414
+ function readRulesOf(c) {
415
+ if (!c || c === DEFAULT_CONVENTIONS)
416
+ return DEFAULT_READ_RULES;
417
+ let r = made.get(c);
418
+ if (!r)
419
+ made.set(c, r = rulesOf(c));
420
+ return r;
421
+ }
422
+
263
423
  // packages/verify/dist/apigee-policy.js
264
424
  var MAKES = {
265
425
  OAuthV1: ["oauth2"],
@@ -328,8 +488,57 @@ function authPolicyMismatch(auth, file, policy, lines) {
328
488
  }
329
489
  var authChecks = (auth) => CHECKS[auth] ?? auth;
330
490
 
491
+ // packages/verify/dist/call-security-lines.js
492
+ var CLIENT_CERT = /client[-_ .]?cert|keystore|key[-_ .]?store|ssl[-_ .]?bundle|\.pfx\b|\.p12\b|pkcs12|\bpfx\b|\bp12\b/i;
493
+ var CERT_AND_KEY = (l) => /\bcert\b/i.test(l) && /\bkey\b/i.test(l);
494
+ var KINDS = {
495
+ "tls mtls": {
496
+ test: (l) => CLIENT_CERT.test(l) || CERT_AND_KEY(l),
497
+ needs: "a client certificate, a key store or an SSL bundle"
498
+ },
499
+ "tls tls": { test: (l) => /https|ssl|tls/i.test(l), needs: "https, SSL or TLS" },
500
+ "tls none": {
501
+ test: (l) => !/https:\/\//i.test(l) && (/http:\/\//i.test(l) || /\b(false|disabled?|insecure|plain-?text|off)\b/i.test(l)),
502
+ needs: "a plain http:// URL or a TLS switch set off"
503
+ },
504
+ "auth oauth2": {
505
+ test: (l) => /oauth|token|bearer|jwt|authori[sz]|client[-_ ]?credentials|access[-_ ]?token/i.test(l),
506
+ needs: "a token (OAuth, bearer, JWT or an Authorization header)"
507
+ },
508
+ "auth api-key": {
509
+ test: (l) => /api[-_ ]?key|apikey|x-api|subscription[-_ ]?key|[a-z0-9][-_]key\b/i.test(l),
510
+ needs: "a key"
511
+ },
512
+ "auth basic": {
513
+ test: (l) => /basic|password|passwd|username|credential/i.test(l),
514
+ needs: "a password or basic credentials"
515
+ },
516
+ "auth mtls": {
517
+ test: (l) => CLIENT_CERT.test(l) || CERT_AND_KEY(l),
518
+ needs: "a client certificate, a key store or an SSL bundle"
519
+ }
520
+ };
521
+ function securityLineKind(what, claim, lines) {
522
+ const k = KINDS[`${what} ${claim}`];
523
+ if (!k)
524
+ return void 0;
525
+ return lines.some((l) => k.test(l)) ? void 0 : k.needs;
526
+ }
527
+ function lineOfValue(lines, line, value) {
528
+ if (!lines)
529
+ return void 0;
530
+ const v = value.toLowerCase();
531
+ for (const n of [line, line - 1, line + 1]) {
532
+ const l = lines[n - 1];
533
+ if (l !== void 0 && l.toLowerCase().includes(v))
534
+ return l;
535
+ }
536
+ return lines[line - 1];
537
+ }
538
+
331
539
  // packages/verify/dist/corrections.js
332
- function correctionFor(f, corrections) {
540
+ function correctionFor(f, all2) {
541
+ const corrections = all2.filter((c) => c.action !== "link" && c.action !== "unlink");
333
542
  const at = (c) => c.target.kind === f.kind && c.target.file === f.cite.file && c.target.line === f.cite.line;
334
543
  const byId = corrections.filter((c) => c.fact === f.id && at(c));
335
544
  const byCite = corrections.filter((c) => at(c) && c.target.value === f.cite.value);
@@ -337,6 +546,10 @@ function correctionFor(f, corrections) {
337
546
  const moved = edited.filter((c) => (c.line ?? c.target.line) === f.cite.line);
338
547
  return [...byId, ...byCite, ...moved].sort((a, b) => a.at < b.at ? 1 : a.at > b.at ? -1 : 0)[0];
339
548
  }
549
+ function outsidePairingFor(f, all2) {
550
+ const newest = linksFor(f, all2)[0];
551
+ return newest?.link?.system ? newest : void 0;
552
+ }
340
553
  function correctedFact(f, c) {
341
554
  const raw = JSON.parse(JSON.stringify(f));
342
555
  for (const [k, v] of Object.entries(c.set ?? {})) {
@@ -363,33 +576,64 @@ function applyCorrections(facts, corrections) {
363
576
  const applied = [];
364
577
  const used = /* @__PURE__ */ new Set();
365
578
  const stale = [];
366
- for (const f of facts) {
367
- const c = correctionFor(f, corrections);
368
- if (!c) {
369
- out.push(f);
579
+ const met = /* @__PURE__ */ new Set();
580
+ const withPairing = (f, as, over) => {
581
+ const p = outsidePairingFor(as, corrections);
582
+ if (!p)
583
+ return f;
584
+ if (over && over.at > p.at && over.set?.to !== void 0)
585
+ return f;
586
+ const fixed = correctedFact(f, { ...p, action: "edit", set: { to: p.link.system } });
587
+ if (typeof fixed === "string") {
588
+ stale.push({
589
+ correction: p,
590
+ why: "invalid",
591
+ message: `${p.by}'s pairing of ${f.kind} ${f.id} with ${p.link.system} doesn't make a fact (${fixed}); the call is kept as it was`
592
+ });
593
+ return f;
594
+ }
595
+ applied.push({ fact: f.id, action: "link", by: p.by });
596
+ return { ...fixed, conf: "inferred" };
597
+ };
598
+ for (const f0 of facts) {
599
+ for (const l of linksFor(f0, corrections))
600
+ met.add(l);
601
+ const c = correctionFor(f0, corrections);
602
+ if (c?.action === "wrong") {
603
+ used.add(c);
604
+ applied.push({ fact: f0.id, action: "wrong", by: c.by });
370
605
  continue;
371
606
  }
372
- used.add(c);
373
- if (c.action === "wrong") {
374
- applied.push({ fact: f.id, action: "wrong", by: c.by });
607
+ if (!c) {
608
+ out.push(withPairing(f0, f0));
375
609
  continue;
376
610
  }
377
- const fixed = correctedFact(f, c);
611
+ used.add(c);
612
+ const fixed = correctedFact(f0, c);
378
613
  if (typeof fixed === "string") {
379
614
  stale.push({
380
615
  correction: c,
381
616
  why: "invalid",
382
- message: `${c.by}'s correction of ${f.kind} ${f.id} doesn't make a fact (${fixed}); the fact is kept as it was`
617
+ message: `${c.by}'s correction of ${f0.kind} ${f0.id} doesn't make a fact (${fixed}); the fact is kept as it was`
383
618
  });
384
- out.push(f);
619
+ out.push(withPairing(f0, f0));
385
620
  continue;
386
621
  }
387
- applied.push({ fact: f.id, action: "edit", by: c.by });
388
- out.push(fixed);
622
+ applied.push({ fact: f0.id, action: "edit", by: c.by });
623
+ out.push(withPairing(fixed, f0, c));
389
624
  }
390
625
  for (const c of corrections) {
391
626
  if (used.has(c))
392
627
  continue;
628
+ if (c.action === "link" || c.action === "unlink") {
629
+ if (!met.has(c))
630
+ stale.push({
631
+ correction: c,
632
+ why: "none",
633
+ message: `${c.by}'s pairing of call ${c.fact} (${c.target.file}:${c.target.line}) with ${[c.link?.method, c.link?.path].filter(Boolean).join(" ")} on ${c.link?.repo ?? c.link?.system} meets no call in this read; it is kept in the file and applied when the call is read again`
634
+ });
635
+ continue;
636
+ }
393
637
  const replaced = [...used].some((u) => (u.fact === c.fact || u.target.value === c.target.value) && u.target.kind === c.target.kind && u.target.file === c.target.file && u.target.line === c.target.line);
394
638
  if (replaced || c.action === "wrong")
395
639
  continue;
@@ -1199,10 +1443,11 @@ function isLocaleBundle(path) {
1199
1443
  }
1200
1444
  var DATA_FILE_LINES = 2e3;
1201
1445
  var DATA_FILE_CHARS = 2e5;
1446
+ var API_SPEC = DEFAULT_READ_RULES.apiSpec;
1202
1447
  var DATA_EXT = /\.(json|xml)$/i;
1203
1448
  var KEEP_BIG = /(^|\/)(pom\.xml|package\.json|composer\.json|angular\.json|project\.json|nx\.json|lerna\.json|workspace\.json|tsconfig[^/]*\.json|jsconfig\.json|appsettings[^/]*\.json|launchSettings\.json|[^/]*(openapi|swagger|asyncapi|api-docs)[^/]*\.(json|xml)|[^/]*mapper[^/]*\.xml)$|(^|\/)(config|configuration|migrations?|db|database|changelogs?|liquibase|flyway)\//i;
1204
- function isDataFile(path, lines) {
1205
- if (!DATA_EXT.test(path) || KEEP_BIG.test(path))
1449
+ function isDataFile(path, lines, rules = DEFAULT_READ_RULES) {
1450
+ if (!DATA_EXT.test(path) || KEEP_BIG.test(path) || rules.apiSpec.test(path))
1206
1451
  return false;
1207
1452
  if (lines.length - (lines[lines.length - 1] === "" ? 1 : 0) > DATA_FILE_LINES)
1208
1453
  return true;
@@ -1252,66 +1497,502 @@ function pageRouteMatches(file, lines, exists, path) {
1252
1497
  return norm(route) === norm(path);
1253
1498
  }
1254
1499
 
1255
- // packages/verify/dist/reviewed.js
1256
- var SHAPE = `{"path": "<file or glob>", "why": "..."}`;
1257
- function parseReviewed(text) {
1258
- const entries = [];
1259
- const bad = [];
1260
- text.split(/\r?\n/).forEach((raw, i) => {
1261
- if (!raw.trim())
1500
+ // packages/verify/dist/reachable.js
1501
+ var CODE = /\.(java|kt|kts|scala|groovy|cs|vb|fs|py|rb|go|rs|php|swift|ts|tsx|js|mjs|cjs|dart)$/i;
1502
+ var MODEL_DIR = /(^|\/)(models?|dtos?|payloads?|schemas?|pojos?|beans|viewmodels?)\//i;
1503
+ var MODEL_NAME = /(dto|request|response|payload|model|body)$/i;
1504
+ var MODEL_PART = /[._-](dto|model|schema|payload|request|response)\.[^/.]+$/i;
1505
+ var MAPS_TABLE = /@Entity\b|@Table\b|@Document\b|\[Table\(|__tablename__|models\.Model\b|@Entity\(|\bgorm\.Model\b/;
1506
+ var ROOT_PART = /[._-](controller|resolver|gateway|handlers?|router|routes?|consumer|listener|subscriber|client|publisher|producer|endpoints?)\.[^/.]+$/i;
1507
+ var CLIENT_NAME = /(client|gateway|publisher|producer|sender|feign|proxy|connector)$/i;
1508
+ var CLIENT_DIR = /(^|\/)(clients?|gateways?|integrations?|publishers?|producers?|outbound)\//i;
1509
+ var base = (path) => path.slice(path.lastIndexOf("/") + 1);
1510
+ var stem = (path) => {
1511
+ const b = base(path);
1512
+ const dot = b.indexOf(".");
1513
+ return dot > 0 ? b.slice(0, dot) : b;
1514
+ };
1515
+ function isModelFile(path, text) {
1516
+ if (!CODE.test(path) || readingRank(path) === 5)
1517
+ return false;
1518
+ if (!MODEL_DIR.test(path) && !MODEL_NAME.test(stem(path)) && !MODEL_PART.test(path))
1519
+ return false;
1520
+ return text === void 0 || !MAPS_TABLE.test(text);
1521
+ }
1522
+ var ENTRY_POINT_NAME = /(controller|resource|router|routes?|handlers?|listener|consumer|subscriber|endpoints?|servlet|webhook|resolver)$/i;
1523
+ var ENTRY_POINT_DIR = /(^|\/)(controllers?|routes|routers|handlers|endpoints|listeners|consumers)\//i;
1524
+ var ENTRY_POINT_PART = /[._-](controller|resolver|handlers?|router|routes?|consumer|listener|subscriber|endpoints?)\.[^/.]+$/i;
1525
+ function isEntryPointFile(path) {
1526
+ if (!CODE.test(path) || readingRank(path) === 5 || isModelFile(path))
1527
+ return false;
1528
+ return ENTRY_POINT_NAME.test(stem(path)) || ENTRY_POINT_DIR.test(path) || ENTRY_POINT_PART.test(path);
1529
+ }
1530
+ function isRootFile(path) {
1531
+ if (!CODE.test(path) || readingRank(path) === 5 || isModelFile(path))
1532
+ return false;
1533
+ return readingRank(path) === 2 || isEntryPointFile(path) || ROOT_PART.test(path) || CLIENT_NAME.test(stem(path)) || CLIENT_DIR.test(path);
1534
+ }
1535
+ var DECLARES = /\b(?:class|record|interface|struct|enum|object)\s+([A-Za-z_][A-Za-z0-9_]*)|^\s*(?:export\s+)?(?:declare\s+)?type\s+([A-Za-z_][A-Za-z0-9_]*)/gm;
1536
+ var IDENT = /[A-Za-z_][A-Za-z0-9_]*/g;
1537
+ function declaredTypes(path, text) {
1538
+ const out = [];
1539
+ const s = stem(path);
1540
+ if (/^[A-Z][A-Za-z0-9_]*$/.test(s))
1541
+ out.push(s);
1542
+ for (const m of text.matchAll(DECLARES)) {
1543
+ const n = m[1] ?? m[2];
1544
+ if (n && !out.includes(n))
1545
+ out.push(n);
1546
+ }
1547
+ return out;
1548
+ }
1549
+ function identifiers(text) {
1550
+ return [...new Set(text.match(IDENT) ?? [])];
1551
+ }
1552
+ var NOT_REACHED_WHY = "model classes no entry point, call or field fact reaches (ADR 0169): left out of the read; atticus map <repo> --models all reads them";
1553
+ function reachableModels(o) {
1554
+ const texts = /* @__PURE__ */ new Map();
1555
+ const textOf = (p) => {
1556
+ if (!texts.has(p))
1557
+ texts.set(p, o.read(p) ?? "");
1558
+ return texts.get(p);
1559
+ };
1560
+ const ordered = readingOrder(o.paths);
1561
+ const models = ordered.filter((p) => isModelFile(p, textOf(p))).sort();
1562
+ const isModel = new Set(models);
1563
+ const byName = /* @__PURE__ */ new Map();
1564
+ const declared = /* @__PURE__ */ new Map();
1565
+ for (const p of models) {
1566
+ const names = declaredTypes(p, textOf(p));
1567
+ declared.set(p, names);
1568
+ for (const n of names)
1569
+ byName.set(n, [...byName.get(n) ?? [], p]);
1570
+ }
1571
+ const known = new Set(o.paths);
1572
+ const roots = [];
1573
+ const addRoot = (p) => {
1574
+ if (known.has(p) && !isModel.has(p) && !roots.includes(p))
1575
+ roots.push(p);
1576
+ };
1577
+ const facts = o.facts ?? [];
1578
+ const entries = o.from === "entries";
1579
+ const calls2 = new Set(facts.filter((f) => f.kind === "call").map((f) => f.id));
1580
+ for (const f of facts)
1581
+ if (f.kind === "entry" || f.kind === "call" && !entries)
1582
+ for (const p of citedFiles(f))
1583
+ addRoot(p);
1584
+ for (const p of ordered)
1585
+ if (entries ? isEntryPointFile(p) : isRootFile(p))
1586
+ addRoot(p);
1587
+ const reached = /* @__PURE__ */ new Map();
1588
+ const queue = [];
1589
+ const reach = (names, from, own = []) => {
1590
+ for (const n of names) {
1591
+ if (own.includes(n))
1592
+ continue;
1593
+ for (const p of byName.get(n) ?? []) {
1594
+ const r = reached.get(p);
1595
+ if (r) {
1596
+ if (r.from === from && !r.types.includes(n))
1597
+ r.types.push(n);
1598
+ continue;
1599
+ }
1600
+ reached.set(p, { path: p, types: [n], from });
1601
+ queue.push(p);
1602
+ }
1603
+ }
1604
+ };
1605
+ for (const p of roots)
1606
+ reach(identifiers(textOf(p)), p);
1607
+ for (const f of facts) {
1608
+ for (const p of citedFiles(f))
1609
+ if (isModel.has(p) && !reached.has(p)) {
1610
+ reached.set(p, { path: p, types: [...(declared.get(p) ?? []).slice(0, 1)], from: f.id });
1611
+ queue.push(p);
1612
+ }
1613
+ if (f.kind === "field" && !(entries && calls2.has(f.owner)))
1614
+ reach(identifiers([f.type ?? "", f.transform === "message type" ? f.name : ""].join(" ")), f.id);
1615
+ }
1616
+ const rooted = roots.length > 0 || reached.size > 0;
1617
+ if (!rooted)
1618
+ return {
1619
+ rooted,
1620
+ roots,
1621
+ reached: models.map((p) => ({ path: p, types: declared.get(p) ?? [], from: "" })),
1622
+ notReached: [],
1623
+ models: models.length
1624
+ };
1625
+ for (let i = 0; i < queue.length; i++) {
1626
+ const p = queue[i];
1627
+ reach(identifiers(textOf(p)), p, declared.get(p));
1628
+ }
1629
+ return {
1630
+ rooted,
1631
+ roots,
1632
+ reached: queue.map((p) => reached.get(p)),
1633
+ notReached: models.filter((p) => !reached.has(p)),
1634
+ models: models.length
1635
+ };
1636
+ }
1637
+
1638
+ // packages/verify/dist/method.js
1639
+ var HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
1640
+ function wordsOf(line) {
1641
+ return line.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").toLowerCase().split(/[^a-z]+/).filter(Boolean);
1642
+ }
1643
+ function methodOnLines(method, lines) {
1644
+ const m = method.toLowerCase();
1645
+ return lines.some((l) => wordsOf(l).includes(m));
1646
+ }
1647
+ var CHAIN_LINES = 8;
1648
+ var CONTINUES = /^\s*\??\./;
1649
+ function chainOf(file, line) {
1650
+ const at = (n) => file?.[n - 1];
1651
+ const cited = at(line);
1652
+ if (cited === void 0)
1653
+ return [];
1654
+ const out = [cited];
1655
+ for (let n = line, k = 0; k < CHAIN_LINES && CONTINUES.test(at(n) ?? ""); k++, n--) {
1656
+ const above = at(n - 1);
1657
+ if (above === void 0)
1658
+ break;
1659
+ out.push(above);
1660
+ }
1661
+ for (let n = line + 1, k = 0; k < CHAIN_LINES && CONTINUES.test(at(n) ?? ""); k++, n++)
1662
+ out.push(at(n));
1663
+ return out;
1664
+ }
1665
+ function methodUnseenOf(f, lines, factOf) {
1666
+ if (f.kind !== "entry" && f.kind !== "call")
1667
+ return void 0;
1668
+ if (!f.method || !HTTP_METHODS.has(f.method.toUpperCase()))
1669
+ return void 0;
1670
+ const at = (file, line) => lines(file)?.[line - 1];
1671
+ const route = f.kind === "call" && f.from ? factOf?.(f.from) : void 0;
1672
+ const forwards = route?.kind === "entry" && route.method?.toUpperCase() === f.method.toUpperCase();
1673
+ const looked = [
1674
+ ...chainOf(lines(f.cite.file), f.cite.line),
1675
+ at(f.cite.file, f.cite.line + 1),
1676
+ ...(f.cite.with ?? []).map((p) => at(p.file, p.line)),
1677
+ ...forwards ? [at(route.cite.file, route.cite.line), at(route.cite.file, route.cite.line + 1)] : []
1678
+ ].filter((l) => l !== void 0);
1679
+ if (methodOnLines(f.method, looked))
1680
+ return void 0;
1681
+ const cite2 = `${f.cite.file}:${f.cite.line}`;
1682
+ const method = f.method.toUpperCase();
1683
+ return {
1684
+ id: f.id,
1685
+ kind: f.kind,
1686
+ method,
1687
+ cite: cite2,
1688
+ message: `${f.id} claims ${method}, which neither ${cite2}, the chain it is in, the next line, a cite.with line nor the route it forwards writes`
1689
+ };
1690
+ }
1691
+
1692
+ // packages/verify/dist/skipped.js
1693
+ function notReadForSkipped(path, rules = DEFAULT_READ_RULES) {
1694
+ return rules.notRead.test(path);
1695
+ }
1696
+ var isCommentLine = (l) => /^\s*(\/\/|\/\*|\*|<!--|-- |#(?!\s*(region|endregion|if|elif|else|endif|define|undef|pragma|nullable|include|import)\b)(?!!))/.test(l);
1697
+ var IMPORT_LINE = /^\s*(import|export\s+[^=]*\bfrom\b|from\s+\S+\s+import|package|using|require|#\s*include|@import|@use|use|library)\b/;
1698
+ var IMPORT_CALL = /\brequire\s*\(|\bimport\s*\(|\bfrom\s+["'`]/;
1699
+ var NAMESPACE_LINE = /\bxmlns(:\w+)?\s*=|schemaLocation\s*=|<!DOCTYPE|^\s*<\?xml/i;
1700
+ var URL_RE = /\b(?:https?|wss?|grpcs?|amqps?):\/\/[^\s"'`<>()[\],;\\|^]+/gi;
1701
+ var STANDARD_HOST = /^(www\.)?(w3\.org|json-schema\.org|maven\.apache\.org|apache\.org|springframework\.org|java\.sun\.com|xmlns\.jcp\.org|purl\.org|opensource\.org|gnu\.org|schema\.org|schemas\.[^/]+|xmlns\.[^/]+|ietf\.org|tools\.ietf\.org|creativecommons\.org)$/i;
1702
+ var FS_ROOT = /^\/(usr|etc|var|tmp|bin|sbin|opt|home|dev|proc|sys|lib|lib64|mnt|root|Users|private|Library|System|Applications|Volumes)(\/|$)/;
1703
+ var FILE_EXT = /\.(m?js|cjs|jsx|tsx?|css|scss|less|png|jpe?g|gif|svg|ico|webp|bmp|woff2?|ttf|eot|otf|map|jar|war|ear|class|so|dll|exe|sh|bat|ps1|py|rb|txt|log|pem|crt|key|lock|ya?ml|properties|conf|ini|env|md|mp[34]|wav|pdf|zip|gz|tar)$/i;
1704
+ var PATH_SEG = "[A-Za-z0-9_.~{}$:<>*%@+=-]+";
1705
+ var PATH_RE = new RegExp(`^(?:\\$\\{[^}]+\\}|%s|\\{[A-Za-z_]+\\})?((?:\\/${PATH_SEG})+\\/?)$`);
1706
+ var NOT_TOPIC_LAST = new Set("com org net io dev app ai co uk de fr in jp us eu example internal local localhost test cloud corp lan svc java kt scala cs ts js json xml yml yaml properties html htm txt md sql proto csv log jar dll exe sh py rb go class png svg gif jpg css".split(" "));
1707
+ var NOT_TOPIC_FIRST = new Set("com org net io java javax jakarta android androidx kotlin kotlinx scala sun microsoft system google apache spring springframework management server logging hibernate jdk www".split(" "));
1708
+ var DOTTED_TOPIC = /^[a-z][a-z0-9_-]*(\.[a-z][a-z0-9_-]*){2,}$/;
1709
+ var SEPARATED_NAME = /^[A-Za-z][A-Za-z0-9_]*([._:-][A-Za-z0-9_]+)+$/;
1710
+ var QUOTED = /(["'`])((?:\\.|(?!\1)[^\\\n])*)\1/g;
1711
+ var KEY_VALUE = /^\s*-?\s*["']?([A-Za-z0-9_.\-[\]]+)["']?\s*[:=]\s*([^\s"'#]+)\s*$/;
1712
+ var ELEMENT_TEXT = /<([A-Za-z][\w:.-]*)[^>]*>([^<\s]+)<\/\1>/g;
1713
+ var wordsOf2 = (line) => line.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").toLowerCase().split(/[^a-z]+/).filter(Boolean);
1714
+ var TOPIC_WORDS = /* @__PURE__ */ new Set([
1715
+ "topic",
1716
+ "topics",
1717
+ "queue",
1718
+ "queues",
1719
+ "channel",
1720
+ "channels",
1721
+ "destination",
1722
+ "destinations",
1723
+ "exchange",
1724
+ "exchanges"
1725
+ ]);
1726
+ var topicContext = (line) => {
1727
+ const w = wordsOf2(line);
1728
+ return w.some((x) => TOPIC_WORDS.has(x)) || w.join(" ").includes("routing key");
1729
+ };
1730
+ var BUILD_KEY_WORDS = new Set("scm git gitlab github bitbucket repository repositories repo registry mirror mirrors nexus artifactory snapshot snapshots distribution maven npm nuget pypi sonar sonarqube jenkins ci".split(" "));
1731
+ var BUILD_BLOCK_TAG = /<(\/?)(repositories|repository|pluginRepositories|pluginRepository|snapshotRepository|distributionManagement|scm|mirrors|mirror|site)\b[^>]*?(\/?)>/gi;
1732
+ var BUILD_BLOCK_OPEN = /\b(repositories|publishing|pluginManagement|dependencyResolutionManagement)\s*\{/;
1733
+ var keysBefore = (prefix) => [
1734
+ ...[...prefix.matchAll(/<([A-Za-z][\w:.-]*)/g)].map((m) => m[1]),
1735
+ ...[...prefix.matchAll(/["']?([A-Za-z][\w.-]*)["']?\s*[:=]/g)].map((m) => m[1]),
1736
+ ...[...prefix.matchAll(/\b([A-Za-z]\w*)\s*\{/g)].map((m) => m[1])
1737
+ ];
1738
+ var isBuildUrl = (u, prefix) => /\.git\/?$/i.test(u) || /\bscm:\w+:$/i.test(prefix) || keysBefore(prefix).some((k) => wordsOf2(k).some((w) => BUILD_KEY_WORDS.has(w)));
1739
+ var HEADER_ARG = /header\w*\s*(?:\.\s*\w+\s*)?\(\s*(?:(?:value|name)\s*=\s*)?$/i;
1740
+ var isHeaderName = (s, prefix) => /^x-/i.test(s) || HEADER_ARG.test(prefix.replace(/["'`]$/, ""));
1741
+ var cleanUrl = (u) => u.replace(/[.:]+$/, "").replace(/^([a-z0-9+.-]+:\/\/)[^/@\s]+@/i, "$1<redacted>@");
1742
+ var hostOf = (u) => /^[a-z0-9+.-]+:\/\/([^/:?#]+)/i.exec(u)?.[1] ?? "";
1743
+ function pathOf(s) {
1744
+ const m = PATH_RE.exec(s);
1745
+ if (!m)
1746
+ return void 0;
1747
+ const p = m[1];
1748
+ if (p.length < 2 || p.includes("//") || !/[A-Za-z]/.test(p))
1749
+ return void 0;
1750
+ if (FS_ROOT.test(p) || FILE_EXT.test(p))
1751
+ return void 0;
1752
+ return p;
1753
+ }
1754
+ function topicOf(s, context) {
1755
+ if (DOTTED_TOPIC.test(s)) {
1756
+ const parts = s.split(".");
1757
+ if (!NOT_TOPIC_LAST.has(parts.at(-1)) && !NOT_TOPIC_FIRST.has(parts[0]))
1758
+ return s;
1759
+ }
1760
+ if (context && SEPARATED_NAME.test(s) && !FILE_EXT.test(s)) {
1761
+ const last = s.split(".").at(-1);
1762
+ if (!s.includes(".") || !NOT_TOPIC_LAST.has(last))
1763
+ return s;
1764
+ }
1765
+ return void 0;
1766
+ }
1767
+ function codeOf(raw, state) {
1768
+ let s = raw;
1769
+ let out = "";
1770
+ for (; ; ) {
1771
+ if (state.block) {
1772
+ const close = state.block === "c" ? "*/" : "-->";
1773
+ const i = s.indexOf(close);
1774
+ if (i < 0)
1775
+ return out;
1776
+ s = s.slice(i + close.length);
1777
+ state.block = void 0;
1778
+ }
1779
+ const m = /(^|\s)(\/\*|<!--)/.exec(s);
1780
+ if (!m)
1781
+ return out + s;
1782
+ out += s.slice(0, m.index + m[1].length);
1783
+ s = s.slice(m.index + m[0].length);
1784
+ state.block = m[2] === "/*" ? "c" : "xml";
1785
+ }
1786
+ }
1787
+ function candidatesIn(path, lines) {
1788
+ const out = [];
1789
+ const state = {};
1790
+ let xmlDepth = 0;
1791
+ let braceDepth = 0;
1792
+ lines.forEach((raw, i) => {
1793
+ const wasBlock = state.block !== void 0;
1794
+ let code2 = codeOf(raw, state);
1795
+ const inBuild = xmlDepth > 0 || braceDepth > 0;
1796
+ for (const m of code2.matchAll(BUILD_BLOCK_TAG))
1797
+ if (!m[3])
1798
+ xmlDepth = Math.max(0, xmlDepth + (m[1] ? -1 : 1));
1799
+ if (braceDepth > 0 || BUILD_BLOCK_OPEN.test(code2)) {
1800
+ const from = braceDepth > 0 ? code2 : code2.slice(BUILD_BLOCK_OPEN.exec(code2).index);
1801
+ braceDepth = Math.max(0, braceDepth + (from.match(/\{/g)?.length ?? 0) - (from.match(/\}/g)?.length ?? 0));
1802
+ }
1803
+ if (!code2.trim())
1262
1804
  return;
1263
- const line = i + 1;
1264
- let json2;
1265
- try {
1266
- json2 = JSON.parse(raw);
1267
- } catch (e) {
1268
- bad.push({ line, raw, message: `line ${line} is not JSON: ${e.message}` });
1805
+ if (!wasBlock && isCommentLine(raw))
1806
+ return;
1807
+ if (IMPORT_LINE.test(code2) || IMPORT_CALL.test(code2) || NAMESPACE_LINE.test(code2))
1269
1808
  return;
1809
+ code2 = code2.replace(/(^|[\s;,)}])\/\/.*$/, "$1").replace(/\s#\s.*$/, "");
1810
+ const seen = /* @__PURE__ */ new Set();
1811
+ const text = redactSecrets(cleanUrlIn(raw.trim())).slice(0, 200);
1812
+ const add = (kind, value) => {
1813
+ const key = `${kind} ${value}`;
1814
+ if (seen.has(key))
1815
+ return;
1816
+ seen.add(key);
1817
+ out.push({ line: i + 1, kind, value, text });
1818
+ };
1819
+ const context = topicContext(code2);
1820
+ const urls = [];
1821
+ const opened = inBuild || xmlDepth > 0 || braceDepth > 0;
1822
+ for (const m of code2.matchAll(URL_RE)) {
1823
+ const u = cleanUrl(m[0]);
1824
+ const build = opened || isBuildUrl(u, code2.slice(0, m.index));
1825
+ if (!build && !STANDARD_HOST.test(hostOf(u)))
1826
+ add("url", u);
1827
+ urls.push(m[0]);
1270
1828
  }
1271
- const o = json2;
1272
- const issues = [];
1273
- if (!o || typeof o !== "object" || Array.isArray(o))
1274
- issues.push("not an object");
1275
- else {
1276
- if (typeof o.path !== "string" || !o.path.trim())
1277
- issues.push("path: a file or glob");
1278
- if (typeof o.why !== "string" || !o.why.trim())
1279
- issues.push("why: why it holds nothing");
1280
- const extra = Object.keys(o).filter((k) => k !== "path" && k !== "why");
1281
- if (extra.length)
1282
- issues.push(`unknown keys: ${extra.join(", ")}`);
1829
+ const strings2 = [...code2.matchAll(QUOTED)].map((m) => ({
1830
+ s: m[2],
1831
+ before: code2.slice(0, m.index + 1)
1832
+ }));
1833
+ const kv = KEY_VALUE.exec(code2);
1834
+ if (kv)
1835
+ strings2.push({ s: kv[2], before: "" });
1836
+ for (const m of code2.matchAll(ELEMENT_TEXT))
1837
+ strings2.push({ s: m[2], before: "" });
1838
+ for (const { s, before } of strings2) {
1839
+ if (urls.some((u) => s.includes(u) || u.includes(s)))
1840
+ continue;
1841
+ const p = pathOf(s);
1842
+ if (p) {
1843
+ add("path", p);
1844
+ continue;
1845
+ }
1846
+ const t = topicOf(s, context);
1847
+ if (t && !isHeaderName(t, before))
1848
+ add("topic", t);
1283
1849
  }
1284
- if (issues.length) {
1285
- bad.push({ line, raw, message: `line ${line} isn't ${SHAPE}: ${issues.join("; ")}` });
1850
+ });
1851
+ return out;
1852
+ }
1853
+ var cleanUrlIn = (line) => line.replace(/\b([a-z0-9+.-]+:\/\/)[^/@\s"'`]+@/gi, "$1<redacted>@");
1854
+ var normPath = (p) => normalizePath(p.replace(/(^|\/)%[sd](?=\/|$)/g, "$1{}"));
1855
+ function indexFacts(facts) {
1856
+ const at = /* @__PURE__ */ new Set();
1857
+ const strings2 = /* @__PURE__ */ new Set();
1858
+ const walk = (v) => {
1859
+ if (typeof v === "string") {
1860
+ if (v.length >= 3)
1861
+ strings2.add(v);
1286
1862
  return;
1287
1863
  }
1288
- entries.push({ line, path: o.path.trim(), why: o.why.trim() });
1289
- });
1290
- return { entries, bad };
1864
+ if (Array.isArray(v))
1865
+ return v.forEach(walk);
1866
+ if (v && typeof v === "object") {
1867
+ const o = v;
1868
+ if (typeof o.file === "string" && typeof o.line === "number")
1869
+ at.add(`${o.file}:${o.line}`);
1870
+ Object.values(o).forEach(walk);
1871
+ }
1872
+ };
1873
+ facts.forEach(walk);
1874
+ const paths = /* @__PURE__ */ new Set();
1875
+ for (const s of strings2) {
1876
+ if (/\s/.test(s))
1877
+ continue;
1878
+ const u = /^[a-z0-9+.-]+:\/\/[^/]+(\/.*)?$/i.exec(s);
1879
+ const p = u ? u[1] : s.includes("/") ? s : void 0;
1880
+ if (p && p.length > 1)
1881
+ paths.add(`${normPath(p)}/`);
1882
+ }
1883
+ return { at, strings: [...strings2].map((s) => s.toLowerCase()), paths: [...paths] };
1884
+ }
1885
+ function factsCover(file, c, ix) {
1886
+ if ([c.line - 1, c.line, c.line + 1].some((n) => ix.at.has(`${file}:${n}`)))
1887
+ return true;
1888
+ if (c.kind === "path") {
1889
+ const p = `${normPath(c.value)}/`;
1890
+ return ix.paths.some((f) => f.includes(p));
1891
+ }
1892
+ const v = c.value.toLowerCase().replace(/\/+$/, "");
1893
+ return ix.strings.some((s) => s.includes(v));
1894
+ }
1895
+ var skippedOf = (file, c) => ({
1896
+ cite: `${file}:${c.line}`,
1897
+ kind: c.kind,
1898
+ value: c.value,
1899
+ text: c.text
1900
+ });
1901
+ var SPEC_MARK = /^\s*["']?(openapi|swagger)["']?\s*:/m;
1902
+ var SPEC_EXT = /\.(ya?ml|json)$/i;
1903
+ function specOpsUncoveredIn(file, text, facts) {
1904
+ if (!SPEC_EXT.test(file) || !SPEC_MARK.test(text.slice(0, 4e3)))
1905
+ return void 0;
1906
+ let doc;
1907
+ try {
1908
+ doc = readOpenApiDocument(text);
1909
+ } catch {
1910
+ return void 0;
1911
+ }
1912
+ if (!doc || !doc.operations.length)
1913
+ return void 0;
1914
+ const server = (() => {
1915
+ const s = doc.servers[0];
1916
+ if (!s)
1917
+ return "";
1918
+ const p = /^[a-z0-9+.-]+:\/\/[^/]+(\/.*)?$/i.exec(s)?.[1] ?? (s.startsWith("/") ? s : "");
1919
+ return p.replace(/\/+$/, "");
1920
+ })();
1921
+ const entries = facts.filter((f) => f.kind === "entry");
1922
+ const atLines = /* @__PURE__ */ new Set();
1923
+ for (const e of entries)
1924
+ for (const k of indexFacts([e]).at)
1925
+ atLines.add(k);
1926
+ const out = [];
1927
+ for (const op of doc.operations) {
1928
+ const lines = [op.methodLine, op.line].filter((n) => n !== void 0);
1929
+ if (lines.some((n) => [n - 1, n, n + 1].some((x) => atLines.has(`${file}:${x}`))))
1930
+ continue;
1931
+ const method = op.method.toUpperCase();
1932
+ const own = normPath(op.path);
1933
+ const full = normPath(`${server}${op.path}`);
1934
+ const covered = entries.some((e) => {
1935
+ if (e.method && e.method.toUpperCase() !== method)
1936
+ return false;
1937
+ const p = normPath(e.path);
1938
+ return p === own || p === full || p.endsWith(own) || p.split("/").length > 2 && own.endsWith(p);
1939
+ });
1940
+ if (covered)
1941
+ continue;
1942
+ const opId = op.node?.get?.("operationId");
1943
+ const line = op.methodLine ?? op.line ?? 1;
1944
+ out.push({
1945
+ spec: file,
1946
+ method,
1947
+ path: op.path,
1948
+ ...typeof opId === "string" ? { operationId: opId } : {},
1949
+ cite: `${file}:${line}`,
1950
+ message: `${file}:${line} declares ${method} ${op.path}${typeof opId === "string" ? ` (${opId})` : ""}, which no entry covers`
1951
+ });
1952
+ }
1953
+ return out;
1954
+ }
1955
+ var REVIEWED_HOLDS_SHOWN = 5;
1956
+ function reviewedHoldsOf(r, skipped) {
1957
+ const n = skipped.length;
1958
+ const first = skipped.slice(0, 3).map((s) => `${s.cite} (${s.value})`);
1959
+ return {
1960
+ path: r.path,
1961
+ why: r.why,
1962
+ count: n,
1963
+ skipped: skipped.slice(0, REVIEWED_HOLDS_SHOWN),
1964
+ message: `${r.line !== void 0 ? `line ${r.line}: ` : ""}${r.path} is reviewed as holding nothing to record, but ${n} ${n === 1 ? "line in it writes" : "lines in it write"} a path, URL or topic: ${first.join(", ")}${n > 3 ? ` and ${n - 3} more` : ""}`
1965
+ };
1291
1966
  }
1292
- var isGlob = (path) => /[*?]/.test(path);
1293
1967
 
1294
1968
  // packages/verify/dist/verify.js
1295
1969
  var FOLLOWED_TYPES = ["event-topic", "mq-queue", "rest", "grpc"];
1296
- function entriesNoFlowFollows(facts) {
1970
+ function entriesNoFlowFollows(facts, rules = DEFAULT_READ_RULES) {
1297
1971
  const started = new Set(facts.flatMap((f) => f.kind === "flow" ? [f.entry] : []));
1298
- return facts.flatMap((f) => f.kind === "entry" && FOLLOWED_TYPES.includes(f.entryType) && !started.has(f.id) && // An Apigee route forwards; its forwarding call is its flow (ADR 0124 addendum A-gateway).
1299
- !APIGEE_BUNDLE.test(f.cite.file) ? [
1300
- {
1301
- id: f.id,
1302
- entryType: f.entryType,
1303
- ...f.method ? { method: f.method } : {},
1304
- path: f.path,
1305
- cite: `${f.cite.file}:${f.cite.line}`
1306
- }
1307
- ] : []).sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
1972
+ const forwarded = new Set(facts.flatMap((f) => f.kind === "call" && f.from ? [f.from] : []));
1973
+ return facts.flatMap((f) => {
1974
+ if (f.kind !== "entry" || !FOLLOWED_TYPES.includes(f.entryType))
1975
+ return [];
1976
+ const route = rules.gatewayBundle.test(f.cite.file);
1977
+ if (route ? forwarded.has(f.id) : started.has(f.id))
1978
+ return [];
1979
+ return [
1980
+ {
1981
+ id: f.id,
1982
+ entryType: f.entryType,
1983
+ ...f.method ? { method: f.method } : {},
1984
+ path: f.path,
1985
+ cite: `${f.cite.file}:${f.cite.line}`,
1986
+ ...route ? { route: true } : {}
1987
+ }
1988
+ ];
1989
+ }).sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
1308
1990
  }
1309
1991
  var LINES_HELD = 64;
1310
1992
  var SOURCE_EXT = /\.(java|kt|kts|scala|groovy|cs|vb|fs|ts|tsx|js|jsx|mjs|cjs|py|rb|go|rs|php|swift|m|mm|c|cc|cpp|h|hpp|sql|graphql|gql|proto|ya?ml|json|xml|properties|toml|ini|conf|tf|tfvars|hcl|bicep|sh|ps1|bat|cmd|gradle|html?|vue|svelte|cshtml|razor|aspx|jsp|ftl|hbs|cob|cbl|cpy|jcl|avsc|wsdl|xsd|csproj|sln|config)$/i;
1311
1993
  var SOURCE_NAME = /(^|\/)(Dockerfile[^/]*|Makefile|Jenkinsfile|Procfile|Containerfile)$/;
1312
1994
  var LOCKFILE = /(^|\/)(package-lock\.json|npm-shrinkwrap\.json|yarn\.lock|pnpm-lock\.yaml|composer\.lock|Gemfile\.lock|Cargo\.lock|poetry\.lock|go\.sum|packages\.lock\.json)$/;
1313
1995
  var NOT_PRODUCT = [
1314
- /(^|\/)(src\/test|src\/it|test|tests|__tests__|__mocks__|spec|specs|e2e|testdata|test-data|fixtures)\//i,
1315
1996
  /(\.|_|-)(test|spec|tests|specs)\.[a-z0-9]+$/i,
1316
1997
  /(Test|Tests|IT|Spec)\.(java|kt|scala|groovy|cs|vb)$/,
1317
1998
  /(^|\/)test_[^/]+\.py$/,
@@ -1320,19 +2001,30 @@ var NOT_PRODUCT = [
1320
2001
  /(^|\/)messages(_[A-Za-z_]+)?\.properties$/,
1321
2002
  /\.(resx|po|pot)$/i
1322
2003
  ];
1323
- function isSourceFile(path, firstLine = "", lines) {
2004
+ function isSourceFile(path, firstLine = "", lines, rules = DEFAULT_READ_RULES) {
1324
2005
  if (isSecretPath(path) || LOCKFILE.test(path))
1325
2006
  return false;
1326
- if (NOT_PRODUCT.some((re) => re.test(path)) || isLocaleBundle(path))
2007
+ if (rules.notProduct.test(path) || NOT_PRODUCT.some((re) => re.test(path)))
1327
2008
  return false;
1328
- if (lines && isDataFile(path, lines))
2009
+ if (isLocaleBundle(path))
2010
+ return false;
2011
+ if (lines && isDataFile(path, lines, rules))
1329
2012
  return false;
1330
2013
  if (quoteNotRequired(path, firstLine))
1331
2014
  return false;
1332
2015
  return SOURCE_EXT.test(path) || SOURCE_NAME.test(path);
1333
2016
  }
1334
2017
  var lastPart = (s) => s.split(".").pop() ?? s;
1335
- var isCommentLine = (l) => /^\s*(\/\/|\/\*|\*|<!--|-- |#(?!\s*(region|endregion|if|elif|else|endif|define|undef|pragma|nullable|include|import)\b)(?!!))/.test(l);
2018
+ var TEMPLATED = /\{\{|\}\}|\$\{/;
2019
+ function pathIsAName(path) {
2020
+ const p = (path ?? "").trim().replace(/\(\s*[^)]*\)$/, "");
2021
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(p) && /[a-z][A-Z]|_|\$/.test(p);
2022
+ }
2023
+ var serviceAddress = (f) => f.host !== void 0 && /^service$/i.test(f.resourceType ?? "") ? clusterServiceAddress(f.host) : void 0;
2024
+ var repoModule = (exposes) => {
2025
+ const m = /^([^/]+)\/([^/]+)$/.exec(exposes.trim());
2026
+ return m ? { repo: m[1], module: m[2] } : void 0;
2027
+ };
1336
2028
  var routeOrWord = (v) => v.includes("/") ? { kind: "route", value: v } : { kind: "word", value: v };
1337
2029
  function claimsOf(f) {
1338
2030
  switch (f.kind) {
@@ -1368,6 +2060,12 @@ function claimsOf(f) {
1368
2060
  }
1369
2061
  case "flow":
1370
2062
  return [routeOrWord(f.cite.value)];
2063
+ // ADR 0163: the value as written, on its line.
2064
+ case "term":
2065
+ return [{ kind: "word", value: f.value }];
2066
+ // ADR 0180: the signal the type rests on, as written on its line.
2067
+ case "component":
2068
+ return [{ kind: "word", value: f.cite.value }];
1371
2069
  }
1372
2070
  }
1373
2071
  var sameCommit = (a, b) => a.startsWith(b) || b.startsWith(a);
@@ -1403,6 +2101,7 @@ function refsOf(f) {
1403
2101
  return out;
1404
2102
  }
1405
2103
  async function verifyFacts(o) {
2104
+ const rules = readRulesOf(o.conventions);
1406
2105
  const carriedFrom = /* @__PURE__ */ new Map();
1407
2106
  const stale = /* @__PURE__ */ new Map();
1408
2107
  const corrected = applyCorrections(o.facts, o.corrections ?? []);
@@ -1504,7 +2203,7 @@ async function verifyFacts(o) {
1504
2203
  ].flatMap((at) => derivedNamesAt(at.file, linesOf2(at.file) ?? [], at.line, deriveEnv));
1505
2204
  const holds = (c) => claimOnLines(lines, cite2.line, c) || // a framework that routes by file place (Razor Pages, WebForms, Next.js)
1506
2205
  c.kind === "route" && pageRouteMatches(cite2.file, lines, (p) => linesOf2(p) !== void 0, c.value) || parts.length > 0 && (c.kind === "route" ? partsNamePath([...near(lines, cite2.line), ...partLines], c.value) : partLines.some((l) => lineNamesClaim(l, c)) || // a word put together from parts (`namespace:task`, a key a helper prefixes)
1507
- c.kind === "word" && partsBuildWord(c.value, parts.map((p) => p.value), near(lines, cite2.line))) || derivedHolds(c, derivedHere(), partLines, partsNamePath);
2206
+ c.kind === "word" && partsBuildWord(c.value, parts.map((p) => p.value), near(lines, cite2.line))) || o.derivedNames !== false && derivedHolds(c, derivedHere(), partLines, partsNamePath);
1508
2207
  if (claims && !claims.some(holds))
1509
2208
  return {
1510
2209
  code: "claim-not-on-line",
@@ -1541,6 +2240,9 @@ async function verifyFacts(o) {
1541
2240
  return value.includes("/") ? [routeOrWord(value), { kind: "word", value }] : [routeOrWord(value)];
1542
2241
  return NEVER_CHECKED_STEPS.includes(kind) ? void 0 : [routeOrWord(value)];
1543
2242
  };
2243
+ const estate = o.estate ? new Set(o.estate.components.map(factIdPart)) : void 0;
2244
+ const serves2 = facts.some((f) => f.kind === "entry");
2245
+ const repoComponents = new Map(Object.entries(o.estate?.repoComponents ?? {}).map(([r, cs]) => [factIdPart(r), new Set(cs.map(factIdPart))]));
1544
2246
  for (const f of facts) {
1545
2247
  const label = `${f.kind} ${f.id}`;
1546
2248
  if (o.commit && !sameCommit(f.commit, o.commit)) {
@@ -1566,8 +2268,80 @@ async function verifyFacts(o) {
1566
2268
  (f.scopes ?? []).forEach((s, i) => add(f, checkCite(s, [{ kind: "word", value: s.value }], `${label} scope ${i + 1} ("${s.value}")`)));
1567
2269
  if (f.kind === "entry")
1568
2270
  (f.callers ?? []).forEach((c, i) => add(f, checkCite(c, [{ kind: "word", value: c.value }], `${label} caller ${i + 1} (${c.as} "${c.value}")`)));
2271
+ if (f.kind === "call" && (f.callType === "rest" || f.callType === "soap") && pathIsAName(f.path))
2272
+ problem(f, "call-path-is-a-name", `${label} path "${f.path}" is the name of what holds the path, not the path: follow it to where it is set and write the literal path, or the setting's placeholder with its whole key (\`\${forge.uris.refund}\`) with the line that sets it in cite.with, and record the setting as a config fact`);
2273
+ if (f.kind === "deploy" && (f.host !== void 0 || f.basePath !== void 0)) {
2274
+ if (f.host !== void 0 && TEMPLATED.test(f.host))
2275
+ problem(f, "exposure-template", `${label} host "${f.host}" is a template: give the host the values file sets, with that line in cite.with (one fact per environment's values file, each with its env)`);
2276
+ else if (f.host !== void 0 && serviceAddress(f) !== void 0) {
2277
+ const a = serviceAddress(f);
2278
+ const parts = [
2279
+ ["name", a.name],
2280
+ ["namespace", a.namespace],
2281
+ ...a.port !== void 0 ? [["port", String(a.port)]] : []
2282
+ ];
2283
+ for (const [what, value] of parts)
2284
+ add(f, checkCite(f.cite, [{ kind: "word", value }], `${label} host's ${what}`, false));
2285
+ } else if (f.host !== void 0)
2286
+ add(f, checkCite(f.cite, [{ kind: "word", value: f.host }], `${label} host`, false));
2287
+ if (f.basePath !== void 0 && isPatternPath(f.basePath))
2288
+ problem(f, "exposure-regex", `${label} basePath "${f.basePath}" is a regular expression: write its plain prefix, "${plainPathPrefix(f.basePath)}" (the segments before the first pattern), and keep the line that writes the expression cited`);
2289
+ else if (f.basePath !== void 0)
2290
+ add(f, checkCite(f.cite, [routeOrWord(f.basePath), { kind: "word", value: f.basePath }], `${label} basePath`, false));
2291
+ const inModule = f.exposes !== void 0 ? repoModule(f.exposes) : void 0;
2292
+ if (inModule && estate && estate.has(factIdPart(inModule.repo))) {
2293
+ const known = repoComponents.get(factIdPart(inModule.repo));
2294
+ if (known && !known.has(factIdPart(inModule.module)))
2295
+ problem(f, "exposure-component", `${label} exposes "${f.exposes}": ${inModule.repo}'s facts name no component ${inModule.module} (they name ${[...known].sort().join(", ")}); name the module as its facts do, or write a question when no file says which`);
2296
+ } else if (f.exposes !== void 0 && estate && !estate.has(factIdPart(inModule?.repo ?? f.exposes)))
2297
+ problem(f, "exposure-component", `${label} exposes "${f.exposes}", which is no component or repo of the estate (repos.manifest.yaml): name the component the route reaches, or write a question when no file says which`);
2298
+ if (f.exposes === void 0 && !serves2)
2299
+ problem(f, "exposure-component", `${label} says where a component is reached and names none: this repo's facts serve nothing, so its routes reach another repo's component; name it in exposes (one of the estate's components), or write a question when no file says which`);
2300
+ }
2301
+ if (f.kind === "call") {
2302
+ for (const [what, claim, at] of [
2303
+ ["auth", f.auth, f.authCite],
2304
+ ["tls", f.tls, f.tlsCite]
2305
+ ]) {
2306
+ if (claim !== void 0 && !at && f.by !== "person")
2307
+ problem(f, "security-uncited", `${label} claims ${what} ${claim} and cites no line for it: give ${what}Cite, the line that sets it (a client certificate, a token, an https URL) and what it writes there, or leave ${what} out`);
2308
+ if (!at)
2309
+ continue;
2310
+ const bad = checkCite({ ...at }, [{ kind: "word", value: at.value }], `${label} ${what}Cite${claim ? ` (${what} ${claim})` : ""}`, false);
2311
+ add(f, bad);
2312
+ if (bad || claim === void 0)
2313
+ continue;
2314
+ const lines = [at, ...at.with ?? []].flatMap((p) => {
2315
+ const l = lineOfValue(linesOf2(p.file), p.line, p.value);
2316
+ return l === void 0 ? [] : [l];
2317
+ });
2318
+ const needs = securityLineKind(what, claim, lines);
2319
+ if (needs)
2320
+ problem(f, "security-line-kind", `${label} claims ${what} ${claim} at ${at.file}:${at.line}${at.with?.length ? " and its parts" : ""}, which write${at.with?.length ? "" : "s"} no ${needs}: cite the line that sets it (with the line that builds it in ${what}Cite.with), or write the ${what} the line sets`);
2321
+ }
2322
+ }
2323
+ if (f.kind === "term") {
2324
+ const m = f.meaning;
2325
+ if (m && m.file !== f.cite.file)
2326
+ problem(f, "term-meaning", `${label} says ${m.file}:${m.line} gives "${f.value}" its meaning, and the value is in ${f.cite.file}: cite the line of the same file (the list's or the block's key) that does`);
2327
+ else if (m && m.line > f.cite.line)
2328
+ problem(f, "term-meaning", `${label} says line ${m.line} gives "${f.value}" its meaning, below the value's line ${f.cite.line}: cite the line above it (the list's or the block's key) that does`);
2329
+ else if (m)
2330
+ add(f, checkCite({ ...m }, [{ kind: "word", value: m.value }], `${label} meaning`, false));
2331
+ const bad = f.note !== void 0 && o.notes ? noteProblem(f.note, o.notes) : void 0;
2332
+ if (bad)
2333
+ problem(f, bad.code, `${label} ${bad.message}`);
2334
+ }
2335
+ if (f.kind === "component") {
2336
+ if (o.componentTypes && !o.componentTypes.has(f.type))
2337
+ problem(f, "component-type", `${label} says the component is a ${f.type}, which isn't one of the organisation's component types: add it (org-pack/component-types/${f.type}/type.yaml) or say one of ${[...o.componentTypes].join(", ")}`);
2338
+ const subject = factIdPart(f.component ?? f.repo);
2339
+ const first = facts.find((x) => x.kind === "component" && factIdPart(x.component ?? x.repo) === subject && (x === f || !found.has(x.id)));
2340
+ if (first && first !== f)
2341
+ problem(f, "component-type", `${label} says what ${subject} is a second time (${first.id} already does): one fact says what a component is`);
2342
+ }
1569
2343
  if (f.kind === "framework") {
1570
- const bad = frameworkNameProblem(f);
2344
+ const bad = frameworkNameProblem(f, rules.frameworks);
1571
2345
  if (bad)
1572
2346
  problem(f, "framework-name", `${label} ${bad}`);
1573
2347
  if (f.version !== void 0)
@@ -1661,23 +2435,29 @@ async function verifyFacts(o) {
1661
2435
  return { ...c, quote: q };
1662
2436
  };
1663
2437
  const authUncited = [];
2438
+ const methodUnseen = [];
2439
+ const kept = new Map(facts.filter((f) => !rejected.has(f.id)).map((f) => [f.id, f]));
2440
+ const factById = (id) => kept.get(id);
1664
2441
  const accepted = facts.filter((f) => !rejected.has(f.id)).map((f) => {
1665
2442
  o.room?.();
1666
2443
  const verified = f.by === "person" && f.verified !== "none" ? f.verified : "line";
1667
- const base = { ...f, verified, cite: fixQuote(f.cite) };
1668
- if (base.kind === "entry" && base.auth !== void 0 && !base.authCite && f.by !== "person")
2444
+ const base2 = { ...f, verified, cite: fixQuote(f.cite) };
2445
+ if (base2.kind === "entry" && base2.auth !== void 0 && !base2.authCite && f.by !== "person")
1669
2446
  authUncited.push({
1670
- id: base.id,
1671
- auth: base.auth,
1672
- cite: `${base.cite.file}:${base.cite.line}`
2447
+ id: base2.id,
2448
+ auth: base2.auth,
2449
+ cite: `${base2.cite.file}:${base2.cite.line}`
1673
2450
  });
1674
- if (base.kind === "flow")
2451
+ const unseen = methodUnseenOf(base2, linesOf2, factById);
2452
+ if (unseen)
2453
+ methodUnseen.push(unseen);
2454
+ if (base2.kind === "flow")
1675
2455
  return {
1676
- ...base,
1677
- steps: base.steps.map((s) => ({ ...s, cite: fixQuote(s.cite) })),
1678
- errors: base.errors.map((e) => ({ ...e, cite: fixQuote(e.cite) }))
2456
+ ...base2,
2457
+ steps: base2.steps.map((s) => ({ ...s, cite: fixQuote(s.cite) })),
2458
+ errors: base2.errors.map((e) => ({ ...e, cite: fixQuote(e.cite) }))
1679
2459
  };
1680
- return base;
2460
+ return base2;
1681
2461
  });
1682
2462
  const excludeRes = (o.exclude ?? []).map(globToRegExp);
1683
2463
  const sources = [];
@@ -1685,7 +2465,7 @@ async function verifyFacts(o) {
1685
2465
  for (const p of o.source.list()) {
1686
2466
  o.room?.();
1687
2467
  const ls = readLines(p) ?? [""];
1688
- if (!isSourceFile(p, ls[0] ?? "", ls))
2468
+ if (!isSourceFile(p, ls[0] ?? "", ls, rules))
1689
2469
  continue;
1690
2470
  if (excludeRes.some((re) => re.test(p)))
1691
2471
  excluded.push(p);
@@ -1701,6 +2481,8 @@ async function verifyFacts(o) {
1701
2481
  const reviewedSet = /* @__PURE__ */ new Set();
1702
2482
  const reviewed = [];
1703
2483
  const reviewedProblems = [];
2484
+ const reviewedDropped = [];
2485
+ const reviewedGlobs = [];
1704
2486
  for (const r of o.reviewed ?? []) {
1705
2487
  const refuse = (message) => reviewedProblems.push({
1706
2488
  ...r.line !== void 0 ? { line: r.line } : {},
@@ -1716,36 +2498,105 @@ async function verifyFacts(o) {
1716
2498
  refuse("is a secret's file, which is never read into a map");
1717
2499
  continue;
1718
2500
  }
1719
- const re = isGlob(r.path) ? globToRegExp(r.path) : void 0;
1720
- const matched = re ? allFiles.filter((p) => re.test(p)) : allFiles.filter((p) => p === r.path);
2501
+ const range = lineRangeOf(r.path);
2502
+ if (range) {
2503
+ const text = allFiles.includes(range.file) ? o.source.read(range.file) : void 0;
2504
+ if (text === void 0) {
2505
+ refuse("is not in the repo at this commit; name a file you read");
2506
+ continue;
2507
+ }
2508
+ const last = text.split(/\r?\n/).length;
2509
+ if (range.from < 1 || range.from > range.to || range.to > last) {
2510
+ refuse(`is past the file's last line (${last}) or out of order; name the lines you read`);
2511
+ continue;
2512
+ }
2513
+ }
2514
+ const target = range ? range.file : r.path;
2515
+ const re = !range && isGlob(r.path) ? globToRegExp(r.path) : void 0;
2516
+ const matched = re ? allFiles.filter((p) => re.test(p)) : allFiles.filter((p) => p === target);
1721
2517
  if (!matched.length) {
1722
- refuse(re ? "matches no file in the repo at this commit; name the files you read" : "is not in the repo at this commit; name a file you read");
2518
+ if (re)
2519
+ reviewedDropped.push({
2520
+ ...r.line !== void 0 ? { line: r.line } : {},
2521
+ path: r.path,
2522
+ why: r.why,
2523
+ message: `${r.line !== void 0 ? `line ${r.line}: ` : ""}${r.path} matches no file in the repo at this commit; dropped (it stands for no file)`
2524
+ });
2525
+ else
2526
+ refuse("is not in the repo at this commit; name a file you read");
2527
+ continue;
2528
+ }
2529
+ const entryPoints = re ? matched.filter((p) => isSource.has(p) && !touched.has(p) && isEntryPointFile(p)) : [];
2530
+ if (entryPoints.length) {
2531
+ refuse(`covers ${entryPoints[0]}, an entry point (a controller, route, handler or listener)${entryPoints.length > 1 ? ` and ${entryPoints.length - 1} more` : ""} no fact cites: read it, and write its entries and the fields of the classes it takes and returns; never mark an entry point reviewed with a glob (name it alone if it truly holds nothing)`);
1723
2532
  continue;
1724
2533
  }
1725
2534
  const files = matched.filter((p) => isSource.has(p) && !touched.has(p)).sort();
1726
2535
  for (const p of files)
1727
2536
  reviewedSet.add(p);
1728
2537
  reviewed.push({ path: r.path, why: r.why, files });
2538
+ if (re)
2539
+ reviewedGlobs.push({ entry: r, files });
1729
2540
  }
1730
2541
  const read = sources.filter((p) => touched.has(p)).sort();
1731
2542
  const reviewedFiles = sources.filter((p) => reviewedSet.has(p)).sort();
1732
- const unknown = sources.filter((p) => !touched.has(p) && !reviewedSet.has(p)).sort();
2543
+ const leftOut = new Set(o.notReached ?? []);
2544
+ const notReached = sources.filter((p) => leftOut.has(p) && !touched.has(p) && !reviewedSet.has(p)).sort();
2545
+ const unknown = sources.filter((p) => !touched.has(p) && !reviewedSet.has(p) && !leftOut.has(p)).sort();
1733
2546
  const total = read.length + reviewedFiles.length + unknown.length;
1734
2547
  const ratio = total === 0 ? 1 : Math.round(read.length / total * 1e3) / 1e3;
2548
+ const ix = indexFacts(accepted);
2549
+ const specOpsUncovered = [];
2550
+ const specs = /* @__PURE__ */ new Set();
2551
+ for (const p of sources) {
2552
+ if (notReadForSkipped(p, rules))
2553
+ continue;
2554
+ o.room?.();
2555
+ const text = o.readers !== false && /\.(ya?ml|json)$/i.test(p) ? o.source.read(p) : void 0;
2556
+ const ops = text === void 0 ? void 0 : specOpsUncoveredIn(p, text, accepted);
2557
+ if (!ops)
2558
+ continue;
2559
+ specs.add(p);
2560
+ specOpsUncovered.push(...ops);
2561
+ }
2562
+ const skippedIn = (p) => {
2563
+ if (specs.has(p) || notReadForSkipped(p, rules))
2564
+ return [];
2565
+ o.room?.();
2566
+ const ls = readLines(p);
2567
+ return ls ? candidatesIn(p, ls).flatMap((c) => factsCover(p, c, ix) ? [] : [skippedOf(p, c)]) : [];
2568
+ };
2569
+ const skipped = read.flatMap(skippedIn);
2570
+ const reviewedHolds = reviewedGlobs.flatMap(({ entry, files }) => {
2571
+ const held = files.flatMap(skippedIn);
2572
+ return held.length ? [reviewedHoldsOf(entry, held)] : [];
2573
+ });
1735
2574
  return {
1736
2575
  accepted,
1737
2576
  // a rejected fact goes back as it was read, at its own commit, even one that was carried
1738
2577
  rejected: [...rejected.values()].map((r) => carriedFrom.has(r.fact.id) ? { ...r, fact: { ...r.fact, commit: carriedFrom.get(r.fact.id) } } : r).sort((a, b) => a.fact.id < b.fact.id ? -1 : a.fact.id > b.fact.id ? 1 : 0),
1739
- coverage: { read, reviewed: reviewedFiles, unknown, excluded: excluded.sort(), ratio },
2578
+ coverage: {
2579
+ read,
2580
+ reviewed: reviewedFiles,
2581
+ unknown,
2582
+ excluded: excluded.sort(),
2583
+ notReached,
2584
+ ratio
2585
+ },
1740
2586
  redacted,
1741
2587
  quotesDropped,
1742
2588
  carried: accepted.flatMap((f) => carriedFrom.has(f.id) ? [{ id: f.id, from: carriedFrom.get(f.id) }] : []),
1743
2589
  corrections: { applied: corrected.applied, stale: corrected.stale },
1744
- entriesNoFlowFollows: entriesNoFlowFollows(accepted),
2590
+ entriesNoFlowFollows: entriesNoFlowFollows(accepted, rules),
1745
2591
  reviewed,
1746
2592
  reviewedProblems,
2593
+ reviewedDropped,
1747
2594
  authUncited: authUncited.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0),
1748
- frameworkGaps: frameworkGaps(accepted)
2595
+ methodUnseen: methodUnseen.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0),
2596
+ skipped,
2597
+ reviewedHolds,
2598
+ specOpsUncovered,
2599
+ frameworkGaps: frameworkGaps(accepted, rules.frameworks)
1749
2600
  };
1750
2601
  }
1751
2602
 
@@ -1758,11 +2609,11 @@ var ENTRY_DIR = /(^|\/)(controllers?|routes|routers|handlers|api|endpoints|jobs|
1758
2609
  var BOUNDARY_NAME = /(client|gateway|repository|repo|dao|mapper|entity|model|dto|request|response|publisher|producer|store|cache|schema)$/i;
1759
2610
  var BOUNDARY_FILE = /\.(sql|proto|graphql|gql|avsc|wsdl|xsd)$|(^|\/)(openapi|swagger)[^/]*\.(ya?ml|json)$/i;
1760
2611
  var BOUNDARY_DIR = /(^|\/)(clients?|gateways?|repositories|dao|models?|entities|dto|migrations?)\//i;
1761
- var TEST = /(^|\/)(test|tests|__tests__|spec|specs|e2e|it)\/|[._-](test|spec)\.[^/.]+$|(Test|Tests|IT|Spec)\.[^/.]+$|(^|\/)test_[^/]+$|_test\.go$/;
1762
- var stem = (path) => {
1763
- const base = path.slice(path.lastIndexOf("/") + 1);
1764
- const dot = base.indexOf(".");
1765
- return dot > 0 ? base.slice(0, dot) : base;
2612
+ var TEST = /(^|\/)(test|tests|__tests__|spec|specs|e2e|it)\/|(^|\/)(test|tests)[-_][a-z0-9_-]+\/|[._-](test|spec)\.[^/.]+$|(Test|Tests|IT|Spec)\.[^/.]+$|(^|\/)test_[^/]+$|_test\.go$/;
2613
+ var stem2 = (path) => {
2614
+ const base2 = path.slice(path.lastIndexOf("/") + 1);
2615
+ const dot = base2.indexOf(".");
2616
+ return dot > 0 ? base2.slice(0, dot) : base2;
1766
2617
  };
1767
2618
  function readingRank(path) {
1768
2619
  if (TEST.test(path))
@@ -1771,7 +2622,7 @@ function readingRank(path) {
1771
2622
  return 0;
1772
2623
  if (CONFIG.test(path))
1773
2624
  return 1;
1774
- const s = stem(path);
2625
+ const s = stem2(path);
1775
2626
  if (ENTRY_NAME.test(s) || ENTRY_DIR.test(path))
1776
2627
  return 2;
1777
2628
  if (BOUNDARY_NAME.test(s) || BOUNDARY_FILE.test(path) || BOUNDARY_DIR.test(path))
@@ -1819,9 +2670,9 @@ var CONFIG_CODE = /(^|\/)(config|configuration|settings|environments?)(\/|\.[^/]
1819
2670
  var IAC = /\.(tf|tfvars|hcl|bicep)$|(^|\/)(serverless|template)[^/]*\.ya?ml$|(^|\/)(cdk|pulumi|terraform)\//;
1820
2671
  var DATA_FILE = /\.sql$|(^|\/)(migrations?|db|database|changelogs?|liquibase|flyway)\//i;
1821
2672
  var CONTRACT = /\.(proto|avsc|wsdl|xsd)$/i;
1822
- var API_SCHEMA = /\.(graphql|gql)$|(^|\/)(openapi|swagger|asyncapi)[^/]*\.(ya?ml|json)$/i;
2673
+ var API_SCHEMA = /\.(graphql|gql)$/i;
1823
2674
  var STRUCTURED = /\.(ya?ml|json|xml|config|properties|toml|ini|conf)$/i;
1824
- function fileKinds(path) {
2675
+ function fileKinds(path, rules = DEFAULT_READ_RULES) {
1825
2676
  if (GATEWAY_FILE.test(path))
1826
2677
  return [...GATEWAY_KINDS];
1827
2678
  const k = /* @__PURE__ */ new Set();
@@ -1837,7 +2688,7 @@ function fileKinds(path) {
1837
2688
  add(["data"]);
1838
2689
  if (CONTRACT.test(path))
1839
2690
  add(["protocols"]);
1840
- if (API_SCHEMA.test(path))
2691
+ if (API_SCHEMA.test(path) || rules.apiSpec.test(path))
1841
2692
  add(["routes", "protocols"]);
1842
2693
  const isCode = SERVER_CODE.test(path) || DEVICE_CODE.test(path) || SCRIPT_CODE.test(path);
1843
2694
  if (PAGE_CODE.test(path) || DEVICE_CODE.test(path))
@@ -1884,8 +2735,11 @@ ${numberedSlice(source.read(s.path) ?? "", s)}`;
1884
2735
  var tokensOf = (chars) => Math.ceil(chars / 4);
1885
2736
  function planBatches(source, o) {
1886
2737
  const room = Math.max(1, o.room);
2738
+ const rules = readRulesOf(o.conventions);
1887
2739
  const excludeRes = (o.exclude ?? []).map(globToRegExp);
1888
2740
  const excluded = [];
2741
+ const leaveOut = new Set(o.reach?.leaveOut ?? []);
2742
+ const notReached = [];
1889
2743
  const paths = [];
1890
2744
  const texts = /* @__PURE__ */ new Map();
1891
2745
  for (const p of source.list()) {
@@ -1893,7 +2747,7 @@ function planBatches(source, o) {
1893
2747
  if (text === void 0)
1894
2748
  continue;
1895
2749
  const ls = linesOf(text);
1896
- if (!isSourceFile(p, ls[0] ?? "", ls))
2750
+ if (!isSourceFile(p, ls[0] ?? "", ls, rules))
1897
2751
  continue;
1898
2752
  if (excludeRes.some((re) => re.test(p))) {
1899
2753
  excluded.push(p);
@@ -1901,6 +2755,10 @@ function planBatches(source, o) {
1901
2755
  }
1902
2756
  if (!text.trim())
1903
2757
  continue;
2758
+ if (leaveOut.has(p)) {
2759
+ notReached.push(p);
2760
+ continue;
2761
+ }
1904
2762
  paths.push(p);
1905
2763
  texts.set(p, text);
1906
2764
  }
@@ -1916,8 +2774,8 @@ function planBatches(source, o) {
1916
2774
  const slices = [];
1917
2775
  let lines = 0;
1918
2776
  let leastRoom = room;
1919
- for (const path of readingOrder(paths)) {
1920
- const kinds2 = fileKinds(path);
2777
+ for (const path of reachOrder(paths, o.reach?.first)) {
2778
+ const kinds2 = fileKinds(path, rules);
1921
2779
  const left = room - overhead(kinds2);
1922
2780
  leastRoom = Math.min(leastRoom, left);
1923
2781
  const fileRoom = Math.max(1, left);
@@ -1965,9 +2823,20 @@ function planBatches(source, o) {
1965
2823
  tokens: slices.reduce((n, s) => n + s.tokens, 0),
1966
2824
  batches,
1967
2825
  excluded: excluded.sort(),
1968
- leastRoom
2826
+ leastRoom,
2827
+ notReached: notReached.sort()
1969
2828
  };
1970
2829
  }
2830
+ function reachOrder(paths, first) {
2831
+ const order = readingOrder(paths);
2832
+ if (!first?.length)
2833
+ return order;
2834
+ const has = new Set(paths);
2835
+ const head = order.filter((p) => readingRank(p) <= 1);
2836
+ const lead = [...new Set(first)].filter((p) => has.has(p) && readingRank(p) > 1);
2837
+ const taken = /* @__PURE__ */ new Set([...head, ...lead]);
2838
+ return [...head, ...lead, ...order.filter((p) => !taken.has(p))];
2839
+ }
1971
2840
 
1972
2841
  // packages/verify/dist/vendored.js
1973
2842
  var LICENSE_FILE = /^(LICEN[CS]E|COPYING)([._-][^/]*)?$/i;
@@ -2032,10 +2901,10 @@ function buildModules(path, text) {
2032
2901
  return found.filter((m) => !m.startsWith("!")).map(clean).filter(Boolean).map((m) => dir ? `${dir}/${m}` : m);
2033
2902
  }
2034
2903
  var JAVA_LIKE = /\.(java|kt|kts|scala|groovy)$/i;
2035
- var DOTNET = /\.(cs|vb|fs)$/i;
2904
+ var DOTNET2 = /\.(cs|vb|fs)$/i;
2036
2905
  var rootOf = (name) => name.split(".").slice(0, 2).join(".").toLowerCase();
2037
2906
  function packageRoot(path, text) {
2038
- const re = JAVA_LIKE.test(path) ? /^\s*package\s+([A-Za-z_][\w.]*)/m : DOTNET.test(path) ? /^\s*namespace\s+([A-Za-z_][\w.]*)/m : void 0;
2907
+ const re = JAVA_LIKE.test(path) ? /^\s*package\s+([A-Za-z_][\w.]*)/m : DOTNET2.test(path) ? /^\s*namespace\s+([A-Za-z_][\w.]*)/m : void 0;
2039
2908
  const m = re?.exec(text);
2040
2909
  return m ? rootOf(m[1]) : void 0;
2041
2910
  }
@@ -2053,7 +2922,7 @@ function packageRoots(paths, inits, read) {
2053
2922
  }
2054
2923
  if (top)
2055
2924
  roots.add(nameOf(top).toLowerCase());
2056
- } else if ((JAVA_LIKE.test(p) || DOTNET.test(p)) && readCount < PACKAGE_SAMPLE) {
2925
+ } else if ((JAVA_LIKE.test(p) || DOTNET2.test(p)) && readCount < PACKAGE_SAMPLE) {
2057
2926
  readCount++;
2058
2927
  const r = packageRoot(p, read(p) ?? "");
2059
2928
  if (r)
@@ -2233,6 +3102,27 @@ function exclusionYaml(proposals) {
2233
3102
  ].join("\n");
2234
3103
  }
2235
3104
  var SESSION_TOKENS = 15e4;
3105
+ var SESSION_SHARE = 0.45;
3106
+ function sessionTokensFor(limit) {
3107
+ return limit && limit > 0 ? Math.floor(limit * SESSION_SHARE) : SESSION_TOKENS;
3108
+ }
3109
+ var RANGE_OVERLAP_LINES = 20;
3110
+ var RANGE_OVERLAP_SHARE = 0.02;
3111
+ function rangeOverlap(each) {
3112
+ return Math.min(Math.floor(each / 5), Math.max(RANGE_OVERLAP_LINES, Math.ceil(RANGE_OVERLAP_SHARE * each)));
3113
+ }
3114
+ function lineRanges(f, room) {
3115
+ const n = Math.max(1, Math.min(f.lines, Math.ceil(f.tokens / (0.8 * room))));
3116
+ const each = Math.ceil(f.lines / n);
3117
+ const overlap = rangeOverlap(each);
3118
+ const out = [];
3119
+ for (let start = 1; start <= f.lines; start += each) {
3120
+ const from = start === 1 ? 1 : start - overlap;
3121
+ const to = Math.min(f.lines, start + each - 1);
3122
+ out.push({ from, to, start, tokens: Math.ceil(f.tokens * (to - from + 1) / f.lines) });
3123
+ }
3124
+ return out;
3125
+ }
2236
3126
  function unitsOf(dir, fs, room) {
2237
3127
  const prefix = dir ? `${dir}/` : "";
2238
3128
  const direct = [];
@@ -2254,15 +3144,105 @@ function unitsOf(dir, fs, room) {
2254
3144
  out.push(...unitsOf(child, children.get(child), room));
2255
3145
  return out;
2256
3146
  }
2257
- function planParts(files, room = SESSION_TOKENS) {
3147
+ function groupUnits(dir, fs, room, holds) {
3148
+ const prefix = dir ? `${dir}/` : "";
3149
+ const direct = [];
3150
+ const children = /* @__PURE__ */ new Map();
3151
+ for (const f of fs) {
3152
+ const rest = f.path.slice(prefix.length);
3153
+ const slash = rest.indexOf("/");
3154
+ if (slash < 0)
3155
+ direct.push(f);
3156
+ else {
3157
+ const child = prefix + rest.slice(0, slash);
3158
+ children.set(child, [...children.get(child) ?? [], f]);
3159
+ }
3160
+ }
3161
+ if (dir && holds(dir, true, fs.length) && (sum(fs) <= room || !children.size))
3162
+ return [{ glob: `${dir}/**`, files: fs.length, tokens: sum(fs) }];
3163
+ const out = [];
3164
+ if (direct.length) {
3165
+ if (holds(dir, false, direct.length))
3166
+ out.push({ glob: `${prefix}*`, files: direct.length, tokens: sum(direct) });
3167
+ else
3168
+ for (const f of [...direct].sort((a, b) => byPath2(a.path, b.path)))
3169
+ out.push({ glob: f.path, files: 1, tokens: f.tokens });
3170
+ }
3171
+ for (const child of [...children.keys()].sort(byPath2))
3172
+ out.push(...groupUnits(child, children.get(child), room, holds));
3173
+ return out;
3174
+ }
3175
+ function rangeUnits(f, room) {
3176
+ return lineRanges({ ...f, lines: f.lines }, room).map((r, i) => ({
3177
+ glob: `${f.path}#L${r.from}-L${r.to}`,
3178
+ files: i === 0 ? 1 : 0,
3179
+ tokens: r.tokens
3180
+ }));
3181
+ }
3182
+ function orderedUnits(files, room, o) {
3183
+ const isBig = (f) => f.tokens > room && (f.lines ?? 0) > 1;
3184
+ const byFile = new Map(files.map((f) => [f.path, f]));
3185
+ const head = new Set(o.head.filter((p) => byFile.has(p)));
3186
+ const models = [...new Set(o.models)].filter((p) => byFile.has(p) && !head.has(p));
3187
+ const inModels = new Set(models);
3188
+ const rest = files.filter((f) => !head.has(f.path) && !inModels.has(f.path));
3189
+ const all2 = [...files.map((f) => f.path), ...o.leftOut];
3190
+ const counts = () => {
3191
+ const deep = /* @__PURE__ */ new Map();
3192
+ const own = /* @__PURE__ */ new Map();
3193
+ const count = (m, k) => m.set(k, (m.get(k) ?? 0) + 1);
3194
+ for (const p of all2) {
3195
+ const parts = p.split("/");
3196
+ count(own, parts.slice(0, -1).join("/"));
3197
+ for (let d = 1; d < parts.length; d++)
3198
+ count(deep, parts.slice(0, d).join("/"));
3199
+ }
3200
+ return (dir, isDeep, n) => (isDeep ? deep.get(dir) : own.get(dir)) === n;
3201
+ };
3202
+ const holds = counts();
3203
+ const unitsOfGroup = (group) => {
3204
+ const small = group.filter((f) => !isBig(f)).sort((a, b) => byPath2(a.path, b.path));
3205
+ const out = groupUnits("", small, room, holds);
3206
+ for (const f of group.filter(isBig))
3207
+ out.push(...rangeUnits(f, room));
3208
+ return out;
3209
+ };
3210
+ return [
3211
+ ...unitsOfGroup(files.filter((f) => head.has(f.path))),
3212
+ ...models.flatMap((p) => {
3213
+ const f = byFile.get(p);
3214
+ return isBig(f) ? rangeUnits(f, room) : [{ glob: p, files: 1, tokens: f.tokens }];
3215
+ }),
3216
+ ...unitsOfGroup(rest)
3217
+ ];
3218
+ }
3219
+ function planParts(files, room = SESSION_TOKENS, order) {
2258
3220
  const total = sum(files);
2259
3221
  if (total <= room)
2260
3222
  return [];
2261
- const sorted = [...files].sort((a, b) => byPath2(a.path, b.path));
3223
+ let units;
3224
+ if (order)
3225
+ units = orderedUnits(files, room, order);
3226
+ else {
3227
+ const big = files.filter((f) => f.tokens > room && (f.lines ?? 0) > 1);
3228
+ const rest = files.filter((f) => !big.includes(f));
3229
+ const ranges = [];
3230
+ for (const f of big)
3231
+ for (const [i, r] of lineRanges({ ...f, lines: f.lines }, room).entries())
3232
+ ranges.push({
3233
+ glob: `${f.path}#L${r.from}-L${r.to}`,
3234
+ files: i === 0 ? 1 : 0,
3235
+ tokens: r.tokens,
3236
+ key: `${f.path}\0${String(r.start).padStart(12, "0")}`
3237
+ });
3238
+ const sorted = [...rest].sort((a, b) => byPath2(a.path, b.path));
3239
+ const keyOf = (g) => g.replace(/\/?\*\*?$/, "");
3240
+ units = [...unitsOf("", sorted, room).map((u) => ({ ...u, key: keyOf(u.glob) })), ...ranges].sort((a, b) => byPath2(a.key, b.key)).map(({ key: _k, ...u }) => u);
3241
+ }
2262
3242
  const target = total / Math.ceil(total / room);
2263
3243
  const parts = [];
2264
3244
  let current;
2265
- for (const u of unitsOf("", sorted, room)) {
3245
+ for (const u of units) {
2266
3246
  if (current && (current.tokens + u.tokens > room || current.tokens >= target)) {
2267
3247
  parts.push(current);
2268
3248
  current = void 0;
@@ -2277,13 +3257,104 @@ function planParts(files, room = SESSION_TOKENS) {
2277
3257
  return parts;
2278
3258
  }
2279
3259
 
3260
+ // packages/verify/dist/gaps.js
3261
+ var GAP_CONTEXT_LINES = 20;
3262
+ var byPath3 = (a, b) => a < b ? -1 : a > b ? 1 : 0;
3263
+ function quoted2(v) {
3264
+ return JSON.stringify(v).replace(/[\u007f-\u009f\u2028\u2029]/g, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`);
3265
+ }
3266
+ function split(cite2) {
3267
+ const m = /^(.+):(\d+)$/.exec(cite2);
3268
+ return m ? { file: m[1], line: Number(m[2]) } : void 0;
3269
+ }
3270
+ function planGaps(from, size, room, o = {}) {
3271
+ const whole = new Set(from.unknown ?? []);
3272
+ const lines = /* @__PURE__ */ new Map();
3273
+ const note = (cite2, why) => {
3274
+ const at = split(cite2);
3275
+ if (!at)
3276
+ return;
3277
+ const of = lines.get(at.file) ?? /* @__PURE__ */ new Map();
3278
+ of.set(at.line, [...of.get(at.line) ?? [], why]);
3279
+ lines.set(at.file, of);
3280
+ };
3281
+ for (const s of from.skipped ?? [])
3282
+ note(s.cite, `writes the ${s.kind} ${quoted2(s.value)}, which no fact cites`);
3283
+ for (const h of from.reviewedHolds ?? [])
3284
+ for (const s of h.skipped)
3285
+ note(s.cite, `writes the ${s.kind} ${quoted2(s.value)}, but ${quoted2(h.path)} is reviewed as ${quoted2(h.why)}: write its fact, or narrow the reviewed line`);
3286
+ for (const o2 of from.specOpsUncovered ?? [])
3287
+ note(o2.cite, `declares ${o2.method} ${quoted2(o2.path)}, which no entry covers: find the code that serves it`);
3288
+ for (const e of from.entriesNoFlowFollows ?? [])
3289
+ note(e.cite, e.route ? `is ${e.id} (${[e.method, quoted2(e.path)].filter(Boolean).join(" ")}), a gateway route with no forwarding call: write the call it forwards to its RouteRule's target, \`from\` it, as the gateway rules say` : `is ${e.id} (${[e.entryType, e.method, quoted2(e.path)].filter(Boolean).join(" ")}), which no flow follows: write its flow`);
3290
+ const unlisted = Math.max(0, (from.skippedCount ?? 0) - (from.skipped?.length ?? 0)) + Math.max(0, (from.specOpsUncoveredCount ?? 0) - (from.specOpsUncovered?.length ?? 0));
3291
+ const gone = [];
3292
+ const reads = [];
3293
+ const named = [.../* @__PURE__ */ new Set([...whole, ...lines.keys()])].sort(byPath3);
3294
+ const files = o.first?.length ? reachOrder(named, o.first) : named;
3295
+ for (const file of files) {
3296
+ const sz = size(file);
3297
+ if (!sz) {
3298
+ gone.push(file);
3299
+ continue;
3300
+ }
3301
+ const at = lines.get(file) ?? /* @__PURE__ */ new Map();
3302
+ const whys = (a, b) => [...at.entries()].filter(([l]) => l >= a && l <= b).sort((x, y) => x[0] - y[0]).flatMap(([l, ws]) => [...new Set(ws)].sort().map((w) => `line ${l} ${w}`));
3303
+ const share = (a, b) => Math.ceil(sz.tokens * (b - a + 1) / sz.lines);
3304
+ if (whole.has(file)) {
3305
+ const head = "no fact cites it: read it whole";
3306
+ if (sz.tokens <= room || sz.lines <= 1) {
3307
+ reads.push({ path: file, file, tokens: sz.tokens, why: [head, ...whys(1, sz.lines)] });
3308
+ continue;
3309
+ }
3310
+ for (const r of lineRanges(sz, room))
3311
+ reads.push({
3312
+ path: `${file}#L${r.from}-L${r.to}`,
3313
+ file,
3314
+ tokens: r.tokens,
3315
+ // R4: a line the ranges share is named in the range it is its own in, the earlier one.
3316
+ why: ["no fact cites the file: read these lines", ...whys(r.start, r.to)]
3317
+ });
3318
+ continue;
3319
+ }
3320
+ const windows = [];
3321
+ for (const l of [...at.keys()].sort((a, b) => a - b)) {
3322
+ const a = Math.max(1, l - GAP_CONTEXT_LINES);
3323
+ const b = Math.min(sz.lines, l + GAP_CONTEXT_LINES);
3324
+ const last = windows.at(-1);
3325
+ if (last && a <= last[1] + 1)
3326
+ last[1] = Math.max(last[1], b);
3327
+ else
3328
+ windows.push([a, b]);
3329
+ }
3330
+ for (const [a, b] of windows)
3331
+ reads.push({ path: `${file}#L${a}-L${b}`, file, tokens: share(a, b), why: whys(a, b) });
3332
+ }
3333
+ const sessions = [];
3334
+ for (const r of reads) {
3335
+ const last = sessions.at(-1);
3336
+ if (last && last.tokens + r.tokens <= room) {
3337
+ last.reads.push(r);
3338
+ last.tokens += r.tokens;
3339
+ } else
3340
+ sessions.push({ reads: [r], tokens: r.tokens });
3341
+ }
3342
+ return {
3343
+ sessions,
3344
+ files: new Set(reads.map((r) => r.file)).size,
3345
+ tokens: reads.reduce((n, r) => n + r.tokens, 0),
3346
+ unlisted,
3347
+ gone
3348
+ };
3349
+ }
3350
+
2280
3351
  // packages/diagrams/dist/views.js
2281
3352
  var VIEW_NAMES = ["context", "container", "data", "event", "sequence"];
2282
3353
  var ViewError = class extends Error {
2283
3354
  };
2284
3355
  var cmp = (a, b) => a < b ? -1 : a > b ? 1 : 0;
2285
3356
  var slug = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
2286
- var normPath = (p) => p.replace(/\{[^}]*\}/g, "{}").replace(/\/+$/, "") || "/";
3357
+ var normPath2 = (p) => p.replace(/\{[^}]*\}/g, "{}").replace(/\/+$/, "") || "/";
2287
3358
  function legendOf(status) {
2288
3359
  if (status === "planned-new")
2289
3360
  return "new";
@@ -2372,7 +3443,7 @@ var Catalog = class {
2372
3443
  what = this.topicOf(i);
2373
3444
  else {
2374
3445
  const ep = i.endpoint ? this.endpoints.get(i.endpoint) : void 0;
2375
- const path = ep?.endpointType === "rest" && normPath(ep.pathOrTopic) === normPath(i.normalizedPath) ? ep.pathOrTopic : i.normalizedPath;
3446
+ const path = ep?.endpointType === "rest" && normPath2(ep.pathOrTopic) === normPath2(i.normalizedPath) ? ep.pathOrTopic : i.normalizedPath;
2376
3447
  what = [i.method?.toUpperCase(), path].filter(Boolean).join(" ");
2377
3448
  }
2378
3449
  return [i.pattern?.code, what].filter(Boolean).join(" ");
@@ -2776,8 +3847,8 @@ function changeView(records2, change, flows, known = []) {
2776
3847
  const nodes = [];
2777
3848
  for (const id of comps) {
2778
3849
  const sys = c.components.has(id) ? c.systemOf(id) : void 0;
2779
- const base = c.components.has(id) || c.isSystem(id) ? c.endNode(id, sys) : { id, label: id, kind: "component" };
2780
- const { legend: _status, ...rest } = base;
3850
+ const base2 = c.components.has(id) || c.isSystem(id) ? c.endNode(id, sys) : { id, label: id, kind: "component" };
3851
+ const { legend: _status, ...rest } = base2;
2781
3852
  nodes.push({ ...rest, label: marked(rest.label, legends.get(id)), ...withMark(id) });
2782
3853
  }
2783
3854
  for (const t of tables) {
@@ -2873,14 +3944,14 @@ var Ids = class {
2873
3944
  const known = this.map.get(key);
2874
3945
  if (known)
2875
3946
  return known;
2876
- let base = raw.replace(/[^A-Za-z0-9_]/g, "_");
2877
- if (!/^[A-Za-z]/.test(base))
2878
- base = `n_${base}`;
2879
- if (RESERVED.has(base.toLowerCase()))
2880
- base = `${base}_`;
2881
- let id = base;
3947
+ let base2 = raw.replace(/[^A-Za-z0-9_]/g, "_");
3948
+ if (!/^[A-Za-z]/.test(base2))
3949
+ base2 = `n_${base2}`;
3950
+ if (RESERVED.has(base2.toLowerCase()))
3951
+ base2 = `${base2}_`;
3952
+ let id = base2;
2882
3953
  for (let n = 2; this.used.has(id); n++)
2883
- id = `${base}_${n}`;
3954
+ id = `${base2}_${n}`;
2884
3955
  this.used.add(id);
2885
3956
  this.map.set(key, id);
2886
3957
  return id;
@@ -3069,8 +4140,8 @@ function nodeStyle(n) {
3069
4140
  return styleText(SHAPES.topic, legend);
3070
4141
  if (n.boundary === "vendor")
3071
4142
  return styleText(SHAPES.vendor, n.kind === "component" ? { atticusKind: "component" } : {}, legend);
3072
- const base = n.kind === "system" ? SHAPES.system : SHAPES.component;
3073
- return styleText(base, n.boundary ? BOUNDARY[n.boundary] : void 0, legend);
4143
+ const base2 = n.kind === "system" ? SHAPES.system : SHAPES.component;
4144
+ return styleText(base2, n.boundary ? BOUNDARY[n.boundary] : void 0, legend);
3074
4145
  }
3075
4146
  function groupStyle(g) {
3076
4147
  return styleText(SHAPES.group, g.kind === "datastore" ? { atticusKind: "datastore-group" } : {}, g.boundary ? { strokeColor: BOUNDARY[g.boundary].strokeColor } : void 0, g.legend ? LEGEND[g.legend] : void 0);
@@ -3161,14 +4232,14 @@ function edgeStyle(e) {
3161
4232
  }
3162
4233
  var compress = (xml) => deflateRawSync(Buffer.from(encodeURIComponent(xml), "utf8"), { level: 9 }).toString("base64");
3163
4234
  function shapeLibrary() {
3164
- const base = [
4235
+ const base2 = [
3165
4236
  ["System", SHAPES.system, W, H],
3166
4237
  ["Component", SHAPES.component, W, H],
3167
4238
  ["Vendor", SHAPES.vendor, W, H + 20],
3168
4239
  ["Data store", SHAPES.datastore, 120, 80],
3169
4240
  ["Topic", SHAPES.topic, W, H]
3170
4241
  ];
3171
- const entries = base.flatMap(([title, style, w, h]) => [void 0, "new", "modify", "retire"].map((legend) => {
4242
+ const entries = base2.flatMap(([title, style, w, h]) => [void 0, "new", "modify", "retire"].map((legend) => {
3172
4243
  const value = attr(title);
3173
4244
  const st = attr(styleText(style, legend ? LEGEND[legend] : void 0));
3174
4245
  const xml = `<mxGraphModel><root><mxCell id="0"/><mxCell id="1" parent="0"/><mxCell id="2" value="${value}" style="${st}" vertex="1" parent="1"><mxGeometry width="${w}" height="${h}" as="geometry"/></mxCell></root></mxGraphModel>`;
@@ -3307,12 +4378,91 @@ var STATE = {
3307
4378
  };
3308
4379
  function recordGaps(r) {
3309
4380
  const g = [];
3310
- if (r.confidence === "inferred")
4381
+ if (r.confidence === "inferred" && !r.flags?.includes(JOINED_BY_PERSON))
3311
4382
  g.push("inferred");
3312
4383
  for (const f of r.flags ?? [])
3313
- g.push(f);
4384
+ if (!JOIN_HOW_FLAGS.has(f) && !LINK_EVIDENCE_NOT_GAPS.has(f))
4385
+ g.push(f);
3314
4386
  return g;
3315
4387
  }
4388
+ var MAX_JOINS = 20;
4389
+ var HOW_OF_FLAG = {
4390
+ "joined-by-path": "path",
4391
+ "joined-by-label": "label",
4392
+ "joined-by-platform-setting": "platform-setting",
4393
+ "joined-by-folder-name": "folder-name",
4394
+ "joined-despite-conflict": "conflict",
4395
+ "joined-by-service-name": "service-name",
4396
+ // G1 (ADR 0174): a person paired the call with the entry on the canvas.
4397
+ "joined-by-person": "person",
4398
+ // ADR 0181: the AI paired it, applied by Atticus from its run: the AI's word, not a person's.
4399
+ [JOINED_BY_AI]: "ai",
4400
+ "joined-by-answer": "answer",
4401
+ "joined-by-name": "named",
4402
+ "joined-by-repo": "repo",
4403
+ "joined-by-host": "host",
4404
+ "declared-caller": "declared",
4405
+ // ADR 0162 §6 (F8): a topic or queue the profile names as an outside system's: a person's word.
4406
+ [DECLARED_TOPIC]: "declared",
4407
+ // ADR 0160 (O3): answered by an entry of another team's shared map.
4408
+ [JOINED_TO_SHARED_MAP]: "shared"
4409
+ };
4410
+ function joinHow(i) {
4411
+ for (const f of i.flags ?? [])
4412
+ if (HOW_OF_FLAG[f])
4413
+ return HOW_OF_FLAG[f];
4414
+ if (i.via === "event-bus" || i.via === "mq")
4415
+ return "topic";
4416
+ return "unknown";
4417
+ }
4418
+ function joinOf(i) {
4419
+ const side = (s) => evidenceOf(i.evidence.filter((e) => e.side === s));
4420
+ const sided = /* @__PURE__ */ new Set(["caller", "callee", "gateway"]);
4421
+ return {
4422
+ interface: i.id,
4423
+ from: i.from,
4424
+ to: i.to,
4425
+ how: joinHow(i),
4426
+ gateway: i.via === "gateway",
4427
+ verified: i.confidence === "verified",
4428
+ ...i.method ? { method: i.method } : {},
4429
+ ...i.normalizedPath ? { path: i.normalizedPath } : {},
4430
+ caller: side("caller"),
4431
+ callee: side("callee"),
4432
+ hops: side("gateway"),
4433
+ other: evidenceOf(i.evidence.filter((e) => !e.side || !sided.has(e.side))),
4434
+ ...sharedOf(i),
4435
+ ...aiRunOf(i)
4436
+ };
4437
+ }
4438
+ function aiRunOf(i) {
4439
+ if (!i.flags?.includes(JOINED_BY_AI))
4440
+ return {};
4441
+ const run = i.evidence.find((e) => e.source === "ai" && e.run)?.run;
4442
+ return run ? { run } : {};
4443
+ }
4444
+ function sharedOf(i) {
4445
+ if (!i.flags?.includes(JOINED_TO_SHARED_MAP))
4446
+ return {};
4447
+ const e = i.evidence.find((x) => x.extractor === SHARED_MAP_EVIDENCE && x.side === "callee" && x.repo && x.path);
4448
+ if (!e)
4449
+ return {};
4450
+ return {
4451
+ shared: {
4452
+ team: e.by ?? "",
4453
+ repo: e.repo,
4454
+ at: e.line === void 0 ? e.path : `${e.path}:${e.line}`,
4455
+ ...e.commit ? { commit: e.commit } : {}
4456
+ }
4457
+ };
4458
+ }
4459
+ function mergeJoins(a, b) {
4460
+ const by = new Map(a.map((j) => [j.interface, j]));
4461
+ for (const j of b)
4462
+ if (!by.has(j.interface))
4463
+ by.set(j.interface, j);
4464
+ return [...by.values()].sort((x, y) => x.interface < y.interface ? -1 : x.interface > y.interface ? 1 : 0).slice(0, MAX_JOINS);
4465
+ }
3316
4466
  var props = (o) => {
3317
4467
  const out = {};
3318
4468
  for (const [k, v] of Object.entries(o)) {
@@ -3462,6 +4612,7 @@ function buildEstate(records2) {
3462
4612
  add(fromRecord(c, `component:${c.id}`, "component", c.id, parent, { type: c.type, runtime: c.runtime, repo: c.repo, path: c.path }, gaps));
3463
4613
  }
3464
4614
  const endpointNode = /* @__PURE__ */ new Map();
4615
+ const topicByPath = /* @__PURE__ */ new Map();
3465
4616
  for (const e of all(records2, "Endpoint")) {
3466
4617
  const topic = TOPIC_TYPES.has(e.endpointType);
3467
4618
  const id = `${topic ? "topic" : "endpoint"}:${e.id}`;
@@ -3478,7 +4629,30 @@ function buildEstate(records2) {
3478
4629
  auth: e.auth
3479
4630
  }, gaps));
3480
4631
  endpointNode.set(e.id, id);
4632
+ if (topic)
4633
+ topicByPath.set(e.pathOrTopic, id);
3481
4634
  }
4635
+ const busTopic = (name, parent, ev = []) => {
4636
+ const known = topicByPath.get(name);
4637
+ if (known)
4638
+ return known;
4639
+ const id = add({
4640
+ id: `topic:bus.${slug2(name)}`,
4641
+ kind: "topic",
4642
+ label: name,
4643
+ parent,
4644
+ state: "current",
4645
+ gaps: [],
4646
+ evidence: ev,
4647
+ properties: { pathOrTopic: name },
4648
+ next: {
4649
+ command: `atticus query "${name}"`,
4650
+ why: "List the records that name this topic or queue."
4651
+ }
4652
+ });
4653
+ topicByPath.set(name, id);
4654
+ return id;
4655
+ };
3482
4656
  const storeNode = /* @__PURE__ */ new Map();
3483
4657
  for (const d of all(records2, "DataStore")) {
3484
4658
  const owner = d.ownerComponent && components.has(d.ownerComponent);
@@ -3523,9 +4697,33 @@ function buildEstate(records2) {
3523
4697
  const ev = evidenceOf(i.evidence);
3524
4698
  const gaps = [...recordGaps(i), ...i.conflicts.length ? ["conflicts"] : []];
3525
4699
  const target = i.endpoint ? endpointNode.get(i.endpoint) : void 0;
4700
+ const join2 = joinOf(i);
3526
4701
  if (target?.startsWith("topic:")) {
3527
- atoms.push({ from, to: target, type: "publishes", evidence: ev, gaps });
3528
- atoms.push({ from: to, to: target, type: "subscribes", evidence: ev, gaps });
4702
+ atoms.push({ from, to: target, type: "publishes", evidence: ev, gaps, join: join2 });
4703
+ atoms.push({ from: to, to: target, type: "subscribes", evidence: ev, gaps, join: join2 });
4704
+ continue;
4705
+ }
4706
+ if (!i.endpoint && (i.via === "event-bus" || i.via === "mq") && i.normalizedPath) {
4707
+ const isSystem = (n) => n.startsWith("system:") || n.startsWith("unknown:");
4708
+ const [home, homeRef] = !isSystem(to) && isSystem(from) ? [from, i.from] : [to, i.to];
4709
+ const t = busTopic(i.normalizedPath, home);
4710
+ const owner = nodes.get(t);
4711
+ if (owner.parent === home && isSystem(home)) {
4712
+ owner.properties = { ...owner.properties, owner: nodes.get(home)?.label ?? home };
4713
+ const b = nodes.get(home)?.properties.boundary;
4714
+ if (outside.has(homeRef) || b === "vendor" || b === "outside")
4715
+ owner.properties.ownerOutside = true;
4716
+ }
4717
+ if (join2.shared)
4718
+ owner.properties = { ...owner.properties, owner: join2.shared.team, ownerOutside: true };
4719
+ if (i.flags?.includes(DECLARED_TOPIC)) {
4720
+ owner.properties = { ...owner.properties, declared: true };
4721
+ owner.gaps = [.../* @__PURE__ */ new Set([...owner.gaps, DECLARED_TOPIC])];
4722
+ owner.evidence = mergeEvidence(owner.evidence, evidenceOf(i.evidence.filter((e) => e.source === "manual")));
4723
+ }
4724
+ atoms.push({ from, to: t, type: "publishes", evidence: ev, gaps, join: join2 });
4725
+ if (to.startsWith("component:"))
4726
+ atoms.push({ from: to, to: t, type: "subscribes", evidence: ev, gaps, join: join2 });
3529
4727
  continue;
3530
4728
  }
3531
4729
  let caller = from;
@@ -3556,7 +4754,24 @@ function buildEstate(records2) {
3556
4754
  sync: i.sync,
3557
4755
  via: i.via,
3558
4756
  evidence: ev,
3559
- gaps: [...gaps, ...i.endpoint && !target ? ["no-endpoint"] : []]
4757
+ gaps: [...gaps, ...i.endpoint && !target ? ["no-endpoint"] : []],
4758
+ join: join2
4759
+ });
4760
+ }
4761
+ for (const u of all(records2, "Unresolved")) {
4762
+ if (u.unresolvedKind !== "event-publish" && u.unresolvedKind !== "event-subscribe")
4763
+ continue;
4764
+ if (!u.raw || u.raw.includes("${"))
4765
+ continue;
4766
+ const owner = u.owner ? partNode(u.owner) : "estate";
4767
+ const ev = evidenceOf(u.evidence);
4768
+ const t = busTopic(u.raw, u.unresolvedKind === "event-subscribe" ? owner : "estate", ev);
4769
+ atoms.push({
4770
+ from: owner,
4771
+ to: t,
4772
+ type: u.unresolvedKind === "event-publish" ? "publishes" : "subscribes",
4773
+ evidence: ev,
4774
+ gaps: recordGaps(u)
3560
4775
  });
3561
4776
  }
3562
4777
  for (const c of components.values())
@@ -3611,6 +4826,37 @@ function buildEstate(records2) {
3611
4826
  gaps: recordGaps(k)
3612
4827
  });
3613
4828
  }
4829
+ const busy = /* @__PURE__ */ new Set();
4830
+ const internal = (id) => {
4831
+ const n = nodes.get(id);
4832
+ return !!n && n.kind !== "unknown" && n.properties.boundary !== "outside" && n.properties.boundary !== "vendor";
4833
+ };
4834
+ for (const a of atoms)
4835
+ if (a.type === "publishes" || a.type === "subscribes") {
4836
+ if (internal(a.from))
4837
+ busy.add(`${a.type}|${a.to}`);
4838
+ }
4839
+ for (const n of nodes.values()) {
4840
+ if (n.kind !== "topic")
4841
+ continue;
4842
+ if (n.properties.owner === void 0) {
4843
+ let p = n.parent ? nodes.get(n.parent) : void 0;
4844
+ while (p && p.kind !== "system" && p.kind !== "repo" && p.kind !== "estate")
4845
+ p = p.parent ? nodes.get(p.parent) : void 0;
4846
+ if (p && p.kind !== "estate") {
4847
+ let sys = p;
4848
+ while (sys && sys.kind !== "system")
4849
+ sys = sys.parent ? nodes.get(sys.parent) : void 0;
4850
+ n.properties = { ...n.properties, owner: (sys ?? p).label };
4851
+ if (sys && (sys.properties.boundary === "outside" || sys.properties.boundary === "vendor"))
4852
+ n.properties.ownerOutside = true;
4853
+ }
4854
+ }
4855
+ if (!busy.has(`publishes|${n.id}`))
4856
+ n.gaps.push("no-publisher");
4857
+ if (!busy.has(`subscribes|${n.id}`))
4858
+ n.gaps.push("no-listener");
4859
+ }
3614
4860
  const kids = /* @__PURE__ */ new Map();
3615
4861
  for (const n of nodes.values()) {
3616
4862
  if (!n.parent)
@@ -3705,10 +4951,13 @@ function merge(mapped) {
3705
4951
  count: 1,
3706
4952
  state: "current",
3707
4953
  gaps: [...new Set(atom.gaps)].sort(),
3708
- evidence: mergeEvidence(atom.evidence)
4954
+ evidence: mergeEvidence(atom.evidence),
4955
+ ...atom.join ? { joins: [atom.join] } : {}
3709
4956
  });
3710
4957
  continue;
3711
4958
  }
4959
+ if (atom.join)
4960
+ e.joins = mergeJoins(e.joins ?? [], [atom.join]);
3712
4961
  e.count++;
3713
4962
  if (atom.sync && e.sync !== atom.sync)
3714
4963
  e.sync = "mixed";
@@ -3772,16 +5021,27 @@ function nodeView(est, id, o) {
3772
5021
  return ax[i] ?? x;
3773
5022
  };
3774
5023
  const types = LENS_EDGES[lens];
5024
+ const onBus = (atom, b) => (atom.type === "publishes" || atom.type === "subscribes") && est.nodes.get(b)?.kind === "topic" && b !== id;
5025
+ const drawnTopics = /* @__PURE__ */ new Set();
5026
+ for (const atom of est.atoms) {
5027
+ if (!types.has(atom.type))
5028
+ continue;
5029
+ const a = lift(est, lens, atom.from);
5030
+ const b = lift(est, lens, atom.to);
5031
+ if (onBus(atom, b) && (inside(a) || inside(b)))
5032
+ drawnTopics.add(b);
5033
+ }
3775
5034
  const mapped = [];
3776
5035
  for (const atom of est.atoms) {
3777
5036
  if (!types.has(atom.type))
3778
5037
  continue;
3779
5038
  const a = lift(est, lens, atom.from);
3780
5039
  const b = lift(est, lens, atom.to);
3781
- if (!inside(a) && !inside(b))
5040
+ const bus = onBus(atom, b);
5041
+ if (!inside(a) && !inside(b) && !(bus && drawnTopics.has(b)))
3782
5042
  continue;
3783
5043
  const from = place(a);
3784
- const to = place(b);
5044
+ const to = bus ? b : place(b);
3785
5045
  if (from === to)
3786
5046
  continue;
3787
5047
  mapped.push({ from, to, atom });
@@ -3790,10 +5050,12 @@ function nodeView(est, id, o) {
3790
5050
  let hidden = 0;
3791
5051
  if (!o.all)
3792
5052
  ({ edges, hidden } = reduceUses(edges));
3793
- const outside = [...new Set(edges.flatMap((e) => [e.from, e.to]))].filter((n) => n !== id && !shownSet.has(n)).sort();
3794
- const children = shown.map((n) => toNode(est, lens, n));
5053
+ const ends = [...new Set(edges.flatMap((e) => [e.from, e.to]))].filter((n) => n !== id && !shownSet.has(n));
5054
+ const deeper = ends.filter((n) => drawnTopics.has(n) && inside(n)).sort();
5055
+ const outside = ends.filter((n) => !deeper.includes(n)).sort();
5056
+ const children = [...shown, ...deeper].map((n) => toNode(est, lens, n));
3795
5057
  const firstTable = children.find((c) => c.kind === "table");
3796
- const suggest = lens === "data" && firstTable ? { mode: "focus", id: firstTable.id } : shown.length + outside.length > DETAILED_LIMIT ? { mode: "group" } : void 0;
5058
+ const suggest = lens === "data" && firstTable ? { mode: "focus", id: firstTable.id } : shown.length + deeper.length + outside.length > DETAILED_LIMIT ? { mode: "group" } : void 0;
3797
5059
  return {
3798
5060
  lens,
3799
5061
  node: toNode(est, lens, id),
@@ -3931,13 +5193,13 @@ function codeLinkerFor(o) {
3931
5193
  web = `https://${scp[1]}/${scp[2]}`;
3932
5194
  }
3933
5195
  if (web) {
3934
- const base = web.replace(/\/+$/, "").replace(/\.git$/, "");
3935
- const ado = /(^https?:\/\/dev\.azure\.com\/)|(\.visualstudio\.com\/)/i.test(base);
5196
+ const base2 = web.replace(/\/+$/, "").replace(/\.git$/, "");
5197
+ const ado = /(^https?:\/\/dev\.azure\.com\/)|(\.visualstudio\.com\/)/i.test(base2);
3936
5198
  return (path, line, commit) => {
3937
5199
  const p = encodeURI(path);
3938
5200
  if (ado)
3939
- return `${base}?path=/${p}${commit ? `&version=GC${commit}` : ""}${line ? `&line=${line}` : ""}`;
3940
- return `${base}/blob/${commit ?? "HEAD"}/${p}${line ? `#L${line}` : ""}`;
5201
+ return `${base2}?path=/${p}${commit ? `&version=GC${commit}` : ""}${line ? `&line=${line}` : ""}`;
5202
+ return `${base2}/blob/${commit ?? "HEAD"}/${p}${line ? `#L${line}` : ""}`;
3941
5203
  };
3942
5204
  }
3943
5205
  const dir = (o.dir ?? "").replace(/\/+$/, "");
@@ -4152,6 +5414,27 @@ function config(c) {
4152
5414
  return `- ${code(`${s.key} = ${s.valueOrRef}`)} (${notes.join("; ")}): ${cite(c, ownEvidence(s, c.i.repo))}`;
4153
5415
  });
4154
5416
  }
5417
+ function terms(c) {
5418
+ const ts = c.i.terms ?? [];
5419
+ const owners = /* @__PURE__ */ new Map();
5420
+ for (const r of records(c, "Component"))
5421
+ for (const f of r.flags ?? []) {
5422
+ const m = /^term:[^=]+=(.+)$/.exec(f);
5423
+ if (m && !c.comps.has(r.id))
5424
+ owners.set(m[1], [...owners.get(m[1]) ?? [], r.id]);
5425
+ }
5426
+ const listing = /* @__PURE__ */ new Map();
5427
+ for (const g of records(c, "AccessGrant"))
5428
+ if (g.flags?.some((f) => f.startsWith("term:"))) {
5429
+ const route = c.byId.get(g.resource);
5430
+ const by = route?.kind === "GatewayRoute" ? route.proxy : g.resource;
5431
+ listing.set(g.subject, [.../* @__PURE__ */ new Set([...listing.get(g.subject) ?? [], by])]);
5432
+ }
5433
+ return [...ts].sort((a, b) => cmp2(a.term, b.term) || cmp2(a.value, b.value)).map((t) => {
5434
+ const means = t.role === "owner" ? `this repo's own id${listing.get(t.value)?.length ? `; let in by ${list(listing.get(t.value).sort())}` : ""}` : t.role === "consumer" ? `a consumer it lets in${owners.get(t.value)?.length ? `: ${list(owners.get(t.value).sort())}` : ", whose own repo no fact names yet"}` : t.role === "none" ? "names no system" : `**open:** nothing says what it means${owners.get(t.value)?.length ? `; the same value is ${list(owners.get(t.value).sort())}'s own id (a possible link)` : ""}`;
5435
+ return `- ${code(t.term)} ${code(t.value)}: ${means} ${cite(c, t.cite)}${t.meaning ? ` (its role from ${cite(c, t.meaning)})` : ""}`;
5436
+ });
5437
+ }
4155
5438
  var flowFile = (f) => `flows/${f.id}.md`;
4156
5439
  function flowsList(c) {
4157
5440
  if (!c.i.flows.length)
@@ -4365,8 +5648,8 @@ function ctxOf(i, file) {
4365
5648
  }
4366
5649
  function renderMap(i) {
4367
5650
  const files = {};
4368
- const base = ctxOf(i, "map.md");
4369
- for (const comp of [...base.comps].sort(cmp2)) {
5651
+ const base2 = ctxOf(i, "map.md");
5652
+ for (const comp of [...base2.comps].sort(cmp2)) {
4370
5653
  const yaml = openApiOf(i, comp);
4371
5654
  if (yaml)
4372
5655
  files[`contracts/${comp}.openapi.yaml`] = yaml;
@@ -4380,17 +5663,18 @@ function renderMap(i) {
4380
5663
  HEADER2(i.repo, "the catalog and its specs"),
4381
5664
  "",
4382
5665
  ...aboutLine(i.about, i.repo),
4383
- ...builtOnLine(base),
5666
+ ...builtOnLine(base2),
4384
5667
  trustLine(i.trust, i.repo),
4385
5668
  "",
4386
- ...section("What it serves", serves(base)),
4387
- ...section("What it calls", calls(base)),
4388
- ...section("What it stores", stores(base)),
4389
- ...section("Where it runs", deploys(base)),
4390
- ...section("Config that names other systems", config(base)),
4391
- ...section("Flows", flowsList(base)),
4392
- ...section("Contracts", contracts(base, files)),
4393
- ...section("Open questions", openQuestions(base))
5669
+ ...section("What it serves", serves(base2)),
5670
+ ...section("What it calls", calls(base2)),
5671
+ ...section("What it stores", stores(base2)),
5672
+ ...section("Where it runs", deploys(base2)),
5673
+ ...section("Config that names other systems", config(base2)),
5674
+ ...i.terms?.length ? section("Terms", terms(base2)) : [],
5675
+ ...section("Flows", flowsList(base2)),
5676
+ ...section("Contracts", contracts(base2, files)),
5677
+ ...section("Open questions", openQuestions(base2))
4394
5678
  ].join("\n");
4395
5679
  return Object.fromEntries(Object.entries(files).sort(([a], [b]) => cmp2(a, b)));
4396
5680
  }
@@ -4430,7 +5714,7 @@ function rereadPlan(o) {
4430
5714
  if (text === void 0 || !text.trim())
4431
5715
  return false;
4432
5716
  const ls = text.split(/\r?\n/);
4433
- if (!isSourceFile(p, ls[0] ?? "", ls))
5717
+ if (!isSourceFile(p, ls[0] ?? "", ls, readRulesOf(o.conventions)))
4434
5718
  return false;
4435
5719
  return !excludeRes.some((re) => re.test(p));
4436
5720
  });
@@ -4454,10 +5738,241 @@ function onlyFiles(source, paths) {
4454
5738
  return { list: () => source.list().filter((p) => keep.has(p)), read: (p) => source.read(p) };
4455
5739
  }
4456
5740
 
5741
+ // packages/verify/dist/slips.js
5742
+ function slugFactId(id) {
5743
+ return id.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2").toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/-{2,}/g, "-").replace(/^[^a-z0-9]+/, "").replace(/-+$/, "").slice(0, 120);
5744
+ }
5745
+ var isId = (x) => typeof x === "string" && FactId.safeParse(x).success;
5746
+ var isObj = (x) => !!x && typeof x === "object" && !Array.isArray(x);
5747
+ var REF_FIELDS = ["from", "under", "owner", "mappedTo", "entry"];
5748
+ function repairFormSlips(facts, bad, onFile = []) {
5749
+ const slips = [];
5750
+ const cands = bad.flatMap((b) => {
5751
+ if (b.issues.some((i) => i.code === "not-json" || i.code === "duplicate-id"))
5752
+ return [];
5753
+ let json2;
5754
+ try {
5755
+ json2 = JSON.parse(b.raw);
5756
+ } catch {
5757
+ return [];
5758
+ }
5759
+ return isObj(json2) ? [{ b, json: json2 }] : [];
5760
+ });
5761
+ const taken = /* @__PURE__ */ new Map();
5762
+ for (const f of facts)
5763
+ taken.set(f.id, (taken.get(f.id) ?? 0) + 1);
5764
+ for (const { json: json2 } of cands)
5765
+ if (isId(json2.id))
5766
+ taken.set(json2.id, (taken.get(json2.id) ?? 0) + 1);
5767
+ const wanted = /* @__PURE__ */ new Map();
5768
+ for (const { json: json2 } of cands)
5769
+ if (typeof json2.id === "string" && !isId(json2.id)) {
5770
+ const s = slugFactId(json2.id);
5771
+ if (isId(s))
5772
+ wanted.set(s, (wanted.get(s) ?? 0) + 1);
5773
+ }
5774
+ const known = new Set([...facts, ...onFile].map((f) => f.id));
5775
+ const renames = /* @__PURE__ */ new Map();
5776
+ const collided = /* @__PURE__ */ new Map();
5777
+ for (const { b, json: json2 } of cands) {
5778
+ if (typeof json2.id !== "string" || isId(json2.id))
5779
+ continue;
5780
+ const s = slugFactId(json2.id);
5781
+ if (!isId(s))
5782
+ continue;
5783
+ if (taken.has(s) || (wanted.get(s) ?? 0) > 1) {
5784
+ const message = `line ${b.line}: fact id ${json2.id} would slug to ${s}, which another fact already has; rename one of them`;
5785
+ slips.push({ line: b.line, code: "id-collision", fact: json2.id, message });
5786
+ collided.set(b, message);
5787
+ continue;
5788
+ }
5789
+ renames.set(json2.id, s);
5790
+ }
5791
+ const rewritten = cands.map(({ b, json: json2 }) => {
5792
+ const o = structuredClone(json2);
5793
+ const fixes = [];
5794
+ if (typeof o.id === "string" && renames.has(o.id)) {
5795
+ fixes.push({
5796
+ line: b.line,
5797
+ code: "id-slugged",
5798
+ fact: renames.get(o.id),
5799
+ message: `line ${b.line}: fact id ${o.id} slugged to ${renames.get(o.id)}, and every reference to it`
5800
+ });
5801
+ o.id = renames.get(o.id);
5802
+ }
5803
+ const ref = (v) => {
5804
+ if (typeof v !== "string")
5805
+ return void 0;
5806
+ if (renames.has(v))
5807
+ return renames.get(v);
5808
+ if (isId(v))
5809
+ return void 0;
5810
+ const s = slugFactId(v);
5811
+ return known.has(s) ? s : void 0;
5812
+ };
5813
+ const said = (field, from, to) => fixes.push({
5814
+ line: b.line,
5815
+ code: "ref-rewritten",
5816
+ fact: String(o.id),
5817
+ message: `line ${b.line}: ${String(o.id)}'s ${field} ${from} rewritten as ${to}`
5818
+ });
5819
+ for (const k of REF_FIELDS) {
5820
+ const to = ref(o[k]);
5821
+ if (to) {
5822
+ said(k, o[k], to);
5823
+ o[k] = to;
5824
+ }
5825
+ }
5826
+ if (Array.isArray(o.steps))
5827
+ o.steps.forEach((st, i) => {
5828
+ if (!isObj(st))
5829
+ return;
5830
+ const to = ref(st.ref);
5831
+ if (to) {
5832
+ said(`step ${i + 1} ref`, st.ref, to);
5833
+ st.ref = to;
5834
+ }
5835
+ });
5836
+ return { b, o, fixes };
5837
+ });
5838
+ const entries = /* @__PURE__ */ new Map();
5839
+ for (const f of [...onFile, ...facts])
5840
+ if (f.kind === "entry")
5841
+ entries.set(f.id, f);
5842
+ for (const { o } of rewritten)
5843
+ if (o.kind === "entry" && typeof o.id === "string" && isObj(o.cite))
5844
+ entries.set(o.id, o);
5845
+ for (const r of rewritten) {
5846
+ const { o, b } = r;
5847
+ if (o.kind !== "flow" || o.cite !== void 0 || typeof o.entry !== "string")
5848
+ continue;
5849
+ const e = entries.get(o.entry);
5850
+ if (!e || !isObj(e.cite))
5851
+ continue;
5852
+ o.cite = structuredClone(e.cite);
5853
+ const c = e.cite;
5854
+ r.fixes.push({
5855
+ line: b.line,
5856
+ code: "flow-cite-from-entry",
5857
+ fact: String(o.id),
5858
+ message: `line ${b.line}: flow ${String(o.id)} had no cite; it takes its entry ${o.entry}'s line (${String(c.file)}:${String(c.line)})`
5859
+ });
5860
+ }
5861
+ const kept = [...facts];
5862
+ const seen = new Set(facts.map((f) => f.id));
5863
+ const fixedLines = /* @__PURE__ */ new Set();
5864
+ for (const { b, o, fixes } of rewritten) {
5865
+ if (!fixes.length || collided.has(b))
5866
+ continue;
5867
+ const p = Fact.safeParse(o);
5868
+ if (!p.success || seen.has(p.data.id))
5869
+ continue;
5870
+ seen.add(p.data.id);
5871
+ kept.push(p.data);
5872
+ fixedLines.add(b);
5873
+ slips.push(...fixes);
5874
+ }
5875
+ const still = bad.filter((b) => !fixedLines.has(b)).map((b) => collided.has(b) ? {
5876
+ ...b,
5877
+ issues: [
5878
+ ...b.issues,
5879
+ { line: b.line, code: "id-collision", message: collided.get(b) }
5880
+ ]
5881
+ } : b);
5882
+ slips.sort((x, y) => x.line - y.line);
5883
+ return { facts: kept, bad: still, slips };
5884
+ }
5885
+
5886
+ // packages/verify/dist/house-imports.js
5887
+ var CODE2 = /\.(java|kt|kts|scala|groovy|cs|vb|fs|ts|tsx|js|jsx|mjs|cjs|vue|svelte|py|go|rb|php|swift)$/i;
5888
+ var IMPORTS = [
5889
+ // Java, Kotlin, Scala, Groovy, Python's `import a.b`, Go's one-line import
5890
+ /^\s*import\s+(?:static\s+)?([A-Za-z_][\w.]*)/,
5891
+ // C#, F#
5892
+ /^\s*(?:global\s+)?using\s+(?:static\s+)?([A-Za-z_][\w.]*)\s*;/,
5893
+ /^\s*open\s+([A-Za-z_][\w.]*)\s*$/,
5894
+ // Python
5895
+ /^\s*from\s+([A-Za-z_][\w.]*)\s+import\b/,
5896
+ // JavaScript, TypeScript: import ... from "x", export ... from "x", import "x", require("x"), import("x")
5897
+ /\bfrom\s+["']([^"'\s]+)["']/,
5898
+ /^\s*import\s+["']([^"'\s]+)["']/,
5899
+ /\brequire\s*\(\s*["']([^"'\s]+)["']\s*\)/,
5900
+ /\bimport\s*\(\s*["']([^"'\s]+)["']\s*\)/
5901
+ ];
5902
+ var GO_IMPORT = /^\s*(?:import\s+)?(?:[\w.]+\s+)?"([\w.-]+(?:\/[\w.-]+)+)"\s*$/;
5903
+ function modulesOf(line, go) {
5904
+ const out = [];
5905
+ for (const re of go ? [...IMPORTS, GO_IMPORT] : IMPORTS) {
5906
+ const m = re.exec(line);
5907
+ if (m?.[1])
5908
+ out.push(m[1]);
5909
+ }
5910
+ return out;
5911
+ }
5912
+ var lower = (s) => s.toLowerCase();
5913
+ var under3 = (module, pkg) => module === pkg || module.startsWith(pkg) && (/[./]$/.test(pkg) || /[./]/.test(module[pkg.length] ?? ""));
5914
+ function packageOf(module, prefix) {
5915
+ let i = prefix.length;
5916
+ while (i < module.length && /[./]/.test(module[i]))
5917
+ i++;
5918
+ const rest = module.slice(i);
5919
+ const part = /^[^./]*/.exec(rest)?.[0] ?? "";
5920
+ return module.slice(0, i + part.length).replace(/[./]+$/, "");
5921
+ }
5922
+ function nameOf2(pkg) {
5923
+ const parts = lower(pkg).split(/[^a-z0-9]+/).filter(Boolean);
5924
+ if (parts.length > 2 && /^(com|org|net|io|dev|co|uk|de|fr|nl|ch)$/.test(parts[0]))
5925
+ parts.shift();
5926
+ return parts.join("-") || "house-framework";
5927
+ }
5928
+ function houseImportsUntaught(source, o) {
5929
+ if (!o.prefixes.length)
5930
+ return [];
5931
+ const rules = o.rules ?? DEFAULT_READ_RULES;
5932
+ const prefixes = o.prefixes.map(lower);
5933
+ const taught = o.taught.map(lower);
5934
+ const found = /* @__PURE__ */ new Map();
5935
+ const paths = source.list().filter((p) => CODE2.test(p) && !rules.notRead.test(p) && !(o.exclude ?? []).some((re) => re.test(p)) && isSourceFile(p, "", void 0, rules)).sort();
5936
+ for (const p of paths) {
5937
+ const text = source.read(p);
5938
+ if (text === void 0)
5939
+ continue;
5940
+ const lines = text.split(/\r?\n/);
5941
+ const go = /\.go$/i.test(p);
5942
+ lines.forEach((line, i) => {
5943
+ if (!/\b(import|using|from|require|open)\b/.test(line) && !(go && /^\s*"/.test(line)))
5944
+ return;
5945
+ for (const m of modulesOf(line, go)) {
5946
+ const ml = lower(m);
5947
+ const prefix = prefixes.find((x) => ml.startsWith(x));
5948
+ if (!prefix)
5949
+ continue;
5950
+ if (taught.some((t) => under3(ml, t)))
5951
+ continue;
5952
+ const pkg = packageOf(m, prefix);
5953
+ const f = found.get(pkg) ?? { files: /* @__PURE__ */ new Set(), first: `${p}:${i + 1}` };
5954
+ f.files.add(p);
5955
+ found.set(pkg, f);
5956
+ }
5957
+ });
5958
+ }
5959
+ return [...found.entries()].map(([pkg, f]) => {
5960
+ const name = nameOf2(pkg);
5961
+ return {
5962
+ package: pkg,
5963
+ name,
5964
+ files: f.files.size,
5965
+ first: f.first,
5966
+ command: `atticus frameworks learn ${name} --from <its source, JAR or npm package> --package ${pkg}`
5967
+ };
5968
+ }).sort((a, b) => b.files - a.files || (a.package < b.package ? -1 : a.package > b.package ? 1 : 0));
5969
+ }
5970
+
4457
5971
  export {
4458
5972
  insideRepo,
4459
5973
  dirSource,
4460
5974
  correctionFor,
5975
+ outsidePairingFor,
4461
5976
  correctedFact,
4462
5977
  applyCorrections,
4463
5978
  frameworkFacts,
@@ -4465,20 +5980,28 @@ export {
4465
5980
  frameworkTopicsOfType,
4466
5981
  frameworkGaps,
4467
5982
  frameworksToLearn,
5983
+ readRulesOf,
5984
+ lineRangeOf,
4468
5985
  parseReviewed,
4469
- sameCommit,
4470
- staleMessage,
4471
- verifyFacts,
5986
+ readingRank,
4472
5987
  FILE_KINDS,
5988
+ fileKinds,
4473
5989
  kindsOf,
4474
5990
  numberedSlice,
4475
5991
  sliceText,
4476
5992
  planBatches,
5993
+ isEntryPointFile,
5994
+ NOT_REACHED_WHY,
5995
+ reachableModels,
5996
+ sameCommit,
5997
+ staleMessage,
5998
+ verifyFacts,
4477
5999
  SHOWN_PROPOSALS,
4478
6000
  proposeExclusions,
4479
6001
  exclusionYaml,
4480
- SESSION_TOKENS,
6002
+ sessionTokensFor,
4481
6003
  planParts,
6004
+ planGaps,
4482
6005
  VIEW_NAMES,
4483
6006
  ViewError,
4484
6007
  changeView,
@@ -4496,5 +6019,7 @@ export {
4496
6019
  codeLinkerFor,
4497
6020
  renderMap,
4498
6021
  rereadPlan,
4499
- onlyFiles
6022
+ onlyFiles,
6023
+ repairFormSlips,
6024
+ houseImportsUntaught
4500
6025
  };