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

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 (284) hide show
  1. package/dist/bin.js +8 -4
  2. package/dist/chunk-23XVSEV5.js +49 -0
  3. package/dist/{chunk-ZDPAS56H.js → chunk-24OXGB3P.js} +8 -2
  4. package/dist/{chunk-7EOBHUMC.js → chunk-24XLDU72.js} +4 -3
  5. package/dist/{chunk-CR3MTH32.js → chunk-2F4CYZKJ.js} +86 -11
  6. package/dist/{chunk-6IU67AEK.js → chunk-2GF7OLF3.js} +23 -15
  7. package/dist/chunk-2IIMFOPM.js +41 -0
  8. package/dist/chunk-2LGRMVCA.js +617 -0
  9. package/dist/{chunk-XPCM366L.js → chunk-2MMCFP5C.js} +10 -8
  10. package/dist/{chunk-JBTK5OP2.js → chunk-2Q56PQPS.js} +6 -4
  11. package/dist/{chunk-Z6LF7HQN.js → chunk-2Q5EDGEO.js} +7 -5
  12. package/dist/{chunk-3USYZGRF.js → chunk-2RLKFUUL.js} +38 -27
  13. package/dist/chunk-2SKB4O2E.js +59 -0
  14. package/dist/{chunk-SP2I63OO.js → chunk-34S2WGOB.js} +87 -4
  15. package/dist/{chunk-GQ4N4WKF.js → chunk-3NFFJL5D.js} +33 -27
  16. package/dist/{chunk-SJRGMG6A.js → chunk-3NWDLGG6.js} +28 -25
  17. package/dist/{chunk-4WBVUHXN.js → chunk-3RM4UK6W.js} +20 -18
  18. package/dist/chunk-42SIDKEU.js +583 -0
  19. package/dist/{chunk-GLUX4EEP.js → chunk-4EURQWQD.js} +20 -15
  20. package/dist/chunk-4U276ICE.js +39 -0
  21. package/dist/{chunk-ZMFPW66A.js → chunk-53T4O55J.js} +4 -3
  22. package/dist/{chunk-6J5OKA4Y.js → chunk-57TDNMI3.js} +17 -15
  23. package/dist/chunk-5BD2KYLE.js +57 -0
  24. package/dist/{chunk-7EFILTCI.js → chunk-5F7GFN2F.js} +28 -0
  25. package/dist/chunk-5NMKONLP.js +95 -0
  26. package/dist/{chunk-7ZUIMTOU.js → chunk-5SJZW52K.js} +26 -24
  27. package/dist/{chunk-4RS5KZQD.js → chunk-63YRMR6J.js} +6 -5
  28. package/dist/chunk-66PPNOON.js +62 -0
  29. package/dist/chunk-6H3Z5JHH.js +64 -0
  30. package/dist/{chunk-CEXSU4S6.js → chunk-6MUGR4LS.js} +775 -89
  31. package/dist/{chunk-QSXK2RGH.js → chunk-6OJQOPUN.js} +5 -3
  32. package/dist/{chunk-MVZVGYXH.js → chunk-6PPOWY5E.js} +84 -14
  33. package/dist/{chunk-7EKXJZ2Y.js → chunk-6ZGLAACW.js} +299 -38
  34. package/dist/chunk-6ZJ5A7AF.js +222 -0
  35. package/dist/{chunk-L24C55VN.js → chunk-745DAZ4L.js} +142 -30
  36. package/dist/{chunk-Q4D5X7QB.js → chunk-7ASNL4Q4.js} +46 -24
  37. package/dist/chunk-7BR7IPOH.js +35 -0
  38. package/dist/{chunk-7WD63HU2.js → chunk-7DOR27HL.js} +6 -6
  39. package/dist/chunk-7FTTA3HD.js +341 -0
  40. package/dist/{chunk-OLQJTJJU.js → chunk-7GJS6DNX.js} +12 -3
  41. package/dist/{chunk-7JUKDRSZ.js → chunk-7IGKHZ4Z.js} +347 -226
  42. package/dist/{chunk-G2B4ZQGH.js → chunk-7IOWA5D5.js} +11 -6
  43. package/dist/{chunk-EBEW3XXQ.js → chunk-7JITZFJC.js} +7 -5
  44. package/dist/chunk-7MJSQTL5.js +174 -0
  45. package/dist/{chunk-I4JRCD6M.js → chunk-7UOMVMFG.js} +25 -17
  46. package/dist/{chunk-YY6ZQQBP.js → chunk-AFWUDXEM.js} +19 -7
  47. package/dist/chunk-AFZ3QA6W.js +47 -0
  48. package/dist/chunk-AJQ72EBM.js +420 -0
  49. package/dist/chunk-AJYXNTNZ.js +317 -0
  50. package/dist/chunk-AVTBS4OW.js +49 -0
  51. package/dist/{chunk-FHH4I4ZV.js → chunk-BDKC6H5P.js} +12 -6
  52. package/dist/{chunk-IPKHE5D6.js → chunk-BEVTIBRH.js} +1526 -875
  53. package/dist/chunk-BHT7QK4G.js +162 -0
  54. package/dist/{chunk-6JUBZBMW.js → chunk-BIGDQDHD.js} +6 -4
  55. package/dist/{chunk-2BFCZ3RK.js → chunk-BO4KX37X.js} +20 -18
  56. package/dist/chunk-BPK5P77O.js +115 -0
  57. package/dist/chunk-BQ4SQQC6.js +59 -0
  58. package/dist/chunk-BQGDB6ZL.js +7342 -0
  59. package/dist/{chunk-2ANOROX3.js → chunk-BTGPE6CR.js} +11 -9
  60. package/dist/chunk-BTY3HZBO.js +93 -0
  61. package/dist/{chunk-DPZHZQKE.js → chunk-BXYMYEJ4.js} +44 -10
  62. package/dist/{chunk-2RMYG36Q.js → chunk-C3RY6KU6.js} +309 -346
  63. package/dist/chunk-C4R4JLHE.js +40 -0
  64. package/dist/chunk-CADWLE2T.js +18 -0
  65. package/dist/chunk-CBGGOQWU.js +82 -0
  66. package/dist/chunk-CMDXGQTS.js +47 -0
  67. package/dist/{chunk-MC3H7736.js → chunk-CQDGOPGH.js} +58 -18
  68. package/dist/{chunk-HFQCFSJR.js → chunk-CX4RZ2KS.js} +4 -3
  69. package/dist/{chunk-PAMPPUR6.js → chunk-CXJ6FUKN.js} +1852 -207
  70. package/dist/{chunk-ABWHYWNI.js → chunk-D73UEXYW.js} +3 -3
  71. package/dist/chunk-DCZWQLYT.js +1205 -0
  72. package/dist/chunk-DLOBCJL6.js +48 -0
  73. package/dist/{chunk-4BN53TG3.js → chunk-DOBQKHIF.js} +31 -23
  74. package/dist/chunk-E3ISYXO3.js +423 -0
  75. package/dist/{chunk-7SW2QWLU.js → chunk-E6ZIEVOI.js} +16 -13
  76. package/dist/chunk-EBL2R3YG.js +43 -0
  77. package/dist/{chunk-AIOQPLA5.js → chunk-EGQRREQH.js} +41 -15
  78. package/dist/chunk-EHA7RMZ3.js +58 -0
  79. package/dist/{chunk-M766ZJ6W.js → chunk-EIPTLPFC.js} +38 -27
  80. package/dist/chunk-EMRIQUND.js +517 -0
  81. package/dist/{chunk-DFBCSHVT.js → chunk-EPCJQQWD.js} +6 -4
  82. package/dist/{chunk-RZPYKDWS.js → chunk-F3P7N7SF.js} +1 -1
  83. package/dist/{chunk-R77KLRB3.js → chunk-FFLNS6VP.js} +21 -18
  84. package/dist/chunk-FFTRTL6R.js +26 -0
  85. package/dist/{chunk-IHMLFRVV.js → chunk-FPJPLKDW.js} +62 -16
  86. package/dist/{chunk-JCZIKA4C.js → chunk-FSMSFDLD.js} +5 -4
  87. package/dist/{chunk-AVJDSJ2F.js → chunk-FUJM6RTF.js} +8 -7
  88. package/dist/chunk-FZOJFH7Q.js +132 -0
  89. package/dist/{chunk-QRAOANID.js → chunk-G6X77UJZ.js} +23 -21
  90. package/dist/{chunk-3ALD2W2A.js → chunk-GCWRWSU7.js} +10 -4
  91. package/dist/chunk-GIW4ZVDB.js +39 -0
  92. package/dist/{chunk-B2WLAWNJ.js → chunk-GUX3PTYM.js} +3 -3
  93. package/dist/{chunk-DPUYDDQG.js → chunk-GZY6RUZ6.js} +25 -3
  94. package/dist/chunk-H4VO6WG3.js +10 -0
  95. package/dist/{chunk-MMOSZPWJ.js → chunk-H5HBGNXU.js} +5 -4
  96. package/dist/chunk-H7KM7LZE.js +229 -0
  97. package/dist/{chunk-AA6YULYB.js → chunk-HNM2YQT2.js} +7 -7
  98. package/dist/{chunk-3C7EYRFZ.js → chunk-HYU5VLGA.js} +1 -1
  99. package/dist/{chunk-XY3P7JW7.js → chunk-IDKCWSM5.js} +1032 -172
  100. package/dist/chunk-IFKEI4SA.js +237 -0
  101. package/dist/{chunk-F7HYOUFF.js → chunk-IGOE6KPG.js} +67 -22
  102. package/dist/{chunk-TDD55CJH.js → chunk-IH2XURVQ.js} +6 -5
  103. package/dist/chunk-INQHESFB.js +38 -0
  104. package/dist/chunk-IP74LCOQ.js +57 -0
  105. package/dist/chunk-IPHHDDC6.js +45 -0
  106. package/dist/{chunk-T6AIP7H5.js → chunk-ISUAAMGS.js} +110 -153
  107. package/dist/{chunk-A7UPUQFY.js → chunk-IUPG3UKI.js} +624 -319
  108. package/dist/{chunk-L6SGQIR3.js → chunk-IVBOQAGB.js} +7 -3
  109. package/dist/{chunk-27NKUS3X.js → chunk-J2JAKCIW.js} +2205 -796
  110. package/dist/{chunk-V2YTAIQO.js → chunk-J3IOLXWM.js} +24 -7
  111. package/dist/chunk-JA5FI3QV.js +142 -0
  112. package/dist/chunk-JAVI7JSP.js +49 -0
  113. package/dist/{chunk-HLJAXMM4.js → chunk-JB5LINEM.js} +6 -2
  114. package/dist/{chunk-2VHF7OXM.js → chunk-JENSEMXF.js} +1 -1
  115. package/dist/{chunk-6BVONWJ7.js → chunk-JJF6HALY.js} +4 -3
  116. package/dist/chunk-JLUCKAEL.js +38 -0
  117. package/dist/chunk-JRBFANFE.js +221 -0
  118. package/dist/{chunk-R6XF6UR3.js → chunk-KDNTAPLX.js} +4 -4
  119. package/dist/chunk-KFAVQZSX.js +33 -0
  120. package/dist/chunk-KJXD4PNL.js +50 -0
  121. package/dist/chunk-KN5BXMWN.js +44 -0
  122. package/dist/{chunk-PCVDV3UZ.js → chunk-KQGURXFU.js} +9 -7
  123. package/dist/{chunk-WY6PP6AA.js → chunk-KXITGYLF.js} +40 -6
  124. package/dist/{chunk-66YVAGGM.js → chunk-L27SV425.js} +22 -20
  125. package/dist/{chunk-NE2W3OO7.js → chunk-LDLL7LB3.js} +24 -22
  126. package/dist/chunk-LFGTYX5U.js +379 -0
  127. package/dist/{chunk-EYWIDJFU.js → chunk-LGBMTVNT.js} +7 -5
  128. package/dist/{chunk-Q5AMFEWD.js → chunk-LGFL54FP.js} +3 -2
  129. package/dist/{chunk-6TACQN3N.js → chunk-LJXMYKZJ.js} +7 -5
  130. package/dist/{chunk-BKVWTZ6A.js → chunk-LKOR5UET.js} +29 -26
  131. package/dist/chunk-LNBQOP7D.js +294 -0
  132. package/dist/{chunk-O3WI5KWW.js → chunk-LO6P2VYG.js} +2 -2
  133. package/dist/chunk-LQJPN536.js +122 -0
  134. package/dist/{chunk-YEDDOYSC.js → chunk-LYBNHMVZ.js} +29 -5
  135. package/dist/{chunk-A3GY6SOA.js → chunk-LZ7NUC62.js} +20 -18
  136. package/dist/chunk-M32BLWDH.js +37 -0
  137. package/dist/{chunk-VYDBJ73M.js → chunk-M3NANSDZ.js} +17 -15
  138. package/dist/chunk-MFHL5URL.js +31 -0
  139. package/dist/chunk-MFR5OPBD.js +109 -0
  140. package/dist/{chunk-DE76JROX.js → chunk-MG4ZL5RW.js} +32 -20
  141. package/dist/{chunk-N7IXEHK3.js → chunk-MO6P2JZH.js} +10 -8
  142. package/dist/{chunk-65NYPGRC.js → chunk-MUWDB4R4.js} +1 -1
  143. package/dist/chunk-MZCGSXVR.js +34 -0
  144. package/dist/{chunk-MGYZM27B.js → chunk-N3XIYPU3.js} +51 -23
  145. package/dist/chunk-NDCF4R6S.js +172 -0
  146. package/dist/{chunk-DHPACDG7.js → chunk-NR2UJWOC.js} +10 -8
  147. package/dist/chunk-NXJ5URP6.js +51 -0
  148. package/dist/{chunk-UNM6ZOKP.js → chunk-NY2XSR5K.js} +147 -18
  149. package/dist/{chunk-5GDRCDQB.js → chunk-O45HFTFS.js} +3 -2
  150. package/dist/{chunk-ONLS5QA5.js → chunk-O4GXGGBX.js} +14 -12
  151. package/dist/chunk-O5PXQR67.js +249 -0
  152. package/dist/{chunk-PRPPGYZG.js → chunk-O66DM6DJ.js} +6 -4
  153. package/dist/{chunk-KEM4SDDP.js → chunk-O7FDLW7L.js} +11 -8
  154. package/dist/{chunk-T2UYZSCQ.js → chunk-O7XLYEFA.js} +169 -29
  155. package/dist/{chunk-FITWGOJ2.js → chunk-OHNCWBKF.js} +4 -4
  156. package/dist/{chunk-652QWTLZ.js → chunk-OHVTTOPS.js} +3 -3
  157. package/dist/chunk-OMI6FPPN.js +43 -0
  158. package/dist/chunk-ONUPY3H6.js +53 -0
  159. package/dist/chunk-OTLDSZDV.js +69 -0
  160. package/dist/chunk-OUBGGKDU.js +22 -0
  161. package/dist/chunk-OWNCM4ZN.js +330 -0
  162. package/dist/{chunk-YISXLPHN.js → chunk-P2V7QW4D.js} +235 -35
  163. package/dist/{chunk-CNUKCS2T.js → chunk-P32RUW3B.js} +1632 -93
  164. package/dist/{chunk-BWZ77RQT.js → chunk-PAQ2AS5E.js} +1 -1
  165. package/dist/{chunk-ZMAINWBW.js → chunk-PBPBMP2B.js} +4 -3
  166. package/dist/{chunk-ZVM6AKKM.js → chunk-PJSJPMJI.js} +3 -3
  167. package/dist/{chunk-EFHQ3TWP.js → chunk-PKBHS4AA.js} +233 -56
  168. package/dist/{chunk-GT4I4ZWM.js → chunk-PMUAJGLI.js} +20 -16
  169. package/dist/chunk-PPD7HTDP.js +152 -0
  170. package/dist/chunk-PSOFBQWB.js +42 -0
  171. package/dist/{chunk-S2A7YC3Q.js → chunk-Q2TECDIT.js} +10694 -8896
  172. package/dist/chunk-Q4K5WB2W.js +42 -0
  173. package/dist/{chunk-UGLN7QHV.js → chunk-Q6DFNVRJ.js} +14 -12
  174. package/dist/chunk-QA4O2GGC.js +265 -0
  175. package/dist/chunk-QAXW2N3B.js +1216 -0
  176. package/dist/chunk-QBNWF5ZU.js +19 -0
  177. package/dist/chunk-QFWA2DPH.js +224 -0
  178. package/dist/{chunk-23W7VWKR.js → chunk-QKUJMING.js} +1363 -114
  179. package/dist/{chunk-NXFPS2LR.js → chunk-R44CYFPV.js} +16 -13
  180. package/dist/{chunk-CKSITVS4.js → chunk-R7NYHMUU.js} +2687 -786
  181. package/dist/chunk-REWWDVQB.js +162 -0
  182. package/dist/{chunk-JMSCMUCA.js → chunk-RGERPOIV.js} +145 -21
  183. package/dist/{chunk-6JCPI55B.js → chunk-RLDGWLQ7.js} +49 -7
  184. package/dist/chunk-RUGXBPWI.js +384 -0
  185. package/dist/{chunk-CPA6HPVR.js → chunk-RXPJMEOW.js} +18 -12
  186. package/dist/{chunk-3HI6Z55Y.js → chunk-S4PMJU6H.js} +339 -43
  187. package/dist/chunk-S5LSDXV4.js +40 -0
  188. package/dist/chunk-S7FCV7S5.js +51 -0
  189. package/dist/{chunk-5GGOLAKC.js → chunk-SDKLKB6I.js} +28 -26
  190. package/dist/chunk-SFSPGFCV.js +62 -0
  191. package/dist/{chunk-ZNCEKR4J.js → chunk-SFUP7TTO.js} +12 -8
  192. package/dist/{chunk-64EQYWCA.js → chunk-SLXZOMA6.js} +69 -19
  193. package/dist/chunk-SQV37XO2.js +273 -0
  194. package/dist/{chunk-K65BJIMT.js → chunk-SU74NDMQ.js} +19 -17
  195. package/dist/chunk-SYV5LHML.js +165 -0
  196. package/dist/chunk-T4YPZIQN.js +36 -0
  197. package/dist/chunk-T6DQR5TV.js +323 -0
  198. package/dist/{chunk-JIITQXFB.js → chunk-THDMSZ3R.js} +3075 -802
  199. package/dist/{chunk-JGZCG5VM.js → chunk-TNR75OXT.js} +7 -7
  200. package/dist/{chunk-D3PO4DOZ.js → chunk-TUULXQRW.js} +174 -15
  201. package/dist/chunk-TYQBFTBR.js +516 -0
  202. package/dist/chunk-U76OWS3Q.js +49 -0
  203. package/dist/{chunk-YYQN46FY.js → chunk-UCROWG2D.js} +27 -24
  204. package/dist/{chunk-6M5NK3K3.js → chunk-UF4AZOZB.js} +50 -10
  205. package/dist/chunk-UF4GLFBX.js +345 -0
  206. package/dist/{chunk-OT2O76OS.js → chunk-UGUP3ZM6.js} +240 -39
  207. package/dist/chunk-UNAGJYET.js +45 -0
  208. package/dist/{chunk-5WG5YP4T.js → chunk-UOR2DTHH.js} +16 -14
  209. package/dist/{chunk-S3QNRWYR.js → chunk-UPOY4YSQ.js} +14 -12
  210. package/dist/{chunk-EPTBZ3GP.js → chunk-UPS2DGFC.js} +1 -1
  211. package/dist/{chunk-4AK7IBVP.js → chunk-UUU4CLJG.js} +4 -2
  212. package/dist/{chunk-W3BQAZ3Q.js → chunk-UWMJKX5U.js} +14 -12
  213. package/dist/{chunk-QZSX3F7C.js → chunk-UWOJLHFT.js} +27 -23
  214. package/dist/chunk-VBTP56CE.js +535 -0
  215. package/dist/chunk-VCTWPLAO.js +211 -0
  216. package/dist/chunk-VNU26RCO.js +1215 -0
  217. package/dist/{chunk-CSZ4C5N6.js → chunk-VTZMAJ3W.js} +93 -10
  218. package/dist/{chunk-TXLH6WHQ.js → chunk-VW62D6QE.js} +14 -12
  219. package/dist/{chunk-CAQZUNOH.js → chunk-VY72R2XZ.js} +610 -3
  220. package/dist/{chunk-2DIQQJCB.js → chunk-VZALIKIF.js} +18 -15
  221. package/dist/{chunk-TYAID7B7.js → chunk-W62H2XSO.js} +25 -23
  222. package/dist/{chunk-HVUPKCGJ.js → chunk-W7VWI2YH.js} +9 -6
  223. package/dist/{chunk-TANFV3MZ.js → chunk-WERM6KPJ.js} +7 -4
  224. package/dist/{chunk-BOHJZTEQ.js → chunk-WF7B67YE.js} +208 -87
  225. package/dist/{chunk-7FBGJFEE.js → chunk-WG5U3F5V.js} +14 -12
  226. package/dist/{chunk-4NDAAFQT.js → chunk-WJF2CS6M.js} +8 -5
  227. package/dist/{chunk-XAKWMCXI.js → chunk-WKUJ3KPV.js} +4 -4
  228. package/dist/chunk-WMTRHUI5.js +103 -0
  229. package/dist/chunk-WNKPRSGI.js +36 -0
  230. package/dist/chunk-WXFLMQSR.js +223 -0
  231. package/dist/{chunk-RRTKKSLQ.js → chunk-X3WY6MN3.js} +8 -6
  232. package/dist/{chunk-ZCJDOK2S.js → chunk-X4CXA2IL.js} +47 -23
  233. package/dist/{chunk-UBBC2XSG.js → chunk-X5F3Q5LV.js} +560 -105
  234. package/dist/{chunk-C6EKFH7J.js → chunk-XD75VZIH.js} +9 -7
  235. package/dist/chunk-XERYFDB5.js +297 -0
  236. package/dist/chunk-XI2LCCYQ.js +420 -0
  237. package/dist/chunk-Y2EQNWKV.js +933 -0
  238. package/dist/{chunk-LR3R6Z2A.js → chunk-YHCMZC2J.js} +7 -7
  239. package/dist/chunk-YYY3AZFE.js +122 -0
  240. package/dist/{chunk-GDLULHTC.js → chunk-Z5IA3WDR.js} +19 -31
  241. package/dist/{chunk-T6XQL73F.js → chunk-ZFFYNRIO.js} +63 -23
  242. package/dist/{chunk-EDS2L2LD.js → chunk-ZFL7NY4F.js} +56 -27
  243. package/dist/{chunk-YMZ3TNYB.js → chunk-ZGULQAPZ.js} +7 -4
  244. package/dist/{chunk-4SKZDI62.js → chunk-ZIUFM5HT.js} +3 -1
  245. package/dist/{chunk-DKJA76CJ.js → chunk-ZJFRMSJT.js} +15 -13
  246. package/dist/chunk-ZJXLNDH4.js +239 -0
  247. package/dist/chunk-ZQ3ON2VQ.js +252 -0
  248. package/dist/{chunk-HITVBIJS.js → chunk-ZQ7SKESZ.js} +150 -415
  249. package/dist/{chunk-6QSERWXH.js → chunk-ZQXZ2SES.js} +24 -21
  250. package/dist/chunk-ZX57KSEQ.js +39 -0
  251. package/dist/{chunk-I3E7CX4H.js → chunk-ZZK56BLP.js} +5 -3
  252. package/dist/files.json +664 -368
  253. package/dist/packages/naysayer/constitution/constitution.yaml +19 -0
  254. package/dist/packages/sdk/npm/index.js +6194 -2542
  255. package/dist/packages/ui/dist/assets/{elk-api-tma0MCuh.js → elk-api-CIf-0gz5.js} +2 -2
  256. package/dist/packages/ui/dist/assets/index-CoK_Dl0D.js +47 -0
  257. package/dist/packages/ui/dist/assets/index-Dw20IRhQ.css +1 -0
  258. package/dist/packages/ui/dist/index.html +3 -2
  259. package/package.json +5 -5
  260. package/dist/chunk-2BF2EYJV.js +0 -174
  261. package/dist/chunk-42EVSKME.js +0 -37
  262. package/dist/chunk-4BKGMUIK.js +0 -53
  263. package/dist/chunk-5KLGBXG5.js +0 -48
  264. package/dist/chunk-6BNC6EQ4.js +0 -41
  265. package/dist/chunk-6D2CQ5II.js +0 -41
  266. package/dist/chunk-7AVHLZGD.js +0 -45
  267. package/dist/chunk-D46WUV3T.js +0 -33
  268. package/dist/chunk-E36QOJMD.js +0 -47
  269. package/dist/chunk-FQXYPF7E.js +0 -147
  270. package/dist/chunk-GBRFVT63.js +0 -45
  271. package/dist/chunk-ICY36XCH.js +0 -34
  272. package/dist/chunk-J4K3KVLD.js +0 -131
  273. package/dist/chunk-JZU3H6UQ.js +0 -54
  274. package/dist/chunk-NN2ZQEB4.js +0 -38
  275. package/dist/chunk-PCOCACY3.js +0 -385
  276. package/dist/chunk-RTEYOSJY.js +0 -29
  277. package/dist/chunk-RUVFXE2L.js +0 -666
  278. package/dist/chunk-RVH5SUW5.js +0 -69
  279. package/dist/chunk-VN3DP4LQ.js +0 -43
  280. package/dist/chunk-W5BVQF44.js +0 -36
  281. package/dist/chunk-WVPJSRRJ.js +0 -52
  282. package/dist/chunk-XPTOBS2D.js +0 -48
  283. package/dist/packages/ui/dist/assets/index-B54fm9FQ.js +0 -29
  284. package/dist/packages/ui/dist/assets/index-DD_hufQz.css +0 -1
@@ -3,30 +3,57 @@ 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-CQDGOPGH.js";
6
9
  import {
7
10
  globToRegExp
8
- } from "./chunk-GQ4N4WKF.js";
11
+ } from "./chunk-3NFFJL5D.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
+ decodeText,
29
+ factIdPart,
30
+ forwardSlashes,
31
+ gatewayBundleTest,
32
+ isBinaryText,
33
+ isLfsPointer,
34
+ isPatternPath,
16
35
  isSecretPath,
17
36
  lineNamesClaim,
37
+ linksFor,
38
+ listRepoFilesSync,
18
39
  normalizePath,
40
+ notDeviceName,
41
+ noteProblem,
19
42
  partsBuildWord,
20
43
  partsNamePath,
44
+ plainPathPrefix,
45
+ quoteHoldsSecret,
21
46
  quoteMatches,
22
47
  quoteNotRequired,
23
48
  redactSecrets,
24
49
  staleCite,
25
- toCanonicalYaml
26
- } from "./chunk-S2A7YC3Q.js";
50
+ toCanonicalYaml,
51
+ tokenUnits,
52
+ underFolders
53
+ } from "./chunk-Q2TECDIT.js";
27
54
 
28
55
  // packages/verify/dist/source.js
29
- import { readdirSync, readFileSync, statSync } from "node:fs";
56
+ import { lstatSync, readFileSync, realpathSync, statSync } from "node:fs";
30
57
  import { isAbsolute, join, normalize, relative, sep } from "node:path";
31
58
  var SKIP_DIRS = /* @__PURE__ */ new Set([
32
59
  ".git",
@@ -38,6 +65,13 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
38
65
  ".vscode",
39
66
  ".gradle",
40
67
  ".mvn",
68
+ "__pycache__",
69
+ ".venv",
70
+ "venv",
71
+ ".next",
72
+ ".nuxt"
73
+ ]);
74
+ var BUILD_OUTPUT = /* @__PURE__ */ new Set([
41
75
  "target",
42
76
  "build",
43
77
  "dist",
@@ -45,62 +79,92 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
45
79
  "bin",
46
80
  "obj",
47
81
  "vendor",
48
- "__pycache__",
49
- ".venv",
50
- "venv",
51
- ".next",
52
- ".nuxt",
53
82
  "coverage"
54
83
  ]);
84
+ var DOTNET = /\.(csproj|fsproj|vbproj|sln)$/i;
85
+ var NODE = /^package\.json$/;
86
+ var MADE_BESIDE = {
87
+ bin: DOTNET,
88
+ obj: DOTNET,
89
+ vendor: /^(go\.mod|composer\.json|Gemfile)$/,
90
+ target: /^(pom\.xml|build\.sbt|Cargo\.toml)$/,
91
+ build: /^(build\.gradle(\.kts)?|settings\.gradle(\.kts)?|package\.json|CMakeLists\.txt)$/,
92
+ dist: NODE,
93
+ coverage: NODE,
94
+ out: /^(tsconfig\.json|package\.json)$|\.iml$/
95
+ };
96
+ var ONLY_BESIDE = /* @__PURE__ */ new Set(["bin", "obj", "vendor"]);
97
+ function skippedFolder(above, name, siblings) {
98
+ if (SKIP_DIRS.has(name))
99
+ return true;
100
+ if (!BUILD_OUTPUT.has(name))
101
+ return false;
102
+ if (siblings.some((x) => MADE_BESIDE[name].test(x)))
103
+ return true;
104
+ if (above.some((a) => a.toLowerCase() === "src"))
105
+ return false;
106
+ return !ONLY_BESIDE.has(name);
107
+ }
55
108
  function insideRepo(path) {
56
- if (path === "" || isAbsolute(path) || /^[A-Za-z]:/.test(path))
109
+ const p = path.replace(/\\/g, "/");
110
+ if (p === "" || p.includes(":") || isAbsolute(p) || p.startsWith("/"))
57
111
  return false;
58
- const n = normalize(path).split(sep).join("/");
112
+ if (p.split("/").some((x) => x !== "." && x !== ".." && /[. ]$/.test(x)))
113
+ return false;
114
+ const n = normalize(p).split(sep).join("/");
59
115
  return n !== ".." && !n.startsWith("../");
60
116
  }
61
117
  function dirSource(root) {
62
118
  let cache;
63
- const walk = (dir, out) => {
64
- let names;
119
+ let realRoot;
120
+ const fileAt = (path) => {
121
+ if (!insideRepo(path))
122
+ return void 0;
123
+ const full = join(root, ...path.split("/"));
65
124
  try {
66
- names = readdirSync(dir).sort();
125
+ if (!lstatSync(full).isFile())
126
+ return void 0;
127
+ realRoot ??= realpathSync(root);
128
+ const r = relative(realRoot, realpathSync(full));
129
+ if (!r || r.startsWith("..") || isAbsolute(r))
130
+ return void 0;
131
+ return full;
67
132
  } catch {
68
- return;
69
- }
70
- for (const name of names) {
71
- const full = join(dir, name);
72
- let st;
73
- try {
74
- st = statSync(full);
75
- } catch {
76
- continue;
77
- }
78
- if (st.isDirectory()) {
79
- if (!SKIP_DIRS.has(name))
80
- walk(full, out);
81
- } else if (st.isFile())
82
- out.push(relative(root, full).split(sep).join("/"));
133
+ return void 0;
83
134
  }
84
135
  };
85
136
  return {
86
137
  list() {
87
- if (!cache) {
88
- cache = [];
89
- walk(root, cache);
90
- }
138
+ cache ??= listRepoFilesSync(root, { skipFolder: skippedFolder });
91
139
  return cache;
92
140
  },
93
141
  read(path) {
94
- if (!insideRepo(path))
142
+ const full = fileAt(path);
143
+ if (!full)
144
+ return void 0;
145
+ try {
146
+ return decodeText(readFileSync(full));
147
+ } catch {
148
+ return void 0;
149
+ }
150
+ },
151
+ size(path) {
152
+ const full = fileAt(path);
153
+ if (!full)
95
154
  return void 0;
96
155
  try {
97
- return readFileSync(join(root, ...path.split("/")), "utf8");
156
+ return statSync(full).size;
98
157
  } catch {
99
158
  return void 0;
100
159
  }
101
160
  }
102
161
  };
103
162
  }
163
+ var MAX_SOURCE_BYTES = 5 * 1024 * 1024;
164
+ function tooBigToRead(source, path) {
165
+ const n = source.size?.(path);
166
+ return n !== void 0 && n > MAX_SOURCE_BYTES;
167
+ }
104
168
 
105
169
  // packages/verify/dist/frameworks.js
106
170
  var FRAMEWORKS = [
@@ -121,6 +185,11 @@ var FRAMEWORKS = [
121
185
  var KNOWN = [
122
186
  // Java and Kotlin
123
187
  ["spring-boot*", { name: "spring-boot", topic: "spring", shape: "rest" }],
188
+ // the annotations a Spring Boot app's main class carries, which a reader may cite instead
189
+ // (matched by `annotationKey`, so bare, qualified or with arguments)
190
+ ["@springbootapplication", { name: "spring-boot", topic: "spring", shape: "rest" }],
191
+ ["@enableautoconfiguration", { name: "spring-boot", topic: "spring", shape: "rest" }],
192
+ ["@quarkusmain", { name: "quarkus", topic: "quarkus" }],
124
193
  ["spring-webmvc", { name: "spring", topic: "spring", shape: "rest" }],
125
194
  ["spring-webflux", { name: "spring", topic: "spring", shape: "rest" }],
126
195
  ["spring-web", { name: "spring", topic: "spring", shape: "rest" }],
@@ -172,23 +241,45 @@ function declarationKey(declared) {
172
241
  const name = colon.split(/[=<>~!,\s[]/)[0] ?? colon;
173
242
  return name.replace(/^['"]|['"]$/g, "");
174
243
  }
175
- function frameworkOf(declared) {
244
+ function annotationKey(declared) {
245
+ const m = /^@?(?:[a-z_$][\w$]*\.)*([A-Z][\w$]*)\s*(?:\(.*)?$/s.exec(declared.trim());
246
+ return m ? `@${m[1].toLowerCase()}` : void 0;
247
+ }
248
+ function houseKnown(extra) {
249
+ return extra.map((e) => [
250
+ e.declares.toLowerCase(),
251
+ {
252
+ name: e.name,
253
+ ...e.topic && FRAMEWORKS.includes(e.topic) ? { topic: e.topic } : {},
254
+ ...e.shape ? { shape: e.shape } : {}
255
+ }
256
+ ]);
257
+ }
258
+ function frameworkOf(declared, extra = []) {
176
259
  const key = declarationKey(declared);
177
260
  if (!key)
178
261
  return void 0;
179
- for (const [pattern, known] of KNOWN) {
262
+ const table = extra.length ? [...houseKnown(extra), ...KNOWN] : KNOWN;
263
+ for (const [pattern, known] of table) {
180
264
  const star = pattern.endsWith("*");
181
265
  const p = star ? pattern.slice(0, -1) : pattern;
182
266
  if (star ? key.startsWith(p) : key === p)
183
267
  return known;
184
268
  }
269
+ const ann = annotationKey(declared);
270
+ if (ann) {
271
+ for (const [pattern, known] of table)
272
+ if (pattern === ann)
273
+ return known;
274
+ }
185
275
  return void 0;
186
276
  }
187
- function frameworkNameProblem(f) {
188
- const known = frameworkOf(f.cite.value);
277
+ function frameworkNameProblem(f, extra = []) {
278
+ const known = frameworkOf(f.cite.value, extra);
189
279
  const name = f.name.trim().toLowerCase();
190
280
  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}`;
281
+ const legacy = annotationKey(f.cite.value) === `@${name}`;
282
+ 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
283
  }
193
284
  const key = declarationKey(f.cite.value);
194
285
  if (declarationKey(name) === key || name === key)
@@ -196,13 +287,13 @@ function frameworkNameProblem(f) {
196
287
  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
288
  }
198
289
  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) {
290
+ function frameworkTopics(facts, extra = []) {
200
291
  const fws = frameworkFacts(facts);
201
292
  if (!fws.length)
202
293
  return void 0;
203
294
  const topics = /* @__PURE__ */ new Set();
204
295
  for (const f of fws) {
205
- const known = frameworkOf(f.cite.value);
296
+ const known = frameworkOf(f.cite.value, extra);
206
297
  if (!known?.topic)
207
298
  topics.add("house");
208
299
  else
@@ -228,7 +319,7 @@ var SHAPE_ASK = {
228
319
  store: "which tables it reads and writes"
229
320
  };
230
321
  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) {
322
+ function frameworkGaps(facts, extra = []) {
232
323
  const fws = frameworkFacts(facts);
233
324
  if (!fws.length)
234
325
  return [];
@@ -236,7 +327,7 @@ function frameworkGaps(facts) {
236
327
  return [];
237
328
  const out = [];
238
329
  for (const f of fws) {
239
- const shape2 = frameworkOf(f.cite.value)?.shape;
330
+ const shape2 = frameworkOf(f.cite.value, extra)?.shape;
240
331
  if (!shape2 || holdsShape(facts, shape2))
241
332
  continue;
242
333
  out.push({
@@ -249,9 +340,14 @@ function frameworkGaps(facts) {
249
340
  }
250
341
  return out.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
251
342
  }
252
- function frameworksToLearn(facts, known = []) {
343
+ function isApigeeBundle(f) {
344
+ const file = f.cite.file.replace(/\\/g, "/").toLowerCase();
345
+ const said = `${f.cite.value} ${f.cite.quote ?? ""}`;
346
+ return /<\s*(APIProxy|SharedFlowBundle)\b/i.test(said) || /^(apiproxy|sharedflowbundle)$/i.test(f.cite.value.trim()) || /(^|\/)(apiproxy|sharedflowbundle|sharedflows)\//.test(file);
347
+ }
348
+ function frameworksToLearn(facts, known = [], extra = []) {
253
349
  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) => ({
350
+ 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
351
  id: f.id,
256
352
  framework: f.name,
257
353
  cite: `${f.cite.file}:${f.cite.line}`,
@@ -260,6 +356,88 @@ function frameworksToLearn(facts, known = []) {
260
356
  }));
261
357
  }
262
358
 
359
+ // packages/verify/dist/reviewed.js
360
+ var LINE_RANGE = /^(.+)#L(\d+)-L(\d+)$/;
361
+ function lineRangeOf(path) {
362
+ const m = LINE_RANGE.exec(path);
363
+ return m ? { file: m[1], from: Number(m[2]), to: Number(m[3]) } : void 0;
364
+ }
365
+ var SHAPE = `{"path": "<file or glob>", "why": "..."}`;
366
+ function parseReviewed(text) {
367
+ const entries = [];
368
+ const bad = [];
369
+ text.split(/\r?\n/).forEach((raw, i) => {
370
+ if (!raw.trim())
371
+ return;
372
+ const line = i + 1;
373
+ let json2;
374
+ try {
375
+ json2 = JSON.parse(raw);
376
+ } catch (e) {
377
+ bad.push({ line, raw, message: `line ${line} is not JSON: ${e.message}` });
378
+ return;
379
+ }
380
+ const o = json2;
381
+ const issues = [];
382
+ if (!o || typeof o !== "object" || Array.isArray(o))
383
+ issues.push("not an object");
384
+ else {
385
+ if (typeof o.path !== "string" || !o.path.trim())
386
+ issues.push("path: a file or glob");
387
+ if (typeof o.why !== "string" || !o.why.trim())
388
+ issues.push("why: why it holds nothing");
389
+ const extra = Object.keys(o).filter((k) => k !== "path" && k !== "why");
390
+ if (extra.length)
391
+ issues.push(`unknown keys: ${extra.join(", ")}`);
392
+ }
393
+ if (issues.length) {
394
+ bad.push({ line, raw, message: `line ${line} isn't ${SHAPE}: ${issues.join("; ")}` });
395
+ return;
396
+ }
397
+ const path = forwardSlashes(o.path.trim());
398
+ entries.push({ line, path, why: o.why.trim() });
399
+ });
400
+ return { entries, bad };
401
+ }
402
+ var isGlob = (path) => /[*?]/.test(path);
403
+
404
+ // packages/verify/dist/conventions.js
405
+ var SPEC_NAME = /(^|\/)[^/]*(openapi|swagger|asyncapi|api-docs)[^/]*\.(ya?ml|json|xml)$|\.oas\.(ya?ml|json)$/i;
406
+ function notReadTest(folders) {
407
+ const multi = folders.filter((f) => f.includes("/"));
408
+ const multiRe = underFolders(multi);
409
+ const names = new Set(folders.filter((f) => !f.includes("/")).map((f) => f.toLowerCase()));
410
+ return {
411
+ test(path) {
412
+ if (multiRe.test(path))
413
+ return true;
414
+ const parts = path.split("/").slice(0, -1).map((x) => x.toLowerCase());
415
+ const src = parts.indexOf("src");
416
+ return parts.some((x, j) => names.has(x) && !(src >= 0 && j > src && BUILD_OUTPUT.has(x)));
417
+ }
418
+ };
419
+ }
420
+ function rulesOf(c) {
421
+ const folders = underFolders(c.apiSpecFolders, { tail: "(.+/)?[^/]+\\.(ya?ml|json)$" });
422
+ return {
423
+ apiSpec: new RegExp(`${SPEC_NAME.source}|${folders.source}`, "i"),
424
+ notProduct: underFolders(c.notProductFolders),
425
+ notRead: notReadTest(c.notReadFolders),
426
+ gatewayBundle: gatewayBundleTest(c.gatewayBundleFolders),
427
+ frameworks: c.frameworks
428
+ };
429
+ }
430
+ var DEFAULT_READ_RULES = rulesOf(DEFAULT_CONVENTIONS);
431
+ var made = /* @__PURE__ */ new WeakMap();
432
+ function readRulesOf(c) {
433
+ if (!c || c === DEFAULT_CONVENTIONS)
434
+ return DEFAULT_READ_RULES;
435
+ let r = made.get(c);
436
+ if (!r)
437
+ made.set(c, r = rulesOf(c));
438
+ return r;
439
+ }
440
+
263
441
  // packages/verify/dist/apigee-policy.js
264
442
  var MAKES = {
265
443
  OAuthV1: ["oauth2"],
@@ -327,9 +505,77 @@ function authPolicyMismatch(auth, file, policy, lines) {
327
505
  return MAKES_NONE.has(found.element) ? found : void 0;
328
506
  }
329
507
  var authChecks = (auth) => CHECKS[auth] ?? auth;
508
+ var bare = (p) => p.trim().replace(/\/+$/, "");
509
+ function apigeePassThrough(o, file, lines, line, path) {
510
+ if (!bundleRootOf(file))
511
+ return false;
512
+ const p = path.trim();
513
+ const under4 = p.endsWith("/**") ? bare(p.slice(0, -3)) : void 0;
514
+ const near = lines.slice(Math.max(0, line - 2), line + 1);
515
+ const element = (tag) => near.flatMap((l) => {
516
+ const m = new RegExp(`<${tag}>\\s*([^<]*?)\\s*</${tag}>`).exec(l);
517
+ return m ? [m[1]] : [];
518
+ });
519
+ if (o.kind === "entry")
520
+ return under4 !== void 0 && element("BasePath").some((b) => bare(b) === under4);
521
+ if (!o.forwards)
522
+ return false;
523
+ const target = under4 ?? bare(p);
524
+ const urlPath = (u) => bare(u.replace(/^[a-z][a-z0-9+.-]*:\/\/[^/]*/i, ""));
525
+ return element("URL").some((u) => urlPath(u) === target) || element("Path").some((t) => bare(t) === target) || target === "" && near.some((l) => /<RouteRule[\s>]/.test(l));
526
+ }
527
+
528
+ // packages/verify/dist/call-security-lines.js
529
+ var CLIENT_CERT = /client[-_ .]?cert|keystore|key[-_ .]?store|ssl[-_ .]?bundle|\.pfx\b|\.p12\b|pkcs12|\bpfx\b|\bp12\b/i;
530
+ var CERT_AND_KEY = (l) => /\bcert\b/i.test(l) && /\bkey\b/i.test(l);
531
+ var KINDS = {
532
+ "tls mtls": {
533
+ test: (l) => CLIENT_CERT.test(l) || CERT_AND_KEY(l),
534
+ needs: "a client certificate, a key store or an SSL bundle"
535
+ },
536
+ "tls tls": { test: (l) => /https|ssl|tls/i.test(l), needs: "https, SSL or TLS" },
537
+ "tls none": {
538
+ test: (l) => !/https:\/\//i.test(l) && (/http:\/\//i.test(l) || /\b(false|disabled?|insecure|plain-?text|off)\b/i.test(l)),
539
+ needs: "a plain http:// URL or a TLS switch set off"
540
+ },
541
+ "auth oauth2": {
542
+ test: (l) => /oauth|token|bearer|jwt|authori[sz]|client[-_ ]?credentials|access[-_ ]?token/i.test(l),
543
+ needs: "a token (OAuth, bearer, JWT or an Authorization header)"
544
+ },
545
+ "auth api-key": {
546
+ test: (l) => /api[-_ ]?key|apikey|x-api|subscription[-_ ]?key|[a-z0-9][-_]key\b/i.test(l),
547
+ needs: "a key"
548
+ },
549
+ "auth basic": {
550
+ test: (l) => /basic|password|passwd|username|credential/i.test(l),
551
+ needs: "a password or basic credentials"
552
+ },
553
+ "auth mtls": {
554
+ test: (l) => CLIENT_CERT.test(l) || CERT_AND_KEY(l),
555
+ needs: "a client certificate, a key store or an SSL bundle"
556
+ }
557
+ };
558
+ function securityLineKind(what, claim, lines) {
559
+ const k = KINDS[`${what} ${claim}`];
560
+ if (!k)
561
+ return void 0;
562
+ return lines.some((l) => k.test(l)) ? void 0 : k.needs;
563
+ }
564
+ function lineOfValue(lines, line, value) {
565
+ if (!lines)
566
+ return void 0;
567
+ const v = value.toLowerCase();
568
+ for (const n of [line, line - 1, line + 1]) {
569
+ const l = lines[n - 1];
570
+ if (l !== void 0 && l.toLowerCase().includes(v))
571
+ return l;
572
+ }
573
+ return lines[line - 1];
574
+ }
330
575
 
331
576
  // packages/verify/dist/corrections.js
332
- function correctionFor(f, corrections) {
577
+ function correctionFor(f, all2) {
578
+ const corrections = all2.filter((c) => c.action !== "link" && c.action !== "unlink");
333
579
  const at = (c) => c.target.kind === f.kind && c.target.file === f.cite.file && c.target.line === f.cite.line;
334
580
  const byId = corrections.filter((c) => c.fact === f.id && at(c));
335
581
  const byCite = corrections.filter((c) => at(c) && c.target.value === f.cite.value);
@@ -337,6 +583,10 @@ function correctionFor(f, corrections) {
337
583
  const moved = edited.filter((c) => (c.line ?? c.target.line) === f.cite.line);
338
584
  return [...byId, ...byCite, ...moved].sort((a, b) => a.at < b.at ? 1 : a.at > b.at ? -1 : 0)[0];
339
585
  }
586
+ function outsidePairingFor(f, all2) {
587
+ const newest = linksFor(f, all2)[0];
588
+ return newest?.link?.system ? newest : void 0;
589
+ }
340
590
  function correctedFact(f, c) {
341
591
  const raw = JSON.parse(JSON.stringify(f));
342
592
  for (const [k, v] of Object.entries(c.set ?? {})) {
@@ -363,33 +613,64 @@ function applyCorrections(facts, corrections) {
363
613
  const applied = [];
364
614
  const used = /* @__PURE__ */ new Set();
365
615
  const stale = [];
366
- for (const f of facts) {
367
- const c = correctionFor(f, corrections);
368
- if (!c) {
369
- out.push(f);
616
+ const met = /* @__PURE__ */ new Set();
617
+ const withPairing = (f, as, over) => {
618
+ const p = outsidePairingFor(as, corrections);
619
+ if (!p)
620
+ return f;
621
+ if (over && over.at > p.at && over.set?.to !== void 0)
622
+ return f;
623
+ const fixed = correctedFact(f, { ...p, action: "edit", set: { to: p.link.system } });
624
+ if (typeof fixed === "string") {
625
+ stale.push({
626
+ correction: p,
627
+ why: "invalid",
628
+ 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`
629
+ });
630
+ return f;
631
+ }
632
+ applied.push({ fact: f.id, action: "link", by: p.by });
633
+ return { ...fixed, conf: "inferred" };
634
+ };
635
+ for (const f0 of facts) {
636
+ for (const l of linksFor(f0, corrections))
637
+ met.add(l);
638
+ const c = correctionFor(f0, corrections);
639
+ if (c?.action === "wrong") {
640
+ used.add(c);
641
+ applied.push({ fact: f0.id, action: "wrong", by: c.by });
370
642
  continue;
371
643
  }
372
- used.add(c);
373
- if (c.action === "wrong") {
374
- applied.push({ fact: f.id, action: "wrong", by: c.by });
644
+ if (!c) {
645
+ out.push(withPairing(f0, f0));
375
646
  continue;
376
647
  }
377
- const fixed = correctedFact(f, c);
648
+ used.add(c);
649
+ const fixed = correctedFact(f0, c);
378
650
  if (typeof fixed === "string") {
379
651
  stale.push({
380
652
  correction: c,
381
653
  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`
654
+ message: `${c.by}'s correction of ${f0.kind} ${f0.id} doesn't make a fact (${fixed}); the fact is kept as it was`
383
655
  });
384
- out.push(f);
656
+ out.push(withPairing(f0, f0));
385
657
  continue;
386
658
  }
387
- applied.push({ fact: f.id, action: "edit", by: c.by });
388
- out.push(fixed);
659
+ applied.push({ fact: f0.id, action: "edit", by: c.by });
660
+ out.push(withPairing(fixed, f0, c));
389
661
  }
390
662
  for (const c of corrections) {
391
663
  if (used.has(c))
392
664
  continue;
665
+ if (c.action === "link" || c.action === "unlink") {
666
+ if (!met.has(c))
667
+ stale.push({
668
+ correction: c,
669
+ why: "none",
670
+ 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`
671
+ });
672
+ continue;
673
+ }
393
674
  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
675
  if (replaced || c.action === "wrong")
395
676
  continue;
@@ -1199,10 +1480,11 @@ function isLocaleBundle(path) {
1199
1480
  }
1200
1481
  var DATA_FILE_LINES = 2e3;
1201
1482
  var DATA_FILE_CHARS = 2e5;
1483
+ var API_SPEC = DEFAULT_READ_RULES.apiSpec;
1202
1484
  var DATA_EXT = /\.(json|xml)$/i;
1203
1485
  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))
1486
+ function isDataFile(path, lines, rules = DEFAULT_READ_RULES) {
1487
+ if (!DATA_EXT.test(path) || KEEP_BIG.test(path) || rules.apiSpec.test(path))
1206
1488
  return false;
1207
1489
  if (lines.length - (lines[lines.length - 1] === "" ? 1 : 0) > DATA_FILE_LINES)
1208
1490
  return true;
@@ -1252,66 +1534,502 @@ function pageRouteMatches(file, lines, exists, path) {
1252
1534
  return norm(route) === norm(path);
1253
1535
  }
1254
1536
 
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())
1537
+ // packages/verify/dist/reachable.js
1538
+ var CODE = /\.(java|kt|kts|scala|groovy|cs|vb|fs|py|rb|go|rs|php|swift|ts|tsx|js|mjs|cjs|dart)$/i;
1539
+ var MODEL_DIR = /(^|\/)(models?|dtos?|payloads?|schemas?|pojos?|beans|viewmodels?)\//i;
1540
+ var MODEL_NAME = /(dto|request|response|payload|model|body)$/i;
1541
+ var MODEL_PART = /[._-](dto|model|schema|payload|request|response)\.[^/.]+$/i;
1542
+ var MAPS_TABLE = /@Entity\b|@Table\b|@Document\b|\[Table\(|__tablename__|models\.Model\b|@Entity\(|\bgorm\.Model\b/;
1543
+ var ROOT_PART = /[._-](controller|resolver|gateway|handlers?|router|routes?|consumer|listener|subscriber|client|publisher|producer|endpoints?)\.[^/.]+$/i;
1544
+ var CLIENT_NAME = /(client|gateway|publisher|producer|sender|feign|proxy|connector)$/i;
1545
+ var CLIENT_DIR = /(^|\/)(clients?|gateways?|integrations?|publishers?|producers?|outbound)\//i;
1546
+ var base = (path) => path.slice(path.lastIndexOf("/") + 1);
1547
+ var stem = (path) => {
1548
+ const b = base(path);
1549
+ const dot = b.indexOf(".");
1550
+ return dot > 0 ? b.slice(0, dot) : b;
1551
+ };
1552
+ function isModelFile(path, text) {
1553
+ if (!CODE.test(path) || readingRank(path) === 5)
1554
+ return false;
1555
+ if (!MODEL_DIR.test(path) && !MODEL_NAME.test(stem(path)) && !MODEL_PART.test(path))
1556
+ return false;
1557
+ return text === void 0 || !MAPS_TABLE.test(text);
1558
+ }
1559
+ var ENTRY_POINT_NAME = /(controller|resource|router|routes?|handlers?|listener|consumer|subscriber|endpoints?|servlet|webhook|resolver)$/i;
1560
+ var ENTRY_POINT_DIR = /(^|\/)(controllers?|routes|routers|handlers|endpoints|listeners|consumers)\//i;
1561
+ var ENTRY_POINT_PART = /[._-](controller|resolver|handlers?|router|routes?|consumer|listener|subscriber|endpoints?)\.[^/.]+$/i;
1562
+ function isEntryPointFile(path) {
1563
+ if (!CODE.test(path) || readingRank(path) === 5 || isModelFile(path))
1564
+ return false;
1565
+ return ENTRY_POINT_NAME.test(stem(path)) || ENTRY_POINT_DIR.test(path) || ENTRY_POINT_PART.test(path);
1566
+ }
1567
+ function isRootFile(path) {
1568
+ if (!CODE.test(path) || readingRank(path) === 5 || isModelFile(path))
1569
+ return false;
1570
+ return readingRank(path) === 2 || isEntryPointFile(path) || ROOT_PART.test(path) || CLIENT_NAME.test(stem(path)) || CLIENT_DIR.test(path);
1571
+ }
1572
+ 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;
1573
+ var IDENT = /[A-Za-z_][A-Za-z0-9_]*/g;
1574
+ function declaredTypes(path, text) {
1575
+ const out = [];
1576
+ const s = stem(path);
1577
+ if (/^[A-Z][A-Za-z0-9_]*$/.test(s))
1578
+ out.push(s);
1579
+ for (const m of text.matchAll(DECLARES)) {
1580
+ const n = m[1] ?? m[2];
1581
+ if (n && !out.includes(n))
1582
+ out.push(n);
1583
+ }
1584
+ return out;
1585
+ }
1586
+ function identifiers(text) {
1587
+ return [...new Set(text.match(IDENT) ?? [])];
1588
+ }
1589
+ 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";
1590
+ function reachableModels(o) {
1591
+ const texts = /* @__PURE__ */ new Map();
1592
+ const textOf = (p) => {
1593
+ if (!texts.has(p))
1594
+ texts.set(p, o.read(p) ?? "");
1595
+ return texts.get(p);
1596
+ };
1597
+ const ordered = readingOrder(o.paths);
1598
+ const models = ordered.filter((p) => isModelFile(p, textOf(p))).sort();
1599
+ const isModel = new Set(models);
1600
+ const byName = /* @__PURE__ */ new Map();
1601
+ const declared = /* @__PURE__ */ new Map();
1602
+ for (const p of models) {
1603
+ const names = declaredTypes(p, textOf(p));
1604
+ declared.set(p, names);
1605
+ for (const n of names)
1606
+ byName.set(n, [...byName.get(n) ?? [], p]);
1607
+ }
1608
+ const known = new Set(o.paths);
1609
+ const roots = [];
1610
+ const addRoot = (p) => {
1611
+ if (known.has(p) && !isModel.has(p) && !roots.includes(p))
1612
+ roots.push(p);
1613
+ };
1614
+ const facts = o.facts ?? [];
1615
+ const entries = o.from === "entries";
1616
+ const calls2 = new Set(facts.filter((f) => f.kind === "call").map((f) => f.id));
1617
+ for (const f of facts)
1618
+ if (f.kind === "entry" || f.kind === "call" && !entries)
1619
+ for (const p of citedFiles(f))
1620
+ addRoot(p);
1621
+ for (const p of ordered)
1622
+ if (entries ? isEntryPointFile(p) : isRootFile(p))
1623
+ addRoot(p);
1624
+ const reached = /* @__PURE__ */ new Map();
1625
+ const queue = [];
1626
+ const reach = (names, from, own = []) => {
1627
+ for (const n of names) {
1628
+ if (own.includes(n))
1629
+ continue;
1630
+ for (const p of byName.get(n) ?? []) {
1631
+ const r = reached.get(p);
1632
+ if (r) {
1633
+ if (r.from === from && !r.types.includes(n))
1634
+ r.types.push(n);
1635
+ continue;
1636
+ }
1637
+ reached.set(p, { path: p, types: [n], from });
1638
+ queue.push(p);
1639
+ }
1640
+ }
1641
+ };
1642
+ for (const p of roots)
1643
+ reach(identifiers(textOf(p)), p);
1644
+ for (const f of facts) {
1645
+ for (const p of citedFiles(f))
1646
+ if (isModel.has(p) && !reached.has(p)) {
1647
+ reached.set(p, { path: p, types: [...(declared.get(p) ?? []).slice(0, 1)], from: f.id });
1648
+ queue.push(p);
1649
+ }
1650
+ if (f.kind === "field" && !(entries && calls2.has(f.owner)))
1651
+ reach(identifiers([f.type ?? "", f.transform === "message type" ? f.name : ""].join(" ")), f.id);
1652
+ }
1653
+ const rooted = roots.length > 0 || reached.size > 0;
1654
+ if (!rooted)
1655
+ return {
1656
+ rooted,
1657
+ roots,
1658
+ reached: models.map((p) => ({ path: p, types: declared.get(p) ?? [], from: "" })),
1659
+ notReached: [],
1660
+ models: models.length
1661
+ };
1662
+ for (let i = 0; i < queue.length; i++) {
1663
+ const p = queue[i];
1664
+ reach(identifiers(textOf(p)), p, declared.get(p));
1665
+ }
1666
+ return {
1667
+ rooted,
1668
+ roots,
1669
+ reached: queue.map((p) => reached.get(p)),
1670
+ notReached: models.filter((p) => !reached.has(p)),
1671
+ models: models.length
1672
+ };
1673
+ }
1674
+
1675
+ // packages/verify/dist/method.js
1676
+ var HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
1677
+ function wordsOf(line) {
1678
+ 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);
1679
+ }
1680
+ function methodOnLines(method, lines) {
1681
+ const m = method.toLowerCase();
1682
+ return lines.some((l) => wordsOf(l).includes(m));
1683
+ }
1684
+ var CHAIN_LINES = 8;
1685
+ var CONTINUES = /^\s*\??\./;
1686
+ function chainOf(file, line) {
1687
+ const at = (n) => file?.[n - 1];
1688
+ const cited = at(line);
1689
+ if (cited === void 0)
1690
+ return [];
1691
+ const out = [cited];
1692
+ for (let n = line, k = 0; k < CHAIN_LINES && CONTINUES.test(at(n) ?? ""); k++, n--) {
1693
+ const above = at(n - 1);
1694
+ if (above === void 0)
1695
+ break;
1696
+ out.push(above);
1697
+ }
1698
+ for (let n = line + 1, k = 0; k < CHAIN_LINES && CONTINUES.test(at(n) ?? ""); k++, n++)
1699
+ out.push(at(n));
1700
+ return out;
1701
+ }
1702
+ function methodUnseenOf(f, lines, factOf) {
1703
+ if (f.kind !== "entry" && f.kind !== "call")
1704
+ return void 0;
1705
+ if (!f.method || !HTTP_METHODS.has(f.method.toUpperCase()))
1706
+ return void 0;
1707
+ const at = (file, line) => lines(file)?.[line - 1];
1708
+ const route = f.kind === "call" && f.from ? factOf?.(f.from) : void 0;
1709
+ const forwards = route?.kind === "entry" && route.method?.toUpperCase() === f.method.toUpperCase();
1710
+ const looked = [
1711
+ ...chainOf(lines(f.cite.file), f.cite.line),
1712
+ at(f.cite.file, f.cite.line + 1),
1713
+ ...(f.cite.with ?? []).map((p) => at(p.file, p.line)),
1714
+ ...forwards ? [at(route.cite.file, route.cite.line), at(route.cite.file, route.cite.line + 1)] : []
1715
+ ].filter((l) => l !== void 0);
1716
+ if (methodOnLines(f.method, looked))
1717
+ return void 0;
1718
+ const cite2 = `${f.cite.file}:${f.cite.line}`;
1719
+ const method = f.method.toUpperCase();
1720
+ return {
1721
+ id: f.id,
1722
+ kind: f.kind,
1723
+ method,
1724
+ cite: cite2,
1725
+ 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`
1726
+ };
1727
+ }
1728
+
1729
+ // packages/verify/dist/skipped.js
1730
+ function notReadForSkipped(path, rules = DEFAULT_READ_RULES) {
1731
+ return rules.notRead.test(path);
1732
+ }
1733
+ var isCommentLine = (l) => /^\s*(\/\/|\/\*|\*|<!--|-- |#(?!\s*(region|endregion|if|elif|else|endif|define|undef|pragma|nullable|include|import)\b)(?!!))/.test(l);
1734
+ var IMPORT_LINE = /^\s*(import|export\s+[^=]*\bfrom\b|from\s+\S+\s+import|package|using|require|#\s*include|@import|@use|use|library)\b/;
1735
+ var IMPORT_CALL = /\brequire\s*\(|\bimport\s*\(|\bfrom\s+["'`]/;
1736
+ var NAMESPACE_LINE = /\bxmlns(:\w+)?\s*=|schemaLocation\s*=|<!DOCTYPE|^\s*<\?xml/i;
1737
+ var URL_RE = /\b(?:https?|wss?|grpcs?|amqps?):\/\/[^\s"'`<>()[\],;\\|^]+/gi;
1738
+ 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;
1739
+ var FS_ROOT = /^\/(usr|etc|var|tmp|bin|sbin|opt|home|dev|proc|sys|lib|lib64|mnt|root|Users|private|Library|System|Applications|Volumes)(\/|$)/;
1740
+ 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;
1741
+ var PATH_SEG = "[A-Za-z0-9_.~{}$:<>*%@+=-]+";
1742
+ var PATH_RE = new RegExp(`^(?:\\$\\{[^}]+\\}|%s|\\{[A-Za-z_]+\\})?((?:\\/${PATH_SEG})+\\/?)$`);
1743
+ 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(" "));
1744
+ 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(" "));
1745
+ var DOTTED_TOPIC = /^[a-z][a-z0-9_-]*(\.[a-z][a-z0-9_-]*){2,}$/;
1746
+ var SEPARATED_NAME = /^[A-Za-z][A-Za-z0-9_]*([._:-][A-Za-z0-9_]+)+$/;
1747
+ var QUOTED = /(["'`])((?:\\.|(?!\1)[^\\\n])*)\1/g;
1748
+ var KEY_VALUE = /^\s*-?\s*["']?([A-Za-z0-9_.\-[\]]+)["']?\s*[:=]\s*([^\s"'#]+)\s*$/;
1749
+ var ELEMENT_TEXT = /<([A-Za-z][\w:.-]*)[^>]*>([^<\s]+)<\/\1>/g;
1750
+ 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);
1751
+ var TOPIC_WORDS = /* @__PURE__ */ new Set([
1752
+ "topic",
1753
+ "topics",
1754
+ "queue",
1755
+ "queues",
1756
+ "channel",
1757
+ "channels",
1758
+ "destination",
1759
+ "destinations",
1760
+ "exchange",
1761
+ "exchanges"
1762
+ ]);
1763
+ var topicContext = (line) => {
1764
+ const w = wordsOf2(line);
1765
+ return w.some((x) => TOPIC_WORDS.has(x)) || w.join(" ").includes("routing key");
1766
+ };
1767
+ 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(" "));
1768
+ var BUILD_BLOCK_TAG = /<(\/?)(repositories|repository|pluginRepositories|pluginRepository|snapshotRepository|distributionManagement|scm|mirrors|mirror|site)\b[^>]*?(\/?)>/gi;
1769
+ var BUILD_BLOCK_OPEN = /\b(repositories|publishing|pluginManagement|dependencyResolutionManagement)\s*\{/;
1770
+ var keysBefore = (prefix) => [
1771
+ ...[...prefix.matchAll(/<([A-Za-z][\w:.-]*)/g)].map((m) => m[1]),
1772
+ ...[...prefix.matchAll(/["']?([A-Za-z][\w.-]*)["']?\s*[:=]/g)].map((m) => m[1]),
1773
+ ...[...prefix.matchAll(/\b([A-Za-z]\w*)\s*\{/g)].map((m) => m[1])
1774
+ ];
1775
+ 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)));
1776
+ var HEADER_ARG = /header\w*\s*(?:\.\s*\w+\s*)?\(\s*(?:(?:value|name)\s*=\s*)?$/i;
1777
+ var isHeaderName = (s, prefix) => /^x-/i.test(s) || HEADER_ARG.test(prefix.replace(/["'`]$/, ""));
1778
+ var cleanUrl = (u) => u.replace(/[.:]+$/, "").replace(/^([a-z0-9+.-]+:\/\/)[^/@\s]+@/i, "$1<redacted>@");
1779
+ var hostOf = (u) => /^[a-z0-9+.-]+:\/\/([^/:?#]+)/i.exec(u)?.[1] ?? "";
1780
+ function pathOf(s) {
1781
+ const m = PATH_RE.exec(s);
1782
+ if (!m)
1783
+ return void 0;
1784
+ const p = m[1];
1785
+ if (p.length < 2 || p.includes("//") || !/[A-Za-z]/.test(p))
1786
+ return void 0;
1787
+ if (FS_ROOT.test(p) || FILE_EXT.test(p))
1788
+ return void 0;
1789
+ return p;
1790
+ }
1791
+ function topicOf(s, context) {
1792
+ if (DOTTED_TOPIC.test(s)) {
1793
+ const parts = s.split(".");
1794
+ if (!NOT_TOPIC_LAST.has(parts.at(-1)) && !NOT_TOPIC_FIRST.has(parts[0]))
1795
+ return s;
1796
+ }
1797
+ if (context && SEPARATED_NAME.test(s) && !FILE_EXT.test(s)) {
1798
+ const last = s.split(".").at(-1);
1799
+ if (!s.includes(".") || !NOT_TOPIC_LAST.has(last))
1800
+ return s;
1801
+ }
1802
+ return void 0;
1803
+ }
1804
+ function codeOf(raw, state) {
1805
+ let s = raw;
1806
+ let out = "";
1807
+ for (; ; ) {
1808
+ if (state.block) {
1809
+ const close = state.block === "c" ? "*/" : "-->";
1810
+ const i = s.indexOf(close);
1811
+ if (i < 0)
1812
+ return out;
1813
+ s = s.slice(i + close.length);
1814
+ state.block = void 0;
1815
+ }
1816
+ const m = /(^|\s)(\/\*|<!--)/.exec(s);
1817
+ if (!m)
1818
+ return out + s;
1819
+ out += s.slice(0, m.index + m[1].length);
1820
+ s = s.slice(m.index + m[0].length);
1821
+ state.block = m[2] === "/*" ? "c" : "xml";
1822
+ }
1823
+ }
1824
+ function candidatesIn(path, lines) {
1825
+ const out = [];
1826
+ const state = {};
1827
+ let xmlDepth = 0;
1828
+ let braceDepth = 0;
1829
+ lines.forEach((raw, i) => {
1830
+ const wasBlock = state.block !== void 0;
1831
+ let code2 = codeOf(raw, state);
1832
+ const inBuild = xmlDepth > 0 || braceDepth > 0;
1833
+ for (const m of code2.matchAll(BUILD_BLOCK_TAG))
1834
+ if (!m[3])
1835
+ xmlDepth = Math.max(0, xmlDepth + (m[1] ? -1 : 1));
1836
+ if (braceDepth > 0 || BUILD_BLOCK_OPEN.test(code2)) {
1837
+ const from = braceDepth > 0 ? code2 : code2.slice(BUILD_BLOCK_OPEN.exec(code2).index);
1838
+ braceDepth = Math.max(0, braceDepth + (from.match(/\{/g)?.length ?? 0) - (from.match(/\}/g)?.length ?? 0));
1839
+ }
1840
+ if (!code2.trim())
1262
1841
  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}` });
1842
+ if (!wasBlock && isCommentLine(raw))
1843
+ return;
1844
+ if (IMPORT_LINE.test(code2) || IMPORT_CALL.test(code2) || NAMESPACE_LINE.test(code2))
1269
1845
  return;
1846
+ code2 = code2.replace(/(^|[\s;,)}])\/\/.*$/, "$1").replace(/\s#\s.*$/, "");
1847
+ const seen = /* @__PURE__ */ new Set();
1848
+ const text = redactSecrets(cleanUrlIn(raw.trim())).slice(0, 200);
1849
+ const add = (kind, value) => {
1850
+ const key = `${kind} ${value}`;
1851
+ if (seen.has(key))
1852
+ return;
1853
+ seen.add(key);
1854
+ out.push({ line: i + 1, kind, value, text });
1855
+ };
1856
+ const context = topicContext(code2);
1857
+ const urls = [];
1858
+ const opened = inBuild || xmlDepth > 0 || braceDepth > 0;
1859
+ for (const m of code2.matchAll(URL_RE)) {
1860
+ const u = cleanUrl(m[0]);
1861
+ const build = opened || isBuildUrl(u, code2.slice(0, m.index));
1862
+ if (!build && !STANDARD_HOST.test(hostOf(u)))
1863
+ add("url", u);
1864
+ urls.push(m[0]);
1270
1865
  }
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(", ")}`);
1866
+ const strings2 = [...code2.matchAll(QUOTED)].map((m) => ({
1867
+ s: m[2],
1868
+ before: code2.slice(0, m.index + 1)
1869
+ }));
1870
+ const kv = KEY_VALUE.exec(code2);
1871
+ if (kv)
1872
+ strings2.push({ s: kv[2], before: "" });
1873
+ for (const m of code2.matchAll(ELEMENT_TEXT))
1874
+ strings2.push({ s: m[2], before: "" });
1875
+ for (const { s, before } of strings2) {
1876
+ if (urls.some((u) => s.includes(u) || u.includes(s)))
1877
+ continue;
1878
+ const p = pathOf(s);
1879
+ if (p) {
1880
+ add("path", p);
1881
+ continue;
1882
+ }
1883
+ const t = topicOf(s, context);
1884
+ if (t && !isHeaderName(t, before))
1885
+ add("topic", t);
1283
1886
  }
1284
- if (issues.length) {
1285
- bad.push({ line, raw, message: `line ${line} isn't ${SHAPE}: ${issues.join("; ")}` });
1887
+ });
1888
+ return out;
1889
+ }
1890
+ var cleanUrlIn = (line) => line.replace(/\b([a-z0-9+.-]+:\/\/)[^/@\s"'`]+@/gi, "$1<redacted>@");
1891
+ var normPath = (p) => normalizePath(p.replace(/(^|\/)%[sd](?=\/|$)/g, "$1{}"));
1892
+ function indexFacts(facts) {
1893
+ const at = /* @__PURE__ */ new Set();
1894
+ const strings2 = /* @__PURE__ */ new Set();
1895
+ const walk = (v) => {
1896
+ if (typeof v === "string") {
1897
+ if (v.length >= 3)
1898
+ strings2.add(v);
1286
1899
  return;
1287
1900
  }
1288
- entries.push({ line, path: o.path.trim(), why: o.why.trim() });
1289
- });
1290
- return { entries, bad };
1901
+ if (Array.isArray(v))
1902
+ return v.forEach(walk);
1903
+ if (v && typeof v === "object") {
1904
+ const o = v;
1905
+ if (typeof o.file === "string" && typeof o.line === "number")
1906
+ at.add(`${o.file}:${o.line}`);
1907
+ Object.values(o).forEach(walk);
1908
+ }
1909
+ };
1910
+ facts.forEach(walk);
1911
+ const paths = /* @__PURE__ */ new Set();
1912
+ for (const s of strings2) {
1913
+ if (/\s/.test(s))
1914
+ continue;
1915
+ const u = /^[a-z0-9+.-]+:\/\/[^/]+(\/.*)?$/i.exec(s);
1916
+ const p = u ? u[1] : s.includes("/") ? s : void 0;
1917
+ if (p && p.length > 1)
1918
+ paths.add(`${normPath(p)}/`);
1919
+ }
1920
+ return { at, strings: [...strings2].map((s) => s.toLowerCase()), paths: [...paths] };
1921
+ }
1922
+ function factsCover(file, c, ix) {
1923
+ if ([c.line - 1, c.line, c.line + 1].some((n) => ix.at.has(`${file}:${n}`)))
1924
+ return true;
1925
+ if (c.kind === "path") {
1926
+ const p = `${normPath(c.value)}/`;
1927
+ return ix.paths.some((f) => f.includes(p));
1928
+ }
1929
+ const v = c.value.toLowerCase().replace(/\/+$/, "");
1930
+ return ix.strings.some((s) => s.includes(v));
1931
+ }
1932
+ var skippedOf = (file, c) => ({
1933
+ cite: `${file}:${c.line}`,
1934
+ kind: c.kind,
1935
+ value: c.value,
1936
+ text: c.text
1937
+ });
1938
+ var SPEC_MARK = /^\s*["']?(openapi|swagger)["']?\s*:/m;
1939
+ var SPEC_EXT = /\.(ya?ml|json)$/i;
1940
+ function specOpsUncoveredIn(file, text, facts) {
1941
+ if (!SPEC_EXT.test(file) || !SPEC_MARK.test(text.slice(0, 4e3)))
1942
+ return void 0;
1943
+ let doc;
1944
+ try {
1945
+ doc = readOpenApiDocument(text);
1946
+ } catch {
1947
+ return void 0;
1948
+ }
1949
+ if (!doc || !doc.operations.length)
1950
+ return void 0;
1951
+ const server = (() => {
1952
+ const s = doc.servers[0];
1953
+ if (!s)
1954
+ return "";
1955
+ const p = /^[a-z0-9+.-]+:\/\/[^/]+(\/.*)?$/i.exec(s)?.[1] ?? (s.startsWith("/") ? s : "");
1956
+ return p.replace(/\/+$/, "");
1957
+ })();
1958
+ const entries = facts.filter((f) => f.kind === "entry");
1959
+ const atLines = /* @__PURE__ */ new Set();
1960
+ for (const e of entries)
1961
+ for (const k of indexFacts([e]).at)
1962
+ atLines.add(k);
1963
+ const out = [];
1964
+ for (const op of doc.operations) {
1965
+ const lines = [op.methodLine, op.line].filter((n) => n !== void 0);
1966
+ if (lines.some((n) => [n - 1, n, n + 1].some((x) => atLines.has(`${file}:${x}`))))
1967
+ continue;
1968
+ const method = op.method.toUpperCase();
1969
+ const own = normPath(op.path);
1970
+ const full = normPath(`${server}${op.path}`);
1971
+ const covered = entries.some((e) => {
1972
+ if (e.method && e.method.toUpperCase() !== method)
1973
+ return false;
1974
+ const p = normPath(e.path);
1975
+ return p === own || p === full || p.endsWith(own) || p.split("/").length > 2 && own.endsWith(p);
1976
+ });
1977
+ if (covered)
1978
+ continue;
1979
+ const opId = op.node?.get?.("operationId");
1980
+ const line = op.methodLine ?? op.line ?? 1;
1981
+ out.push({
1982
+ spec: file,
1983
+ method,
1984
+ path: op.path,
1985
+ ...typeof opId === "string" ? { operationId: opId } : {},
1986
+ cite: `${file}:${line}`,
1987
+ message: `${file}:${line} declares ${method} ${op.path}${typeof opId === "string" ? ` (${opId})` : ""}, which no entry covers`
1988
+ });
1989
+ }
1990
+ return out;
1991
+ }
1992
+ var REVIEWED_HOLDS_SHOWN = 5;
1993
+ function reviewedHoldsOf(r, skipped) {
1994
+ const n = skipped.length;
1995
+ const first = skipped.slice(0, 3).map((s) => `${s.cite} (${s.value})`);
1996
+ return {
1997
+ path: r.path,
1998
+ why: r.why,
1999
+ count: n,
2000
+ skipped: skipped.slice(0, REVIEWED_HOLDS_SHOWN),
2001
+ 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` : ""}`
2002
+ };
1291
2003
  }
1292
- var isGlob = (path) => /[*?]/.test(path);
1293
2004
 
1294
2005
  // packages/verify/dist/verify.js
1295
2006
  var FOLLOWED_TYPES = ["event-topic", "mq-queue", "rest", "grpc"];
1296
- function entriesNoFlowFollows(facts) {
2007
+ function entriesNoFlowFollows(facts, rules = DEFAULT_READ_RULES) {
1297
2008
  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);
2009
+ const forwarded = new Set(facts.flatMap((f) => f.kind === "call" && f.from ? [f.from] : []));
2010
+ return facts.flatMap((f) => {
2011
+ if (f.kind !== "entry" || !FOLLOWED_TYPES.includes(f.entryType))
2012
+ return [];
2013
+ const route = rules.gatewayBundle.test(f.cite.file);
2014
+ if (route ? forwarded.has(f.id) : started.has(f.id))
2015
+ return [];
2016
+ return [
2017
+ {
2018
+ id: f.id,
2019
+ entryType: f.entryType,
2020
+ ...f.method ? { method: f.method } : {},
2021
+ path: f.path,
2022
+ cite: `${f.cite.file}:${f.cite.line}`,
2023
+ ...route ? { route: true } : {}
2024
+ }
2025
+ ];
2026
+ }).sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
1308
2027
  }
1309
2028
  var LINES_HELD = 64;
1310
2029
  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
2030
  var SOURCE_NAME = /(^|\/)(Dockerfile[^/]*|Makefile|Jenkinsfile|Procfile|Containerfile)$/;
1312
2031
  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
2032
  var NOT_PRODUCT = [
1314
- /(^|\/)(src\/test|src\/it|test|tests|__tests__|__mocks__|spec|specs|e2e|testdata|test-data|fixtures)\//i,
1315
2033
  /(\.|_|-)(test|spec|tests|specs)\.[a-z0-9]+$/i,
1316
2034
  /(Test|Tests|IT|Spec)\.(java|kt|scala|groovy|cs|vb)$/,
1317
2035
  /(^|\/)test_[^/]+\.py$/,
@@ -1320,19 +2038,44 @@ var NOT_PRODUCT = [
1320
2038
  /(^|\/)messages(_[A-Za-z_]+)?\.properties$/,
1321
2039
  /\.(resx|po|pot)$/i
1322
2040
  ];
1323
- function isSourceFile(path, firstLine = "", lines) {
2041
+ function isSourceFile(path, firstLine = "", lines, rules = DEFAULT_READ_RULES) {
1324
2042
  if (isSecretPath(path) || LOCKFILE.test(path))
1325
2043
  return false;
1326
- if (NOT_PRODUCT.some((re) => re.test(path)) || isLocaleBundle(path))
2044
+ if (rules.notProduct.test(path) || NOT_PRODUCT.some((re) => re.test(path)))
1327
2045
  return false;
1328
- if (lines && isDataFile(path, lines))
2046
+ if (isLocaleBundle(path))
2047
+ return false;
2048
+ if (lines && isDataFile(path, lines, rules))
1329
2049
  return false;
1330
2050
  if (quoteNotRequired(path, firstLine))
1331
2051
  return false;
2052
+ if (isLfsPointer(firstLine))
2053
+ return false;
2054
+ if (isBinaryText(firstLine) || lines && binaryLines(lines))
2055
+ return false;
1332
2056
  return SOURCE_EXT.test(path) || SOURCE_NAME.test(path);
1333
2057
  }
2058
+ function binaryLines(lines) {
2059
+ let n = 0;
2060
+ for (const l of lines) {
2061
+ if (isBinaryText(l))
2062
+ return true;
2063
+ if ((n += l.length + 1) > 8e3)
2064
+ return false;
2065
+ }
2066
+ return false;
2067
+ }
1334
2068
  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);
2069
+ var TEMPLATED = /\{\{|\}\}|\$\{/;
2070
+ function pathIsAName(path) {
2071
+ const p = (path ?? "").trim().replace(/\(\s*[^)]*\)$/, "");
2072
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(p) && /[a-z][A-Z]|_|\$/.test(p);
2073
+ }
2074
+ var serviceAddress = (f) => f.host !== void 0 && /^service$/i.test(f.resourceType ?? "") ? clusterServiceAddress(f.host) : void 0;
2075
+ var repoModule = (exposes) => {
2076
+ const m = /^([^/]+)\/([^/]+)$/.exec(exposes.trim());
2077
+ return m ? { repo: m[1], module: m[2] } : void 0;
2078
+ };
1336
2079
  var routeOrWord = (v) => v.includes("/") ? { kind: "route", value: v } : { kind: "word", value: v };
1337
2080
  function claimsOf(f) {
1338
2081
  switch (f.kind) {
@@ -1368,6 +2111,12 @@ function claimsOf(f) {
1368
2111
  }
1369
2112
  case "flow":
1370
2113
  return [routeOrWord(f.cite.value)];
2114
+ // ADR 0163: the value as written, on its line.
2115
+ case "term":
2116
+ return [{ kind: "word", value: f.value }];
2117
+ // ADR 0180: the signal the type rests on, as written on its line.
2118
+ case "component":
2119
+ return [{ kind: "word", value: f.cite.value }];
1371
2120
  }
1372
2121
  }
1373
2122
  var sameCommit = (a, b) => a.startsWith(b) || b.startsWith(a);
@@ -1402,11 +2151,47 @@ function refsOf(f) {
1402
2151
  }
1403
2152
  return out;
1404
2153
  }
2154
+ function withCanonicalFiles(f, canonical) {
2155
+ const walk = (x) => {
2156
+ if (Array.isArray(x)) {
2157
+ const out2 = x.map(walk);
2158
+ return out2.some((v, i) => v !== x[i]) ? out2 : x;
2159
+ }
2160
+ if (!x || typeof x !== "object")
2161
+ return x;
2162
+ const o = x;
2163
+ let out;
2164
+ for (const [k, v] of Object.entries(o)) {
2165
+ const w = k === "file" && typeof v === "string" && typeof o.line === "number" ? canonical(v) : walk(v);
2166
+ if (w !== v)
2167
+ (out ??= { ...o })[k] = w;
2168
+ }
2169
+ return out ?? o;
2170
+ };
2171
+ return walk(f);
2172
+ }
1405
2173
  async function verifyFacts(o) {
2174
+ const rules = readRulesOf(o.conventions);
1406
2175
  const carriedFrom = /* @__PURE__ */ new Map();
1407
2176
  const stale = /* @__PURE__ */ new Map();
1408
2177
  const corrected = applyCorrections(o.facts, o.corrections ?? []);
1409
- const facts = [...corrected.facts].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0).map((f) => {
2178
+ let fileList;
2179
+ const listed2 = () => fileList ??= o.source.list();
2180
+ let listedSet;
2181
+ let folded;
2182
+ const fold = (p) => p.replace(/\\/g, "/").toLowerCase();
2183
+ const canonical = (path) => {
2184
+ listedSet ??= new Set(listed2());
2185
+ if (listedSet.has(path))
2186
+ return path;
2187
+ if (!folded) {
2188
+ folded = /* @__PURE__ */ new Map();
2189
+ for (const p of listed2())
2190
+ folded.set(fold(p), folded.has(fold(p)) ? null : p);
2191
+ }
2192
+ return folded.get(fold(path)) ?? path;
2193
+ };
2194
+ const facts = [...corrected.facts].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0).map((f) => withCanonicalFiles(f, canonical)).map((f) => {
1410
2195
  if (!o.commit || sameCommit(f.commit, o.commit))
1411
2196
  return f;
1412
2197
  const changes = changesFor(o.carry, f.commit);
@@ -1421,7 +2206,8 @@ async function verifyFacts(o) {
1421
2206
  return { ...f, commit: o.commit };
1422
2207
  });
1423
2208
  const readLines = (path) => {
1424
- const text = insideRepo(path) && !isSecretPath(path) ? o.source.read(path) : void 0;
2209
+ listedSet ??= new Set(listed2());
2210
+ const text = insideRepo(path) && !isSecretPath(path) && listedSet.has(path) ? o.source.read(path) : void 0;
1425
2211
  return text === void 0 ? void 0 : text.replace(/\r\n?/g, "\n").split("\n");
1426
2212
  };
1427
2213
  const linesCache = /* @__PURE__ */ new Map();
@@ -1438,9 +2224,8 @@ async function verifyFacts(o) {
1438
2224
  linesCache.delete(linesCache.keys().next().value);
1439
2225
  return lines;
1440
2226
  };
1441
- let fileList;
1442
- const deriveEnv = { lines: linesOf2, files: () => fileList ??= o.source.list() };
1443
- const checkCite = (cite2, claims, label, withHint = true) => {
2227
+ const deriveEnv = { lines: linesOf2, files: listed2 };
2228
+ const checkCite = (cite2, claims, label, withHint = true, alsoHolds) => {
1444
2229
  const where = `${cite2.file}:${cite2.line}`;
1445
2230
  if (!insideRepo(cite2.file))
1446
2231
  return {
@@ -1504,7 +2289,7 @@ async function verifyFacts(o) {
1504
2289
  ].flatMap((at) => derivedNamesAt(at.file, linesOf2(at.file) ?? [], at.line, deriveEnv));
1505
2290
  const holds = (c) => claimOnLines(lines, cite2.line, c) || // a framework that routes by file place (Razor Pages, WebForms, Next.js)
1506
2291
  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);
2292
+ c.kind === "word" && partsBuildWord(c.value, parts.map((p) => p.value), near(lines, cite2.line))) || o.derivedNames !== false && derivedHolds(c, derivedHere(), partLines, partsNamePath) || alsoHolds !== void 0 && alsoHolds(lines, cite2.line);
1508
2293
  if (claims && !claims.some(holds))
1509
2294
  return {
1510
2295
  code: "claim-not-on-line",
@@ -1541,6 +2326,9 @@ async function verifyFacts(o) {
1541
2326
  return value.includes("/") ? [routeOrWord(value), { kind: "word", value }] : [routeOrWord(value)];
1542
2327
  return NEVER_CHECKED_STEPS.includes(kind) ? void 0 : [routeOrWord(value)];
1543
2328
  };
2329
+ const estate = o.estate ? new Set(o.estate.components.map(factIdPart)) : void 0;
2330
+ const serves2 = facts.some((f) => f.kind === "entry");
2331
+ const repoComponents = new Map(Object.entries(o.estate?.repoComponents ?? {}).map(([r, cs]) => [factIdPart(r), new Set(cs.map(factIdPart))]));
1544
2332
  for (const f of facts) {
1545
2333
  const label = `${f.kind} ${f.id}`;
1546
2334
  if (o.commit && !sameCommit(f.commit, o.commit)) {
@@ -1554,7 +2342,7 @@ async function verifyFacts(o) {
1554
2342
  problem(f, "wrong-commit", `${label} was read at ${f.commit}, not at ${o.commit}; read it again`);
1555
2343
  continue;
1556
2344
  }
1557
- add(f, checkCite(f.cite, claimsOf(f), label));
2345
+ add(f, checkCite(f.cite, claimsOf(f), label, true, f.kind === "entry" || f.kind === "call" ? (ls, n) => apigeePassThrough({ kind: f.kind, forwards: f.kind === "call" && !!f.from }, f.cite.file, ls, n, f.path) : void 0));
1558
2346
  if (f.kind === "entry" && f.authCite)
1559
2347
  add(f, checkCite({ ...f.authCite }, [{ kind: "word", value: f.authCite.value }], `${label} authCite${f.auth ? ` (auth ${f.auth})` : ""}`, false));
1560
2348
  if (f.kind === "entry" && f.authCite && f.auth) {
@@ -1566,8 +2354,80 @@ async function verifyFacts(o) {
1566
2354
  (f.scopes ?? []).forEach((s, i) => add(f, checkCite(s, [{ kind: "word", value: s.value }], `${label} scope ${i + 1} ("${s.value}")`)));
1567
2355
  if (f.kind === "entry")
1568
2356
  (f.callers ?? []).forEach((c, i) => add(f, checkCite(c, [{ kind: "word", value: c.value }], `${label} caller ${i + 1} (${c.as} "${c.value}")`)));
2357
+ if (f.kind === "call" && (f.callType === "rest" || f.callType === "soap") && pathIsAName(f.path))
2358
+ 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`);
2359
+ if (f.kind === "deploy" && (f.host !== void 0 || f.basePath !== void 0)) {
2360
+ if (f.host !== void 0 && TEMPLATED.test(f.host))
2361
+ 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)`);
2362
+ else if (f.host !== void 0 && serviceAddress(f) !== void 0) {
2363
+ const a = serviceAddress(f);
2364
+ const parts = [
2365
+ ["name", a.name],
2366
+ ["namespace", a.namespace],
2367
+ ...a.port !== void 0 ? [["port", String(a.port)]] : []
2368
+ ];
2369
+ for (const [what, value] of parts)
2370
+ add(f, checkCite(f.cite, [{ kind: "word", value }], `${label} host's ${what}`, false));
2371
+ } else if (f.host !== void 0)
2372
+ add(f, checkCite(f.cite, [{ kind: "word", value: f.host }], `${label} host`, false));
2373
+ if (f.basePath !== void 0 && isPatternPath(f.basePath))
2374
+ 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`);
2375
+ else if (f.basePath !== void 0)
2376
+ add(f, checkCite(f.cite, [routeOrWord(f.basePath), { kind: "word", value: f.basePath }], `${label} basePath`, false));
2377
+ const inModule = f.exposes !== void 0 ? repoModule(f.exposes) : void 0;
2378
+ if (inModule && estate && estate.has(factIdPart(inModule.repo))) {
2379
+ const known = repoComponents.get(factIdPart(inModule.repo));
2380
+ if (known && !known.has(factIdPart(inModule.module)))
2381
+ 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`);
2382
+ } else if (f.exposes !== void 0 && estate && !estate.has(factIdPart(inModule?.repo ?? f.exposes)))
2383
+ 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`);
2384
+ if (f.exposes === void 0 && !serves2)
2385
+ 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`);
2386
+ }
2387
+ if (f.kind === "call") {
2388
+ for (const [what, claim, at] of [
2389
+ ["auth", f.auth, f.authCite],
2390
+ ["tls", f.tls, f.tlsCite]
2391
+ ]) {
2392
+ if (claim !== void 0 && !at && f.by !== "person")
2393
+ 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`);
2394
+ if (!at)
2395
+ continue;
2396
+ const bad = checkCite({ ...at }, [{ kind: "word", value: at.value }], `${label} ${what}Cite${claim ? ` (${what} ${claim})` : ""}`, false);
2397
+ add(f, bad);
2398
+ if (bad || claim === void 0)
2399
+ continue;
2400
+ const lines = [at, ...at.with ?? []].flatMap((p) => {
2401
+ const l = lineOfValue(linesOf2(p.file), p.line, p.value);
2402
+ return l === void 0 ? [] : [l];
2403
+ });
2404
+ const needs = securityLineKind(what, claim, lines);
2405
+ if (needs)
2406
+ 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`);
2407
+ }
2408
+ }
2409
+ if (f.kind === "term") {
2410
+ const m = f.meaning;
2411
+ if (m && m.file !== f.cite.file)
2412
+ 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`);
2413
+ else if (m && m.line > f.cite.line)
2414
+ 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`);
2415
+ else if (m)
2416
+ add(f, checkCite({ ...m }, [{ kind: "word", value: m.value }], `${label} meaning`, false));
2417
+ const bad = f.note !== void 0 && o.notes ? noteProblem(f.note, o.notes) : void 0;
2418
+ if (bad)
2419
+ problem(f, bad.code, `${label} ${bad.message}`);
2420
+ }
2421
+ if (f.kind === "component") {
2422
+ if (o.componentTypes && !o.componentTypes.has(f.type))
2423
+ 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(", ")}`);
2424
+ const subject = factIdPart(f.component ?? f.repo);
2425
+ const first = facts.find((x) => x.kind === "component" && factIdPart(x.component ?? x.repo) === subject && (x === f || !found.has(x.id)));
2426
+ if (first && first !== f)
2427
+ problem(f, "component-type", `${label} says what ${subject} is a second time (${first.id} already does): one fact says what a component is`);
2428
+ }
1569
2429
  if (f.kind === "framework") {
1570
- const bad = frameworkNameProblem(f);
2430
+ const bad = frameworkNameProblem(f, rules.frameworks);
1571
2431
  if (bad)
1572
2432
  problem(f, "framework-name", `${label} ${bad}`);
1573
2433
  if (f.version !== void 0)
@@ -1656,36 +2516,49 @@ async function verifyFacts(o) {
1656
2516
  return rest;
1657
2517
  }
1658
2518
  const q = redactSecrets(c.quote);
2519
+ if (q === c.quote && quoteHoldsSecret(q)) {
2520
+ redacted++;
2521
+ const { quote: _secret, ...rest } = c;
2522
+ return rest;
2523
+ }
1659
2524
  if (q !== c.quote)
1660
2525
  redacted++;
1661
2526
  return { ...c, quote: q };
1662
2527
  };
1663
2528
  const authUncited = [];
2529
+ const methodUnseen = [];
2530
+ const kept = new Map(facts.filter((f) => !rejected.has(f.id)).map((f) => [f.id, f]));
2531
+ const factById = (id) => kept.get(id);
1664
2532
  const accepted = facts.filter((f) => !rejected.has(f.id)).map((f) => {
1665
2533
  o.room?.();
1666
2534
  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")
2535
+ const base2 = { ...f, verified, cite: fixQuote(f.cite) };
2536
+ if (base2.kind === "entry" && base2.auth !== void 0 && !base2.authCite && f.by !== "person")
1669
2537
  authUncited.push({
1670
- id: base.id,
1671
- auth: base.auth,
1672
- cite: `${base.cite.file}:${base.cite.line}`
2538
+ id: base2.id,
2539
+ auth: base2.auth,
2540
+ cite: `${base2.cite.file}:${base2.cite.line}`
1673
2541
  });
1674
- if (base.kind === "flow")
2542
+ const unseen = methodUnseenOf(base2, linesOf2, factById);
2543
+ if (unseen)
2544
+ methodUnseen.push(unseen);
2545
+ if (base2.kind === "flow")
1675
2546
  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) }))
2547
+ ...base2,
2548
+ steps: base2.steps.map((s) => ({ ...s, cite: fixQuote(s.cite) })),
2549
+ errors: base2.errors.map((e) => ({ ...e, cite: fixQuote(e.cite) }))
1679
2550
  };
1680
- return base;
2551
+ return base2;
1681
2552
  });
1682
2553
  const excludeRes = (o.exclude ?? []).map(globToRegExp);
1683
2554
  const sources = [];
1684
2555
  const excluded = [];
1685
2556
  for (const p of o.source.list()) {
1686
2557
  o.room?.();
2558
+ if (!isSourceFile(p, "", void 0, rules) || tooBigToRead(o.source, p))
2559
+ continue;
1687
2560
  const ls = readLines(p) ?? [""];
1688
- if (!isSourceFile(p, ls[0] ?? "", ls))
2561
+ if (!isSourceFile(p, ls[0] ?? "", ls, rules))
1689
2562
  continue;
1690
2563
  if (excludeRes.some((re) => re.test(p)))
1691
2564
  excluded.push(p);
@@ -1701,6 +2574,8 @@ async function verifyFacts(o) {
1701
2574
  const reviewedSet = /* @__PURE__ */ new Set();
1702
2575
  const reviewed = [];
1703
2576
  const reviewedProblems = [];
2577
+ const reviewedDropped = [];
2578
+ const reviewedGlobs = [];
1704
2579
  for (const r of o.reviewed ?? []) {
1705
2580
  const refuse = (message) => reviewedProblems.push({
1706
2581
  ...r.line !== void 0 ? { line: r.line } : {},
@@ -1716,36 +2591,105 @@ async function verifyFacts(o) {
1716
2591
  refuse("is a secret's file, which is never read into a map");
1717
2592
  continue;
1718
2593
  }
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);
2594
+ const range = lineRangeOf(r.path);
2595
+ if (range) {
2596
+ const text = allFiles.includes(range.file) ? o.source.read(range.file) : void 0;
2597
+ if (text === void 0) {
2598
+ refuse("is not in the repo at this commit; name a file you read");
2599
+ continue;
2600
+ }
2601
+ const last = text.split(/\r?\n/).length;
2602
+ if (range.from < 1 || range.from > range.to || range.to > last) {
2603
+ refuse(`is past the file's last line (${last}) or out of order; name the lines you read`);
2604
+ continue;
2605
+ }
2606
+ }
2607
+ const target = range ? range.file : r.path;
2608
+ const re = !range && isGlob(r.path) ? globToRegExp(r.path) : void 0;
2609
+ const matched = re ? allFiles.filter((p) => re.test(p)) : allFiles.filter((p) => p === target);
1721
2610
  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");
2611
+ if (re)
2612
+ reviewedDropped.push({
2613
+ ...r.line !== void 0 ? { line: r.line } : {},
2614
+ path: r.path,
2615
+ why: r.why,
2616
+ 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)`
2617
+ });
2618
+ else
2619
+ refuse("is not in the repo at this commit; name a file you read");
2620
+ continue;
2621
+ }
2622
+ const entryPoints = re ? matched.filter((p) => isSource.has(p) && !touched.has(p) && isEntryPointFile(p)) : [];
2623
+ if (entryPoints.length) {
2624
+ 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
2625
  continue;
1724
2626
  }
1725
2627
  const files = matched.filter((p) => isSource.has(p) && !touched.has(p)).sort();
1726
2628
  for (const p of files)
1727
2629
  reviewedSet.add(p);
1728
2630
  reviewed.push({ path: r.path, why: r.why, files });
2631
+ if (re)
2632
+ reviewedGlobs.push({ entry: r, files });
1729
2633
  }
1730
2634
  const read = sources.filter((p) => touched.has(p)).sort();
1731
2635
  const reviewedFiles = sources.filter((p) => reviewedSet.has(p)).sort();
1732
- const unknown = sources.filter((p) => !touched.has(p) && !reviewedSet.has(p)).sort();
2636
+ const leftOut = new Set(o.notReached ?? []);
2637
+ const notReached = sources.filter((p) => leftOut.has(p) && !touched.has(p) && !reviewedSet.has(p)).sort();
2638
+ const unknown = sources.filter((p) => !touched.has(p) && !reviewedSet.has(p) && !leftOut.has(p)).sort();
1733
2639
  const total = read.length + reviewedFiles.length + unknown.length;
1734
2640
  const ratio = total === 0 ? 1 : Math.round(read.length / total * 1e3) / 1e3;
2641
+ const ix = indexFacts(accepted);
2642
+ const specOpsUncovered = [];
2643
+ const specs = /* @__PURE__ */ new Set();
2644
+ for (const p of sources) {
2645
+ if (notReadForSkipped(p, rules))
2646
+ continue;
2647
+ o.room?.();
2648
+ const text = o.readers !== false && /\.(ya?ml|json)$/i.test(p) ? o.source.read(p) : void 0;
2649
+ const ops = text === void 0 ? void 0 : specOpsUncoveredIn(p, text, accepted);
2650
+ if (!ops)
2651
+ continue;
2652
+ specs.add(p);
2653
+ specOpsUncovered.push(...ops);
2654
+ }
2655
+ const skippedIn = (p) => {
2656
+ if (specs.has(p) || notReadForSkipped(p, rules))
2657
+ return [];
2658
+ o.room?.();
2659
+ const ls = readLines(p);
2660
+ return ls ? candidatesIn(p, ls).flatMap((c) => factsCover(p, c, ix) ? [] : [skippedOf(p, c)]) : [];
2661
+ };
2662
+ const skipped = read.flatMap(skippedIn);
2663
+ const reviewedHolds = reviewedGlobs.flatMap(({ entry, files }) => {
2664
+ const held = files.flatMap(skippedIn);
2665
+ return held.length ? [reviewedHoldsOf(entry, held)] : [];
2666
+ });
1735
2667
  return {
1736
2668
  accepted,
1737
2669
  // a rejected fact goes back as it was read, at its own commit, even one that was carried
1738
2670
  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 },
2671
+ coverage: {
2672
+ read,
2673
+ reviewed: reviewedFiles,
2674
+ unknown,
2675
+ excluded: excluded.sort(),
2676
+ notReached,
2677
+ ratio
2678
+ },
1740
2679
  redacted,
1741
2680
  quotesDropped,
1742
2681
  carried: accepted.flatMap((f) => carriedFrom.has(f.id) ? [{ id: f.id, from: carriedFrom.get(f.id) }] : []),
1743
2682
  corrections: { applied: corrected.applied, stale: corrected.stale },
1744
- entriesNoFlowFollows: entriesNoFlowFollows(accepted),
2683
+ entriesNoFlowFollows: entriesNoFlowFollows(accepted, rules),
1745
2684
  reviewed,
1746
2685
  reviewedProblems,
2686
+ reviewedDropped,
1747
2687
  authUncited: authUncited.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0),
1748
- frameworkGaps: frameworkGaps(accepted)
2688
+ methodUnseen: methodUnseen.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0),
2689
+ skipped,
2690
+ reviewedHolds,
2691
+ specOpsUncovered,
2692
+ frameworkGaps: frameworkGaps(accepted, rules.frameworks)
1749
2693
  };
1750
2694
  }
1751
2695
 
@@ -1758,11 +2702,11 @@ var ENTRY_DIR = /(^|\/)(controllers?|routes|routers|handlers|api|endpoints|jobs|
1758
2702
  var BOUNDARY_NAME = /(client|gateway|repository|repo|dao|mapper|entity|model|dto|request|response|publisher|producer|store|cache|schema)$/i;
1759
2703
  var BOUNDARY_FILE = /\.(sql|proto|graphql|gql|avsc|wsdl|xsd)$|(^|\/)(openapi|swagger)[^/]*\.(ya?ml|json)$/i;
1760
2704
  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;
2705
+ var TEST = /(^|\/)(test|tests|__tests__|spec|specs|e2e|it)\/|(^|\/)(test|tests)[-_][a-z0-9_-]+\/|[._-](test|spec)\.[^/.]+$|(Test|Tests|IT|Spec)\.[^/.]+$|(^|\/)test_[^/]+$|_test\.go$/;
2706
+ var stem2 = (path) => {
2707
+ const base2 = path.slice(path.lastIndexOf("/") + 1);
2708
+ const dot = base2.indexOf(".");
2709
+ return dot > 0 ? base2.slice(0, dot) : base2;
1766
2710
  };
1767
2711
  function readingRank(path) {
1768
2712
  if (TEST.test(path))
@@ -1771,7 +2715,7 @@ function readingRank(path) {
1771
2715
  return 0;
1772
2716
  if (CONFIG.test(path))
1773
2717
  return 1;
1774
- const s = stem(path);
2718
+ const s = stem2(path);
1775
2719
  if (ENTRY_NAME.test(s) || ENTRY_DIR.test(path))
1776
2720
  return 2;
1777
2721
  if (BOUNDARY_NAME.test(s) || BOUNDARY_FILE.test(path) || BOUNDARY_DIR.test(path))
@@ -1819,9 +2763,9 @@ var CONFIG_CODE = /(^|\/)(config|configuration|settings|environments?)(\/|\.[^/]
1819
2763
  var IAC = /\.(tf|tfvars|hcl|bicep)$|(^|\/)(serverless|template)[^/]*\.ya?ml$|(^|\/)(cdk|pulumi|terraform)\//;
1820
2764
  var DATA_FILE = /\.sql$|(^|\/)(migrations?|db|database|changelogs?|liquibase|flyway)\//i;
1821
2765
  var CONTRACT = /\.(proto|avsc|wsdl|xsd)$/i;
1822
- var API_SCHEMA = /\.(graphql|gql)$|(^|\/)(openapi|swagger|asyncapi)[^/]*\.(ya?ml|json)$/i;
2766
+ var API_SCHEMA = /\.(graphql|gql)$/i;
1823
2767
  var STRUCTURED = /\.(ya?ml|json|xml|config|properties|toml|ini|conf)$/i;
1824
- function fileKinds(path) {
2768
+ function fileKinds(path, rules = DEFAULT_READ_RULES) {
1825
2769
  if (GATEWAY_FILE.test(path))
1826
2770
  return [...GATEWAY_KINDS];
1827
2771
  const k = /* @__PURE__ */ new Set();
@@ -1837,7 +2781,7 @@ function fileKinds(path) {
1837
2781
  add(["data"]);
1838
2782
  if (CONTRACT.test(path))
1839
2783
  add(["protocols"]);
1840
- if (API_SCHEMA.test(path))
2784
+ if (API_SCHEMA.test(path) || rules.apiSpec.test(path))
1841
2785
  add(["routes", "protocols"]);
1842
2786
  const isCode = SERVER_CODE.test(path) || DEVICE_CODE.test(path) || SCRIPT_CODE.test(path);
1843
2787
  if (PAGE_CODE.test(path) || DEVICE_CODE.test(path))
@@ -1873,27 +2817,45 @@ var linesOf = (text) => {
1873
2817
  const t = text.replace(/\r\n?/g, "\n");
1874
2818
  return (t.endsWith("\n") ? t.slice(0, -1) : t).split("\n");
1875
2819
  };
2820
+ var LONG_LINE = 1e3;
2821
+ function shownLine(line) {
2822
+ if (line.length <= LONG_LINE)
2823
+ return line;
2824
+ const code2 = line.charCodeAt(LONG_LINE - 1);
2825
+ const at = code2 >= 55296 && code2 <= 56319 ? LONG_LINE - 1 : LONG_LINE;
2826
+ return `${line.slice(0, at)} \u2026 [line cut: ${line.length} characters]`;
2827
+ }
1876
2828
  function numberedSlice(text, s) {
1877
- return linesOf(text).slice(s.from - 1, s.to).map((l, i) => `${s.from + i}| ${l}`).join("\n");
2829
+ return linesOf(text).slice(s.from - 1, s.to).map((l, i) => `${s.from + i}| ${shownLine(l)}`).join("\n");
1878
2830
  }
1879
2831
  var header = (s) => `${s.path} lines ${s.from}-${s.to} of ${s.of} (numbered as in the file)`;
1880
2832
  function sliceText(source, s) {
1881
2833
  return `${header(s)}
1882
2834
  ${numberedSlice(source.read(s.path) ?? "", s)}`;
1883
2835
  }
1884
- var tokensOf = (chars) => Math.ceil(chars / 4);
2836
+ var tokensOf = (units) => Math.ceil(units / 4);
2837
+ var wrapUnits = (path) => tokenUnits(`<atticus-data id="1-file-0000" kind="code" origin="${path}#L0000000-0000000">
2838
+
2839
+ </atticus-data>
2840
+
2841
+ `);
1885
2842
  function planBatches(source, o) {
1886
2843
  const room = Math.max(1, o.room);
2844
+ const rules = readRulesOf(o.conventions);
1887
2845
  const excludeRes = (o.exclude ?? []).map(globToRegExp);
1888
2846
  const excluded = [];
2847
+ const leaveOut = new Set(o.reach?.leaveOut ?? []);
2848
+ const notReached = [];
1889
2849
  const paths = [];
1890
2850
  const texts = /* @__PURE__ */ new Map();
1891
2851
  for (const p of source.list()) {
2852
+ if (!isSourceFile(p, "", void 0, rules) || tooBigToRead(source, p))
2853
+ continue;
1892
2854
  const text = source.read(p);
1893
2855
  if (text === void 0)
1894
2856
  continue;
1895
2857
  const ls = linesOf(text);
1896
- if (!isSourceFile(p, ls[0] ?? "", ls))
2858
+ if (!isSourceFile(p, ls[0] ?? "", ls, rules))
1897
2859
  continue;
1898
2860
  if (excludeRes.some((re) => re.test(p))) {
1899
2861
  excluded.push(p);
@@ -1901,6 +2863,10 @@ function planBatches(source, o) {
1901
2863
  }
1902
2864
  if (!text.trim())
1903
2865
  continue;
2866
+ if (leaveOut.has(p)) {
2867
+ notReached.push(p);
2868
+ continue;
2869
+ }
1904
2870
  paths.push(p);
1905
2871
  texts.set(p, text);
1906
2872
  }
@@ -1916,16 +2882,17 @@ function planBatches(source, o) {
1916
2882
  const slices = [];
1917
2883
  let lines = 0;
1918
2884
  let leastRoom = room;
1919
- for (const path of readingOrder(paths)) {
1920
- const kinds2 = fileKinds(path);
2885
+ for (const path of reachOrder(paths, o.reach?.first)) {
2886
+ const kinds2 = fileKinds(path, rules);
1921
2887
  const left = room - overhead(kinds2);
1922
2888
  leastRoom = Math.min(leastRoom, left);
1923
2889
  const fileRoom = Math.max(1, left);
1924
2890
  const all2 = linesOf(texts.get(path));
1925
2891
  const of = all2.length;
1926
2892
  lines += of;
1927
- const sizes = all2.map((l, i) => `${i + 1}| ${l}`.length + 1);
1928
- const cost = (from2, chars) => tokensOf(header({ path, from: from2, to: of, of }).length + chars);
2893
+ const sizes = all2.map((l, i) => tokenUnits(`${i + 1}| ${shownLine(l)}`) + 1);
2894
+ const wrap = wrapUnits(path);
2895
+ const cost = (from2, units) => tokensOf(tokenUnits(header({ path, from: from2, to: of, of })) + wrap + units);
1929
2896
  const total = sizes.reduce((n, c) => n + c, 0);
1930
2897
  if (cost(1, total) <= fileRoom) {
1931
2898
  slices.push({ path, from: 1, to: of, of, tokens: cost(1, total), kinds: kinds2 });
@@ -1965,9 +2932,20 @@ function planBatches(source, o) {
1965
2932
  tokens: slices.reduce((n, s) => n + s.tokens, 0),
1966
2933
  batches,
1967
2934
  excluded: excluded.sort(),
1968
- leastRoom
2935
+ leastRoom,
2936
+ notReached: notReached.sort()
1969
2937
  };
1970
2938
  }
2939
+ function reachOrder(paths, first) {
2940
+ const order = readingOrder(paths);
2941
+ if (!first?.length)
2942
+ return order;
2943
+ const has = new Set(paths);
2944
+ const head = order.filter((p) => readingRank(p) <= 1);
2945
+ const lead = [...new Set(first)].filter((p) => has.has(p) && readingRank(p) > 1);
2946
+ const taken = /* @__PURE__ */ new Set([...head, ...lead]);
2947
+ return [...head, ...lead, ...order.filter((p) => !taken.has(p))];
2948
+ }
1971
2949
 
1972
2950
  // packages/verify/dist/vendored.js
1973
2951
  var LICENSE_FILE = /^(LICEN[CS]E|COPYING)([._-][^/]*)?$/i;
@@ -2032,10 +3010,10 @@ function buildModules(path, text) {
2032
3010
  return found.filter((m) => !m.startsWith("!")).map(clean).filter(Boolean).map((m) => dir ? `${dir}/${m}` : m);
2033
3011
  }
2034
3012
  var JAVA_LIKE = /\.(java|kt|kts|scala|groovy)$/i;
2035
- var DOTNET = /\.(cs|vb|fs)$/i;
3013
+ var DOTNET2 = /\.(cs|vb|fs)$/i;
2036
3014
  var rootOf = (name) => name.split(".").slice(0, 2).join(".").toLowerCase();
2037
3015
  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;
3016
+ 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
3017
  const m = re?.exec(text);
2040
3018
  return m ? rootOf(m[1]) : void 0;
2041
3019
  }
@@ -2053,7 +3031,7 @@ function packageRoots(paths, inits, read) {
2053
3031
  }
2054
3032
  if (top)
2055
3033
  roots.add(nameOf(top).toLowerCase());
2056
- } else if ((JAVA_LIKE.test(p) || DOTNET.test(p)) && readCount < PACKAGE_SAMPLE) {
3034
+ } else if ((JAVA_LIKE.test(p) || DOTNET2.test(p)) && readCount < PACKAGE_SAMPLE) {
2057
3035
  readCount++;
2058
3036
  const r = packageRoot(p, read(p) ?? "");
2059
3037
  if (r)
@@ -2233,6 +3211,27 @@ function exclusionYaml(proposals) {
2233
3211
  ].join("\n");
2234
3212
  }
2235
3213
  var SESSION_TOKENS = 15e4;
3214
+ var SESSION_SHARE = 0.45;
3215
+ function sessionTokensFor(limit) {
3216
+ return limit && limit > 0 ? Math.floor(limit * SESSION_SHARE) : SESSION_TOKENS;
3217
+ }
3218
+ var RANGE_OVERLAP_LINES = 20;
3219
+ var RANGE_OVERLAP_SHARE = 0.02;
3220
+ function rangeOverlap(each) {
3221
+ return Math.min(Math.floor(each / 5), Math.max(RANGE_OVERLAP_LINES, Math.ceil(RANGE_OVERLAP_SHARE * each)));
3222
+ }
3223
+ function lineRanges(f, room) {
3224
+ const n = Math.max(1, Math.min(f.lines, Math.ceil(f.tokens / (0.8 * room))));
3225
+ const each = Math.ceil(f.lines / n);
3226
+ const overlap = rangeOverlap(each);
3227
+ const out = [];
3228
+ for (let start = 1; start <= f.lines; start += each) {
3229
+ const from = start === 1 ? 1 : start - overlap;
3230
+ const to = Math.min(f.lines, start + each - 1);
3231
+ out.push({ from, to, start, tokens: Math.ceil(f.tokens * (to - from + 1) / f.lines) });
3232
+ }
3233
+ return out;
3234
+ }
2236
3235
  function unitsOf(dir, fs, room) {
2237
3236
  const prefix = dir ? `${dir}/` : "";
2238
3237
  const direct = [];
@@ -2254,15 +3253,105 @@ function unitsOf(dir, fs, room) {
2254
3253
  out.push(...unitsOf(child, children.get(child), room));
2255
3254
  return out;
2256
3255
  }
2257
- function planParts(files, room = SESSION_TOKENS) {
3256
+ function groupUnits(dir, fs, room, holds) {
3257
+ const prefix = dir ? `${dir}/` : "";
3258
+ const direct = [];
3259
+ const children = /* @__PURE__ */ new Map();
3260
+ for (const f of fs) {
3261
+ const rest = f.path.slice(prefix.length);
3262
+ const slash = rest.indexOf("/");
3263
+ if (slash < 0)
3264
+ direct.push(f);
3265
+ else {
3266
+ const child = prefix + rest.slice(0, slash);
3267
+ children.set(child, [...children.get(child) ?? [], f]);
3268
+ }
3269
+ }
3270
+ if (dir && holds(dir, true, fs.length) && (sum(fs) <= room || !children.size))
3271
+ return [{ glob: `${dir}/**`, files: fs.length, tokens: sum(fs) }];
3272
+ const out = [];
3273
+ if (direct.length) {
3274
+ if (holds(dir, false, direct.length))
3275
+ out.push({ glob: `${prefix}*`, files: direct.length, tokens: sum(direct) });
3276
+ else
3277
+ for (const f of [...direct].sort((a, b) => byPath2(a.path, b.path)))
3278
+ out.push({ glob: f.path, files: 1, tokens: f.tokens });
3279
+ }
3280
+ for (const child of [...children.keys()].sort(byPath2))
3281
+ out.push(...groupUnits(child, children.get(child), room, holds));
3282
+ return out;
3283
+ }
3284
+ function rangeUnits(f, room) {
3285
+ return lineRanges({ ...f, lines: f.lines }, room).map((r, i) => ({
3286
+ glob: `${f.path}#L${r.from}-L${r.to}`,
3287
+ files: i === 0 ? 1 : 0,
3288
+ tokens: r.tokens
3289
+ }));
3290
+ }
3291
+ function orderedUnits(files, room, o) {
3292
+ const isBig = (f) => f.tokens > room && (f.lines ?? 0) > 1;
3293
+ const byFile = new Map(files.map((f) => [f.path, f]));
3294
+ const head = new Set(o.head.filter((p) => byFile.has(p)));
3295
+ const models = [...new Set(o.models)].filter((p) => byFile.has(p) && !head.has(p));
3296
+ const inModels = new Set(models);
3297
+ const rest = files.filter((f) => !head.has(f.path) && !inModels.has(f.path));
3298
+ const all2 = [...files.map((f) => f.path), ...o.leftOut];
3299
+ const counts = () => {
3300
+ const deep = /* @__PURE__ */ new Map();
3301
+ const own = /* @__PURE__ */ new Map();
3302
+ const count = (m, k) => m.set(k, (m.get(k) ?? 0) + 1);
3303
+ for (const p of all2) {
3304
+ const parts = p.split("/");
3305
+ count(own, parts.slice(0, -1).join("/"));
3306
+ for (let d = 1; d < parts.length; d++)
3307
+ count(deep, parts.slice(0, d).join("/"));
3308
+ }
3309
+ return (dir, isDeep, n) => (isDeep ? deep.get(dir) : own.get(dir)) === n;
3310
+ };
3311
+ const holds = counts();
3312
+ const unitsOfGroup = (group) => {
3313
+ const small = group.filter((f) => !isBig(f)).sort((a, b) => byPath2(a.path, b.path));
3314
+ const out = groupUnits("", small, room, holds);
3315
+ for (const f of group.filter(isBig))
3316
+ out.push(...rangeUnits(f, room));
3317
+ return out;
3318
+ };
3319
+ return [
3320
+ ...unitsOfGroup(files.filter((f) => head.has(f.path))),
3321
+ ...models.flatMap((p) => {
3322
+ const f = byFile.get(p);
3323
+ return isBig(f) ? rangeUnits(f, room) : [{ glob: p, files: 1, tokens: f.tokens }];
3324
+ }),
3325
+ ...unitsOfGroup(rest)
3326
+ ];
3327
+ }
3328
+ function planParts(files, room = SESSION_TOKENS, order) {
2258
3329
  const total = sum(files);
2259
3330
  if (total <= room)
2260
3331
  return [];
2261
- const sorted = [...files].sort((a, b) => byPath2(a.path, b.path));
3332
+ let units;
3333
+ if (order)
3334
+ units = orderedUnits(files, room, order);
3335
+ else {
3336
+ const big = files.filter((f) => f.tokens > room && (f.lines ?? 0) > 1);
3337
+ const rest = files.filter((f) => !big.includes(f));
3338
+ const ranges = [];
3339
+ for (const f of big)
3340
+ for (const [i, r] of lineRanges({ ...f, lines: f.lines }, room).entries())
3341
+ ranges.push({
3342
+ glob: `${f.path}#L${r.from}-L${r.to}`,
3343
+ files: i === 0 ? 1 : 0,
3344
+ tokens: r.tokens,
3345
+ key: `${f.path}\0${String(r.start).padStart(12, "0")}`
3346
+ });
3347
+ const sorted = [...rest].sort((a, b) => byPath2(a.path, b.path));
3348
+ const keyOf = (g) => g.replace(/\/?\*\*?$/, "");
3349
+ 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);
3350
+ }
2262
3351
  const target = total / Math.ceil(total / room);
2263
3352
  const parts = [];
2264
3353
  let current;
2265
- for (const u of unitsOf("", sorted, room)) {
3354
+ for (const u of units) {
2266
3355
  if (current && (current.tokens + u.tokens > room || current.tokens >= target)) {
2267
3356
  parts.push(current);
2268
3357
  current = void 0;
@@ -2277,13 +3366,104 @@ function planParts(files, room = SESSION_TOKENS) {
2277
3366
  return parts;
2278
3367
  }
2279
3368
 
3369
+ // packages/verify/dist/gaps.js
3370
+ var GAP_CONTEXT_LINES = 20;
3371
+ var byPath3 = (a, b) => a < b ? -1 : a > b ? 1 : 0;
3372
+ function quoted2(v) {
3373
+ return JSON.stringify(v).replace(/[\u007f-\u009f\u2028\u2029]/g, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`);
3374
+ }
3375
+ function split(cite2) {
3376
+ const m = /^(.+):(\d+)$/.exec(cite2);
3377
+ return m ? { file: m[1], line: Number(m[2]) } : void 0;
3378
+ }
3379
+ function planGaps(from, size, room, o = {}) {
3380
+ const whole = new Set(from.unknown ?? []);
3381
+ const lines = /* @__PURE__ */ new Map();
3382
+ const note = (cite2, why) => {
3383
+ const at = split(cite2);
3384
+ if (!at)
3385
+ return;
3386
+ const of = lines.get(at.file) ?? /* @__PURE__ */ new Map();
3387
+ of.set(at.line, [...of.get(at.line) ?? [], why]);
3388
+ lines.set(at.file, of);
3389
+ };
3390
+ for (const s of from.skipped ?? [])
3391
+ note(s.cite, `writes the ${s.kind} ${quoted2(s.value)}, which no fact cites`);
3392
+ for (const h of from.reviewedHolds ?? [])
3393
+ for (const s of h.skipped)
3394
+ 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`);
3395
+ for (const o2 of from.specOpsUncovered ?? [])
3396
+ note(o2.cite, `declares ${o2.method} ${quoted2(o2.path)}, which no entry covers: find the code that serves it`);
3397
+ for (const e of from.entriesNoFlowFollows ?? [])
3398
+ 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`);
3399
+ const unlisted = Math.max(0, (from.skippedCount ?? 0) - (from.skipped?.length ?? 0)) + Math.max(0, (from.specOpsUncoveredCount ?? 0) - (from.specOpsUncovered?.length ?? 0));
3400
+ const gone = [];
3401
+ const reads = [];
3402
+ const named = [.../* @__PURE__ */ new Set([...whole, ...lines.keys()])].sort(byPath3);
3403
+ const files = o.first?.length ? reachOrder(named, o.first) : named;
3404
+ for (const file of files) {
3405
+ const sz = size(file);
3406
+ if (!sz) {
3407
+ gone.push(file);
3408
+ continue;
3409
+ }
3410
+ const at = lines.get(file) ?? /* @__PURE__ */ new Map();
3411
+ 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}`));
3412
+ const share = (a, b) => Math.ceil(sz.tokens * (b - a + 1) / sz.lines);
3413
+ if (whole.has(file)) {
3414
+ const head = "no fact cites it: read it whole";
3415
+ if (sz.tokens <= room || sz.lines <= 1) {
3416
+ reads.push({ path: file, file, tokens: sz.tokens, why: [head, ...whys(1, sz.lines)] });
3417
+ continue;
3418
+ }
3419
+ for (const r of lineRanges(sz, room))
3420
+ reads.push({
3421
+ path: `${file}#L${r.from}-L${r.to}`,
3422
+ file,
3423
+ tokens: r.tokens,
3424
+ // R4: a line the ranges share is named in the range it is its own in, the earlier one.
3425
+ why: ["no fact cites the file: read these lines", ...whys(r.start, r.to)]
3426
+ });
3427
+ continue;
3428
+ }
3429
+ const windows = [];
3430
+ for (const l of [...at.keys()].sort((a, b) => a - b)) {
3431
+ const a = Math.max(1, l - GAP_CONTEXT_LINES);
3432
+ const b = Math.min(sz.lines, l + GAP_CONTEXT_LINES);
3433
+ const last = windows.at(-1);
3434
+ if (last && a <= last[1] + 1)
3435
+ last[1] = Math.max(last[1], b);
3436
+ else
3437
+ windows.push([a, b]);
3438
+ }
3439
+ for (const [a, b] of windows)
3440
+ reads.push({ path: `${file}#L${a}-L${b}`, file, tokens: share(a, b), why: whys(a, b) });
3441
+ }
3442
+ const sessions = [];
3443
+ for (const r of reads) {
3444
+ const last = sessions.at(-1);
3445
+ if (last && last.tokens + r.tokens <= room) {
3446
+ last.reads.push(r);
3447
+ last.tokens += r.tokens;
3448
+ } else
3449
+ sessions.push({ reads: [r], tokens: r.tokens });
3450
+ }
3451
+ return {
3452
+ sessions,
3453
+ files: new Set(reads.map((r) => r.file)).size,
3454
+ tokens: reads.reduce((n, r) => n + r.tokens, 0),
3455
+ unlisted,
3456
+ gone
3457
+ };
3458
+ }
3459
+
2280
3460
  // packages/diagrams/dist/views.js
2281
3461
  var VIEW_NAMES = ["context", "container", "data", "event", "sequence"];
2282
3462
  var ViewError = class extends Error {
2283
3463
  };
2284
3464
  var cmp = (a, b) => a < b ? -1 : a > b ? 1 : 0;
2285
3465
  var slug = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
2286
- var normPath = (p) => p.replace(/\{[^}]*\}/g, "{}").replace(/\/+$/, "") || "/";
3466
+ var normPath2 = (p) => p.replace(/\{[^}]*\}/g, "{}").replace(/\/+$/, "") || "/";
2287
3467
  function legendOf(status) {
2288
3468
  if (status === "planned-new")
2289
3469
  return "new";
@@ -2372,7 +3552,7 @@ var Catalog = class {
2372
3552
  what = this.topicOf(i);
2373
3553
  else {
2374
3554
  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;
3555
+ const path = ep?.endpointType === "rest" && normPath2(ep.pathOrTopic) === normPath2(i.normalizedPath) ? ep.pathOrTopic : i.normalizedPath;
2376
3556
  what = [i.method?.toUpperCase(), path].filter(Boolean).join(" ");
2377
3557
  }
2378
3558
  return [i.pattern?.code, what].filter(Boolean).join(" ");
@@ -2776,8 +3956,8 @@ function changeView(records2, change, flows, known = []) {
2776
3956
  const nodes = [];
2777
3957
  for (const id of comps) {
2778
3958
  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;
3959
+ const base2 = c.components.has(id) || c.isSystem(id) ? c.endNode(id, sys) : { id, label: id, kind: "component" };
3960
+ const { legend: _status, ...rest } = base2;
2781
3961
  nodes.push({ ...rest, label: marked(rest.label, legends.get(id)), ...withMark(id) });
2782
3962
  }
2783
3963
  for (const t of tables) {
@@ -2873,14 +4053,14 @@ var Ids = class {
2873
4053
  const known = this.map.get(key);
2874
4054
  if (known)
2875
4055
  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;
4056
+ let base2 = raw.replace(/[^A-Za-z0-9_]/g, "_");
4057
+ if (!/^[A-Za-z]/.test(base2))
4058
+ base2 = `n_${base2}`;
4059
+ if (RESERVED.has(base2.toLowerCase()))
4060
+ base2 = `${base2}_`;
4061
+ let id = base2;
2882
4062
  for (let n = 2; this.used.has(id); n++)
2883
- id = `${base}_${n}`;
4063
+ id = `${base2}_${n}`;
2884
4064
  this.used.add(id);
2885
4065
  this.map.set(key, id);
2886
4066
  return id;
@@ -3069,8 +4249,8 @@ function nodeStyle(n) {
3069
4249
  return styleText(SHAPES.topic, legend);
3070
4250
  if (n.boundary === "vendor")
3071
4251
  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);
4252
+ const base2 = n.kind === "system" ? SHAPES.system : SHAPES.component;
4253
+ return styleText(base2, n.boundary ? BOUNDARY[n.boundary] : void 0, legend);
3074
4254
  }
3075
4255
  function groupStyle(g) {
3076
4256
  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 +4341,14 @@ function edgeStyle(e) {
3161
4341
  }
3162
4342
  var compress = (xml) => deflateRawSync(Buffer.from(encodeURIComponent(xml), "utf8"), { level: 9 }).toString("base64");
3163
4343
  function shapeLibrary() {
3164
- const base = [
4344
+ const base2 = [
3165
4345
  ["System", SHAPES.system, W, H],
3166
4346
  ["Component", SHAPES.component, W, H],
3167
4347
  ["Vendor", SHAPES.vendor, W, H + 20],
3168
4348
  ["Data store", SHAPES.datastore, 120, 80],
3169
4349
  ["Topic", SHAPES.topic, W, H]
3170
4350
  ];
3171
- const entries = base.flatMap(([title, style, w, h]) => [void 0, "new", "modify", "retire"].map((legend) => {
4351
+ const entries = base2.flatMap(([title, style, w, h]) => [void 0, "new", "modify", "retire"].map((legend) => {
3172
4352
  const value = attr(title);
3173
4353
  const st = attr(styleText(style, legend ? LEGEND[legend] : void 0));
3174
4354
  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 +4487,91 @@ var STATE = {
3307
4487
  };
3308
4488
  function recordGaps(r) {
3309
4489
  const g = [];
3310
- if (r.confidence === "inferred")
4490
+ if (r.confidence === "inferred" && !r.flags?.includes(JOINED_BY_PERSON))
3311
4491
  g.push("inferred");
3312
4492
  for (const f of r.flags ?? [])
3313
- g.push(f);
4493
+ if (!JOIN_HOW_FLAGS.has(f) && !LINK_EVIDENCE_NOT_GAPS.has(f))
4494
+ g.push(f);
3314
4495
  return g;
3315
4496
  }
4497
+ var MAX_JOINS = 20;
4498
+ var HOW_OF_FLAG = {
4499
+ "joined-by-path": "path",
4500
+ "joined-by-label": "label",
4501
+ "joined-by-platform-setting": "platform-setting",
4502
+ "joined-by-folder-name": "folder-name",
4503
+ "joined-despite-conflict": "conflict",
4504
+ "joined-by-service-name": "service-name",
4505
+ // G1 (ADR 0174): a person paired the call with the entry on the canvas.
4506
+ "joined-by-person": "person",
4507
+ // ADR 0181: the AI paired it, applied by Atticus from its run: the AI's word, not a person's.
4508
+ [JOINED_BY_AI]: "ai",
4509
+ "joined-by-answer": "answer",
4510
+ "joined-by-name": "named",
4511
+ "joined-by-repo": "repo",
4512
+ "joined-by-host": "host",
4513
+ "declared-caller": "declared",
4514
+ // ADR 0162 §6 (F8): a topic or queue the profile names as an outside system's: a person's word.
4515
+ [DECLARED_TOPIC]: "declared",
4516
+ // ADR 0160 (O3): answered by an entry of another team's shared map.
4517
+ [JOINED_TO_SHARED_MAP]: "shared"
4518
+ };
4519
+ function joinHow(i) {
4520
+ for (const f of i.flags ?? [])
4521
+ if (HOW_OF_FLAG[f])
4522
+ return HOW_OF_FLAG[f];
4523
+ if (i.via === "event-bus" || i.via === "mq")
4524
+ return "topic";
4525
+ return "unknown";
4526
+ }
4527
+ function joinOf(i) {
4528
+ const side = (s) => evidenceOf(i.evidence.filter((e) => e.side === s));
4529
+ const sided = /* @__PURE__ */ new Set(["caller", "callee", "gateway"]);
4530
+ return {
4531
+ interface: i.id,
4532
+ from: i.from,
4533
+ to: i.to,
4534
+ how: joinHow(i),
4535
+ gateway: i.via === "gateway",
4536
+ verified: i.confidence === "verified",
4537
+ ...i.method ? { method: i.method } : {},
4538
+ ...i.normalizedPath ? { path: i.normalizedPath } : {},
4539
+ caller: side("caller"),
4540
+ callee: side("callee"),
4541
+ hops: side("gateway"),
4542
+ other: evidenceOf(i.evidence.filter((e) => !e.side || !sided.has(e.side))),
4543
+ ...sharedOf(i),
4544
+ ...aiRunOf(i)
4545
+ };
4546
+ }
4547
+ function aiRunOf(i) {
4548
+ if (!i.flags?.includes(JOINED_BY_AI))
4549
+ return {};
4550
+ const run = i.evidence.find((e) => e.source === "ai" && e.run)?.run;
4551
+ return run ? { run } : {};
4552
+ }
4553
+ function sharedOf(i) {
4554
+ if (!i.flags?.includes(JOINED_TO_SHARED_MAP))
4555
+ return {};
4556
+ const e = i.evidence.find((x) => x.extractor === SHARED_MAP_EVIDENCE && x.side === "callee" && x.repo && x.path);
4557
+ if (!e)
4558
+ return {};
4559
+ return {
4560
+ shared: {
4561
+ team: e.by ?? "",
4562
+ repo: e.repo,
4563
+ at: e.line === void 0 ? e.path : `${e.path}:${e.line}`,
4564
+ ...e.commit ? { commit: e.commit } : {}
4565
+ }
4566
+ };
4567
+ }
4568
+ function mergeJoins(a, b) {
4569
+ const by = new Map(a.map((j) => [j.interface, j]));
4570
+ for (const j of b)
4571
+ if (!by.has(j.interface))
4572
+ by.set(j.interface, j);
4573
+ return [...by.values()].sort((x, y) => x.interface < y.interface ? -1 : x.interface > y.interface ? 1 : 0).slice(0, MAX_JOINS);
4574
+ }
3316
4575
  var props = (o) => {
3317
4576
  const out = {};
3318
4577
  for (const [k, v] of Object.entries(o)) {
@@ -3462,6 +4721,7 @@ function buildEstate(records2) {
3462
4721
  add(fromRecord(c, `component:${c.id}`, "component", c.id, parent, { type: c.type, runtime: c.runtime, repo: c.repo, path: c.path }, gaps));
3463
4722
  }
3464
4723
  const endpointNode = /* @__PURE__ */ new Map();
4724
+ const topicByPath = /* @__PURE__ */ new Map();
3465
4725
  for (const e of all(records2, "Endpoint")) {
3466
4726
  const topic = TOPIC_TYPES.has(e.endpointType);
3467
4727
  const id = `${topic ? "topic" : "endpoint"}:${e.id}`;
@@ -3478,7 +4738,30 @@ function buildEstate(records2) {
3478
4738
  auth: e.auth
3479
4739
  }, gaps));
3480
4740
  endpointNode.set(e.id, id);
4741
+ if (topic)
4742
+ topicByPath.set(e.pathOrTopic, id);
3481
4743
  }
4744
+ const busTopic = (name, parent, ev = []) => {
4745
+ const known = topicByPath.get(name);
4746
+ if (known)
4747
+ return known;
4748
+ const id = add({
4749
+ id: `topic:bus.${slug2(name)}`,
4750
+ kind: "topic",
4751
+ label: name,
4752
+ parent,
4753
+ state: "current",
4754
+ gaps: [],
4755
+ evidence: ev,
4756
+ properties: { pathOrTopic: name },
4757
+ next: {
4758
+ command: `atticus query "${name}"`,
4759
+ why: "List the records that name this topic or queue."
4760
+ }
4761
+ });
4762
+ topicByPath.set(name, id);
4763
+ return id;
4764
+ };
3482
4765
  const storeNode = /* @__PURE__ */ new Map();
3483
4766
  for (const d of all(records2, "DataStore")) {
3484
4767
  const owner = d.ownerComponent && components.has(d.ownerComponent);
@@ -3523,9 +4806,33 @@ function buildEstate(records2) {
3523
4806
  const ev = evidenceOf(i.evidence);
3524
4807
  const gaps = [...recordGaps(i), ...i.conflicts.length ? ["conflicts"] : []];
3525
4808
  const target = i.endpoint ? endpointNode.get(i.endpoint) : void 0;
4809
+ const join2 = joinOf(i);
3526
4810
  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 });
4811
+ atoms.push({ from, to: target, type: "publishes", evidence: ev, gaps, join: join2 });
4812
+ atoms.push({ from: to, to: target, type: "subscribes", evidence: ev, gaps, join: join2 });
4813
+ continue;
4814
+ }
4815
+ if (!i.endpoint && (i.via === "event-bus" || i.via === "mq") && i.normalizedPath) {
4816
+ const isSystem = (n) => n.startsWith("system:") || n.startsWith("unknown:");
4817
+ const [home, homeRef] = !isSystem(to) && isSystem(from) ? [from, i.from] : [to, i.to];
4818
+ const t = busTopic(i.normalizedPath, home);
4819
+ const owner = nodes.get(t);
4820
+ if (owner.parent === home && isSystem(home)) {
4821
+ owner.properties = { ...owner.properties, owner: nodes.get(home)?.label ?? home };
4822
+ const b = nodes.get(home)?.properties.boundary;
4823
+ if (outside.has(homeRef) || b === "vendor" || b === "outside")
4824
+ owner.properties.ownerOutside = true;
4825
+ }
4826
+ if (join2.shared)
4827
+ owner.properties = { ...owner.properties, owner: join2.shared.team, ownerOutside: true };
4828
+ if (i.flags?.includes(DECLARED_TOPIC)) {
4829
+ owner.properties = { ...owner.properties, declared: true };
4830
+ owner.gaps = [.../* @__PURE__ */ new Set([...owner.gaps, DECLARED_TOPIC])];
4831
+ owner.evidence = mergeEvidence(owner.evidence, evidenceOf(i.evidence.filter((e) => e.source === "manual")));
4832
+ }
4833
+ atoms.push({ from, to: t, type: "publishes", evidence: ev, gaps, join: join2 });
4834
+ if (to.startsWith("component:"))
4835
+ atoms.push({ from: to, to: t, type: "subscribes", evidence: ev, gaps, join: join2 });
3529
4836
  continue;
3530
4837
  }
3531
4838
  let caller = from;
@@ -3556,7 +4863,24 @@ function buildEstate(records2) {
3556
4863
  sync: i.sync,
3557
4864
  via: i.via,
3558
4865
  evidence: ev,
3559
- gaps: [...gaps, ...i.endpoint && !target ? ["no-endpoint"] : []]
4866
+ gaps: [...gaps, ...i.endpoint && !target ? ["no-endpoint"] : []],
4867
+ join: join2
4868
+ });
4869
+ }
4870
+ for (const u of all(records2, "Unresolved")) {
4871
+ if (u.unresolvedKind !== "event-publish" && u.unresolvedKind !== "event-subscribe")
4872
+ continue;
4873
+ if (!u.raw || u.raw.includes("${"))
4874
+ continue;
4875
+ const owner = u.owner ? partNode(u.owner) : "estate";
4876
+ const ev = evidenceOf(u.evidence);
4877
+ const t = busTopic(u.raw, u.unresolvedKind === "event-subscribe" ? owner : "estate", ev);
4878
+ atoms.push({
4879
+ from: owner,
4880
+ to: t,
4881
+ type: u.unresolvedKind === "event-publish" ? "publishes" : "subscribes",
4882
+ evidence: ev,
4883
+ gaps: recordGaps(u)
3560
4884
  });
3561
4885
  }
3562
4886
  for (const c of components.values())
@@ -3611,6 +4935,37 @@ function buildEstate(records2) {
3611
4935
  gaps: recordGaps(k)
3612
4936
  });
3613
4937
  }
4938
+ const busy = /* @__PURE__ */ new Set();
4939
+ const internal = (id) => {
4940
+ const n = nodes.get(id);
4941
+ return !!n && n.kind !== "unknown" && n.properties.boundary !== "outside" && n.properties.boundary !== "vendor";
4942
+ };
4943
+ for (const a of atoms)
4944
+ if (a.type === "publishes" || a.type === "subscribes") {
4945
+ if (internal(a.from))
4946
+ busy.add(`${a.type}|${a.to}`);
4947
+ }
4948
+ for (const n of nodes.values()) {
4949
+ if (n.kind !== "topic")
4950
+ continue;
4951
+ if (n.properties.owner === void 0) {
4952
+ let p = n.parent ? nodes.get(n.parent) : void 0;
4953
+ while (p && p.kind !== "system" && p.kind !== "repo" && p.kind !== "estate")
4954
+ p = p.parent ? nodes.get(p.parent) : void 0;
4955
+ if (p && p.kind !== "estate") {
4956
+ let sys = p;
4957
+ while (sys && sys.kind !== "system")
4958
+ sys = sys.parent ? nodes.get(sys.parent) : void 0;
4959
+ n.properties = { ...n.properties, owner: (sys ?? p).label };
4960
+ if (sys && (sys.properties.boundary === "outside" || sys.properties.boundary === "vendor"))
4961
+ n.properties.ownerOutside = true;
4962
+ }
4963
+ }
4964
+ if (!busy.has(`publishes|${n.id}`))
4965
+ n.gaps.push("no-publisher");
4966
+ if (!busy.has(`subscribes|${n.id}`))
4967
+ n.gaps.push("no-listener");
4968
+ }
3614
4969
  const kids = /* @__PURE__ */ new Map();
3615
4970
  for (const n of nodes.values()) {
3616
4971
  if (!n.parent)
@@ -3705,10 +5060,13 @@ function merge(mapped) {
3705
5060
  count: 1,
3706
5061
  state: "current",
3707
5062
  gaps: [...new Set(atom.gaps)].sort(),
3708
- evidence: mergeEvidence(atom.evidence)
5063
+ evidence: mergeEvidence(atom.evidence),
5064
+ ...atom.join ? { joins: [atom.join] } : {}
3709
5065
  });
3710
5066
  continue;
3711
5067
  }
5068
+ if (atom.join)
5069
+ e.joins = mergeJoins(e.joins ?? [], [atom.join]);
3712
5070
  e.count++;
3713
5071
  if (atom.sync && e.sync !== atom.sync)
3714
5072
  e.sync = "mixed";
@@ -3772,16 +5130,27 @@ function nodeView(est, id, o) {
3772
5130
  return ax[i] ?? x;
3773
5131
  };
3774
5132
  const types = LENS_EDGES[lens];
5133
+ const onBus = (atom, b) => (atom.type === "publishes" || atom.type === "subscribes") && est.nodes.get(b)?.kind === "topic" && b !== id;
5134
+ const drawnTopics = /* @__PURE__ */ new Set();
5135
+ for (const atom of est.atoms) {
5136
+ if (!types.has(atom.type))
5137
+ continue;
5138
+ const a = lift(est, lens, atom.from);
5139
+ const b = lift(est, lens, atom.to);
5140
+ if (onBus(atom, b) && (inside(a) || inside(b)))
5141
+ drawnTopics.add(b);
5142
+ }
3775
5143
  const mapped = [];
3776
5144
  for (const atom of est.atoms) {
3777
5145
  if (!types.has(atom.type))
3778
5146
  continue;
3779
5147
  const a = lift(est, lens, atom.from);
3780
5148
  const b = lift(est, lens, atom.to);
3781
- if (!inside(a) && !inside(b))
5149
+ const bus = onBus(atom, b);
5150
+ if (!inside(a) && !inside(b) && !(bus && drawnTopics.has(b)))
3782
5151
  continue;
3783
5152
  const from = place(a);
3784
- const to = place(b);
5153
+ const to = bus ? b : place(b);
3785
5154
  if (from === to)
3786
5155
  continue;
3787
5156
  mapped.push({ from, to, atom });
@@ -3790,10 +5159,12 @@ function nodeView(est, id, o) {
3790
5159
  let hidden = 0;
3791
5160
  if (!o.all)
3792
5161
  ({ 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));
5162
+ const ends = [...new Set(edges.flatMap((e) => [e.from, e.to]))].filter((n) => n !== id && !shownSet.has(n));
5163
+ const deeper = ends.filter((n) => drawnTopics.has(n) && inside(n)).sort();
5164
+ const outside = ends.filter((n) => !deeper.includes(n)).sort();
5165
+ const children = [...shown, ...deeper].map((n) => toNode(est, lens, n));
3795
5166
  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;
5167
+ const suggest = lens === "data" && firstTable ? { mode: "focus", id: firstTable.id } : shown.length + deeper.length + outside.length > DETAILED_LIMIT ? { mode: "group" } : void 0;
3797
5168
  return {
3798
5169
  lens,
3799
5170
  node: toNode(est, lens, id),
@@ -3931,13 +5302,13 @@ function codeLinkerFor(o) {
3931
5302
  web = `https://${scp[1]}/${scp[2]}`;
3932
5303
  }
3933
5304
  if (web) {
3934
- const base = web.replace(/\/+$/, "").replace(/\.git$/, "");
3935
- const ado = /(^https?:\/\/dev\.azure\.com\/)|(\.visualstudio\.com\/)/i.test(base);
5305
+ const base2 = web.replace(/\/+$/, "").replace(/\.git$/, "");
5306
+ const ado = /(^https?:\/\/dev\.azure\.com\/)|(\.visualstudio\.com\/)/i.test(base2);
3936
5307
  return (path, line, commit) => {
3937
5308
  const p = encodeURI(path);
3938
5309
  if (ado)
3939
- return `${base}?path=/${p}${commit ? `&version=GC${commit}` : ""}${line ? `&line=${line}` : ""}`;
3940
- return `${base}/blob/${commit ?? "HEAD"}/${p}${line ? `#L${line}` : ""}`;
5310
+ return `${base2}?path=/${p}${commit ? `&version=GC${commit}` : ""}${line ? `&line=${line}` : ""}`;
5311
+ return `${base2}/blob/${commit ?? "HEAD"}/${p}${line ? `#L${line}` : ""}`;
3941
5312
  };
3942
5313
  }
3943
5314
  const dir = (o.dir ?? "").replace(/\/+$/, "");
@@ -4152,6 +5523,27 @@ function config(c) {
4152
5523
  return `- ${code(`${s.key} = ${s.valueOrRef}`)} (${notes.join("; ")}): ${cite(c, ownEvidence(s, c.i.repo))}`;
4153
5524
  });
4154
5525
  }
5526
+ function terms(c) {
5527
+ const ts = c.i.terms ?? [];
5528
+ const owners = /* @__PURE__ */ new Map();
5529
+ for (const r of records(c, "Component"))
5530
+ for (const f of r.flags ?? []) {
5531
+ const m = /^term:[^=]+=(.+)$/.exec(f);
5532
+ if (m && !c.comps.has(r.id))
5533
+ owners.set(m[1], [...owners.get(m[1]) ?? [], r.id]);
5534
+ }
5535
+ const listing = /* @__PURE__ */ new Map();
5536
+ for (const g of records(c, "AccessGrant"))
5537
+ if (g.flags?.some((f) => f.startsWith("term:"))) {
5538
+ const route = c.byId.get(g.resource);
5539
+ const by = route?.kind === "GatewayRoute" ? route.proxy : g.resource;
5540
+ listing.set(g.subject, [.../* @__PURE__ */ new Set([...listing.get(g.subject) ?? [], by])]);
5541
+ }
5542
+ return [...ts].sort((a, b) => cmp2(a.term, b.term) || cmp2(a.value, b.value)).map((t) => {
5543
+ 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)` : ""}`;
5544
+ return `- ${code(t.term)} ${code(t.value)}: ${means} ${cite(c, t.cite)}${t.meaning ? ` (its role from ${cite(c, t.meaning)})` : ""}`;
5545
+ });
5546
+ }
4155
5547
  var flowFile = (f) => `flows/${f.id}.md`;
4156
5548
  function flowsList(c) {
4157
5549
  if (!c.i.flows.length)
@@ -4365,8 +5757,8 @@ function ctxOf(i, file) {
4365
5757
  }
4366
5758
  function renderMap(i) {
4367
5759
  const files = {};
4368
- const base = ctxOf(i, "map.md");
4369
- for (const comp of [...base.comps].sort(cmp2)) {
5760
+ const base2 = ctxOf(i, "map.md");
5761
+ for (const comp of [...base2.comps].sort(cmp2)) {
4370
5762
  const yaml = openApiOf(i, comp);
4371
5763
  if (yaml)
4372
5764
  files[`contracts/${comp}.openapi.yaml`] = yaml;
@@ -4380,17 +5772,18 @@ function renderMap(i) {
4380
5772
  HEADER2(i.repo, "the catalog and its specs"),
4381
5773
  "",
4382
5774
  ...aboutLine(i.about, i.repo),
4383
- ...builtOnLine(base),
5775
+ ...builtOnLine(base2),
4384
5776
  trustLine(i.trust, i.repo),
4385
5777
  "",
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))
5778
+ ...section("What it serves", serves(base2)),
5779
+ ...section("What it calls", calls(base2)),
5780
+ ...section("What it stores", stores(base2)),
5781
+ ...section("Where it runs", deploys(base2)),
5782
+ ...section("Config that names other systems", config(base2)),
5783
+ ...i.terms?.length ? section("Terms", terms(base2)) : [],
5784
+ ...section("Flows", flowsList(base2)),
5785
+ ...section("Contracts", contracts(base2, files)),
5786
+ ...section("Open questions", openQuestions(base2))
4394
5787
  ].join("\n");
4395
5788
  return Object.fromEntries(Object.entries(files).sort(([a], [b]) => cmp2(a, b)));
4396
5789
  }
@@ -4422,15 +5815,25 @@ function rereadPlan(o) {
4422
5815
  const excludeRes = (o.exclude ?? []).map(globToRegExp);
4423
5816
  const status = /* @__PURE__ */ new Map();
4424
5817
  for (const changes of used.values())
4425
- for (const c of changes)
4426
- if (c.status !== "deleted")
5818
+ for (const c of changes) {
5819
+ if (c.status === "deleted")
5820
+ continue;
5821
+ if (c.gitlink) {
5822
+ for (const p of o.source.list())
5823
+ if (p.startsWith(`${c.path}/`))
5824
+ status.set(p, "modified");
5825
+ } else
4427
5826
  status.set(c.path, c.status);
5827
+ }
5828
+ const rules = readRulesOf(o.conventions);
4428
5829
  const readable = [...status.keys()].filter((p) => {
5830
+ if (!isSourceFile(p, "", void 0, rules) || tooBigToRead(o.source, p))
5831
+ return false;
4429
5832
  const text = o.source.read(p);
4430
5833
  if (text === void 0 || !text.trim())
4431
5834
  return false;
4432
5835
  const ls = text.split(/\r?\n/);
4433
- if (!isSourceFile(p, ls[0] ?? "", ls))
5836
+ if (!isSourceFile(p, ls[0] ?? "", ls, readRulesOf(o.conventions)))
4434
5837
  return false;
4435
5838
  return !excludeRes.some((re) => re.test(p));
4436
5839
  });
@@ -4454,10 +5857,242 @@ function onlyFiles(source, paths) {
4454
5857
  return { list: () => source.list().filter((p) => keep.has(p)), read: (p) => source.read(p) };
4455
5858
  }
4456
5859
 
5860
+ // packages/verify/dist/slips.js
5861
+ function slugFactId(id) {
5862
+ const slug3 = 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(/-+$/, "");
5863
+ return notDeviceName(slug3).slice(0, 120);
5864
+ }
5865
+ var isId = (x) => typeof x === "string" && FactId.safeParse(x).success;
5866
+ var isObj = (x) => !!x && typeof x === "object" && !Array.isArray(x);
5867
+ var REF_FIELDS = ["from", "under", "owner", "mappedTo", "entry"];
5868
+ function repairFormSlips(facts, bad, onFile = []) {
5869
+ const slips = [];
5870
+ const cands = bad.flatMap((b) => {
5871
+ if (b.issues.some((i) => i.code === "not-json" || i.code === "duplicate-id"))
5872
+ return [];
5873
+ let json2;
5874
+ try {
5875
+ json2 = JSON.parse(b.raw);
5876
+ } catch {
5877
+ return [];
5878
+ }
5879
+ return isObj(json2) ? [{ b, json: json2 }] : [];
5880
+ });
5881
+ const taken = /* @__PURE__ */ new Map();
5882
+ for (const f of facts)
5883
+ taken.set(f.id, (taken.get(f.id) ?? 0) + 1);
5884
+ for (const { json: json2 } of cands)
5885
+ if (isId(json2.id))
5886
+ taken.set(json2.id, (taken.get(json2.id) ?? 0) + 1);
5887
+ const wanted = /* @__PURE__ */ new Map();
5888
+ for (const { json: json2 } of cands)
5889
+ if (typeof json2.id === "string" && !isId(json2.id)) {
5890
+ const s = slugFactId(json2.id);
5891
+ if (isId(s))
5892
+ wanted.set(s, (wanted.get(s) ?? 0) + 1);
5893
+ }
5894
+ const known = new Set([...facts, ...onFile].map((f) => f.id));
5895
+ const renames = /* @__PURE__ */ new Map();
5896
+ const collided = /* @__PURE__ */ new Map();
5897
+ for (const { b, json: json2 } of cands) {
5898
+ if (typeof json2.id !== "string" || isId(json2.id))
5899
+ continue;
5900
+ const s = slugFactId(json2.id);
5901
+ if (!isId(s))
5902
+ continue;
5903
+ if (taken.has(s) || (wanted.get(s) ?? 0) > 1) {
5904
+ const message = `line ${b.line}: fact id ${json2.id} would slug to ${s}, which another fact already has; rename one of them`;
5905
+ slips.push({ line: b.line, code: "id-collision", fact: json2.id, message });
5906
+ collided.set(b, message);
5907
+ continue;
5908
+ }
5909
+ renames.set(json2.id, s);
5910
+ }
5911
+ const rewritten = cands.map(({ b, json: json2 }) => {
5912
+ const o = structuredClone(json2);
5913
+ const fixes = [];
5914
+ if (typeof o.id === "string" && renames.has(o.id)) {
5915
+ fixes.push({
5916
+ line: b.line,
5917
+ code: "id-slugged",
5918
+ fact: renames.get(o.id),
5919
+ message: `line ${b.line}: fact id ${o.id} slugged to ${renames.get(o.id)}, and every reference to it`
5920
+ });
5921
+ o.id = renames.get(o.id);
5922
+ }
5923
+ const ref = (v) => {
5924
+ if (typeof v !== "string")
5925
+ return void 0;
5926
+ if (renames.has(v))
5927
+ return renames.get(v);
5928
+ if (isId(v))
5929
+ return void 0;
5930
+ const s = slugFactId(v);
5931
+ return known.has(s) ? s : void 0;
5932
+ };
5933
+ const said = (field, from, to) => fixes.push({
5934
+ line: b.line,
5935
+ code: "ref-rewritten",
5936
+ fact: String(o.id),
5937
+ message: `line ${b.line}: ${String(o.id)}'s ${field} ${from} rewritten as ${to}`
5938
+ });
5939
+ for (const k of REF_FIELDS) {
5940
+ const to = ref(o[k]);
5941
+ if (to) {
5942
+ said(k, o[k], to);
5943
+ o[k] = to;
5944
+ }
5945
+ }
5946
+ if (Array.isArray(o.steps))
5947
+ o.steps.forEach((st, i) => {
5948
+ if (!isObj(st))
5949
+ return;
5950
+ const to = ref(st.ref);
5951
+ if (to) {
5952
+ said(`step ${i + 1} ref`, st.ref, to);
5953
+ st.ref = to;
5954
+ }
5955
+ });
5956
+ return { b, o, fixes };
5957
+ });
5958
+ const entries = /* @__PURE__ */ new Map();
5959
+ for (const f of [...onFile, ...facts])
5960
+ if (f.kind === "entry")
5961
+ entries.set(f.id, f);
5962
+ for (const { o } of rewritten)
5963
+ if (o.kind === "entry" && typeof o.id === "string" && isObj(o.cite))
5964
+ entries.set(o.id, o);
5965
+ for (const r of rewritten) {
5966
+ const { o, b } = r;
5967
+ if (o.kind !== "flow" || o.cite !== void 0 || typeof o.entry !== "string")
5968
+ continue;
5969
+ const e = entries.get(o.entry);
5970
+ if (!e || !isObj(e.cite))
5971
+ continue;
5972
+ o.cite = structuredClone(e.cite);
5973
+ const c = e.cite;
5974
+ r.fixes.push({
5975
+ line: b.line,
5976
+ code: "flow-cite-from-entry",
5977
+ fact: String(o.id),
5978
+ 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)})`
5979
+ });
5980
+ }
5981
+ const kept = [...facts];
5982
+ const seen = new Set(facts.map((f) => f.id));
5983
+ const fixedLines = /* @__PURE__ */ new Set();
5984
+ for (const { b, o, fixes } of rewritten) {
5985
+ if (!fixes.length || collided.has(b))
5986
+ continue;
5987
+ const p = Fact.safeParse(o);
5988
+ if (!p.success || seen.has(p.data.id))
5989
+ continue;
5990
+ seen.add(p.data.id);
5991
+ kept.push(p.data);
5992
+ fixedLines.add(b);
5993
+ slips.push(...fixes);
5994
+ }
5995
+ const still = bad.filter((b) => !fixedLines.has(b)).map((b) => collided.has(b) ? {
5996
+ ...b,
5997
+ issues: [
5998
+ ...b.issues,
5999
+ { line: b.line, code: "id-collision", message: collided.get(b) }
6000
+ ]
6001
+ } : b);
6002
+ slips.sort((x, y) => x.line - y.line);
6003
+ return { facts: kept, bad: still, slips };
6004
+ }
6005
+
6006
+ // packages/verify/dist/house-imports.js
6007
+ var CODE2 = /\.(java|kt|kts|scala|groovy|cs|vb|fs|ts|tsx|js|jsx|mjs|cjs|vue|svelte|py|go|rb|php|swift)$/i;
6008
+ var IMPORTS = [
6009
+ // Java, Kotlin, Scala, Groovy, Python's `import a.b`, Go's one-line import
6010
+ /^\s*import\s+(?:static\s+)?([A-Za-z_][\w.]*)/,
6011
+ // C#, F#
6012
+ /^\s*(?:global\s+)?using\s+(?:static\s+)?([A-Za-z_][\w.]*)\s*;/,
6013
+ /^\s*open\s+([A-Za-z_][\w.]*)\s*$/,
6014
+ // Python
6015
+ /^\s*from\s+([A-Za-z_][\w.]*)\s+import\b/,
6016
+ // JavaScript, TypeScript: import ... from "x", export ... from "x", import "x", require("x"), import("x")
6017
+ /\bfrom\s+["']([^"'\s]+)["']/,
6018
+ /^\s*import\s+["']([^"'\s]+)["']/,
6019
+ /\brequire\s*\(\s*["']([^"'\s]+)["']\s*\)/,
6020
+ /\bimport\s*\(\s*["']([^"'\s]+)["']\s*\)/
6021
+ ];
6022
+ var GO_IMPORT = /^\s*(?:import\s+)?(?:[\w.]+\s+)?"([\w.-]+(?:\/[\w.-]+)+)"\s*$/;
6023
+ function modulesOf(line, go) {
6024
+ const out = [];
6025
+ for (const re of go ? [...IMPORTS, GO_IMPORT] : IMPORTS) {
6026
+ const m = re.exec(line);
6027
+ if (m?.[1])
6028
+ out.push(m[1]);
6029
+ }
6030
+ return out;
6031
+ }
6032
+ var lower = (s) => s.toLowerCase();
6033
+ var under3 = (module, pkg) => module === pkg || module.startsWith(pkg) && (/[./]$/.test(pkg) || /[./]/.test(module[pkg.length] ?? ""));
6034
+ function packageOf(module, prefix) {
6035
+ let i = prefix.length;
6036
+ while (i < module.length && /[./]/.test(module[i]))
6037
+ i++;
6038
+ const rest = module.slice(i);
6039
+ const part = /^[^./]*/.exec(rest)?.[0] ?? "";
6040
+ return module.slice(0, i + part.length).replace(/[./]+$/, "");
6041
+ }
6042
+ function nameOf2(pkg) {
6043
+ const parts = lower(pkg).split(/[^a-z0-9]+/).filter(Boolean);
6044
+ if (parts.length > 2 && /^(com|org|net|io|dev|co|uk|de|fr|nl|ch)$/.test(parts[0]))
6045
+ parts.shift();
6046
+ return parts.join("-") || "house-framework";
6047
+ }
6048
+ function houseImportsUntaught(source, o) {
6049
+ if (!o.prefixes.length)
6050
+ return [];
6051
+ const rules = o.rules ?? DEFAULT_READ_RULES;
6052
+ const prefixes = o.prefixes.map(lower);
6053
+ const taught = o.taught.map(lower);
6054
+ const found = /* @__PURE__ */ new Map();
6055
+ 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();
6056
+ for (const p of paths) {
6057
+ const text = source.read(p);
6058
+ if (text === void 0)
6059
+ continue;
6060
+ const lines = text.split(/\r?\n/);
6061
+ const go = /\.go$/i.test(p);
6062
+ lines.forEach((line, i) => {
6063
+ if (!/\b(import|using|from|require|open)\b/.test(line) && !(go && /^\s*"/.test(line)))
6064
+ return;
6065
+ for (const m of modulesOf(line, go)) {
6066
+ const ml = lower(m);
6067
+ const prefix = prefixes.find((x) => ml.startsWith(x));
6068
+ if (!prefix)
6069
+ continue;
6070
+ if (taught.some((t) => under3(ml, t)))
6071
+ continue;
6072
+ const pkg = packageOf(m, prefix);
6073
+ const f = found.get(pkg) ?? { files: /* @__PURE__ */ new Set(), first: `${p}:${i + 1}` };
6074
+ f.files.add(p);
6075
+ found.set(pkg, f);
6076
+ }
6077
+ });
6078
+ }
6079
+ return [...found.entries()].map(([pkg, f]) => {
6080
+ const name = nameOf2(pkg);
6081
+ return {
6082
+ package: pkg,
6083
+ name,
6084
+ files: f.files.size,
6085
+ first: f.first,
6086
+ command: `atticus frameworks learn ${name} --from <its source, JAR or npm package> --package ${pkg}`
6087
+ };
6088
+ }).sort((a, b) => b.files - a.files || (a.package < b.package ? -1 : a.package > b.package ? 1 : 0));
6089
+ }
6090
+
4457
6091
  export {
4458
6092
  insideRepo,
4459
6093
  dirSource,
4460
6094
  correctionFor,
6095
+ outsidePairingFor,
4461
6096
  correctedFact,
4462
6097
  applyCorrections,
4463
6098
  frameworkFacts,
@@ -4465,20 +6100,28 @@ export {
4465
6100
  frameworkTopicsOfType,
4466
6101
  frameworkGaps,
4467
6102
  frameworksToLearn,
6103
+ readRulesOf,
6104
+ lineRangeOf,
4468
6105
  parseReviewed,
4469
- sameCommit,
4470
- staleMessage,
4471
- verifyFacts,
6106
+ readingRank,
4472
6107
  FILE_KINDS,
6108
+ fileKinds,
4473
6109
  kindsOf,
4474
6110
  numberedSlice,
4475
6111
  sliceText,
4476
6112
  planBatches,
6113
+ isEntryPointFile,
6114
+ NOT_REACHED_WHY,
6115
+ reachableModels,
6116
+ sameCommit,
6117
+ staleMessage,
6118
+ verifyFacts,
4477
6119
  SHOWN_PROPOSALS,
4478
6120
  proposeExclusions,
4479
6121
  exclusionYaml,
4480
- SESSION_TOKENS,
6122
+ sessionTokensFor,
4481
6123
  planParts,
6124
+ planGaps,
4482
6125
  VIEW_NAMES,
4483
6126
  ViewError,
4484
6127
  changeView,
@@ -4496,5 +6139,7 @@ export {
4496
6139
  codeLinkerFor,
4497
6140
  renderMap,
4498
6141
  rereadPlan,
4499
- onlyFiles
6142
+ onlyFiles,
6143
+ repairFormSlips,
6144
+ houseImportsUntaught
4500
6145
  };