@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.
- package/dist/bin.js +8 -4
- package/dist/chunk-23XVSEV5.js +49 -0
- package/dist/{chunk-ZDPAS56H.js → chunk-24OXGB3P.js} +8 -2
- package/dist/{chunk-7EOBHUMC.js → chunk-24XLDU72.js} +4 -3
- package/dist/{chunk-CR3MTH32.js → chunk-2F4CYZKJ.js} +86 -11
- package/dist/{chunk-6IU67AEK.js → chunk-2GF7OLF3.js} +23 -15
- package/dist/chunk-2IIMFOPM.js +41 -0
- package/dist/chunk-2LGRMVCA.js +617 -0
- package/dist/{chunk-XPCM366L.js → chunk-2MMCFP5C.js} +10 -8
- package/dist/{chunk-JBTK5OP2.js → chunk-2Q56PQPS.js} +6 -4
- package/dist/{chunk-Z6LF7HQN.js → chunk-2Q5EDGEO.js} +7 -5
- package/dist/{chunk-3USYZGRF.js → chunk-2RLKFUUL.js} +38 -27
- package/dist/chunk-2SKB4O2E.js +59 -0
- package/dist/{chunk-SP2I63OO.js → chunk-34S2WGOB.js} +87 -4
- package/dist/{chunk-GQ4N4WKF.js → chunk-3NFFJL5D.js} +33 -27
- package/dist/{chunk-SJRGMG6A.js → chunk-3NWDLGG6.js} +28 -25
- package/dist/{chunk-4WBVUHXN.js → chunk-3RM4UK6W.js} +20 -18
- package/dist/chunk-42SIDKEU.js +583 -0
- package/dist/{chunk-GLUX4EEP.js → chunk-4EURQWQD.js} +20 -15
- package/dist/chunk-4U276ICE.js +39 -0
- package/dist/{chunk-ZMFPW66A.js → chunk-53T4O55J.js} +4 -3
- package/dist/{chunk-6J5OKA4Y.js → chunk-57TDNMI3.js} +17 -15
- package/dist/chunk-5BD2KYLE.js +57 -0
- package/dist/{chunk-7EFILTCI.js → chunk-5F7GFN2F.js} +28 -0
- package/dist/chunk-5NMKONLP.js +95 -0
- package/dist/{chunk-7ZUIMTOU.js → chunk-5SJZW52K.js} +26 -24
- package/dist/{chunk-4RS5KZQD.js → chunk-63YRMR6J.js} +6 -5
- package/dist/chunk-66PPNOON.js +62 -0
- package/dist/chunk-6H3Z5JHH.js +64 -0
- package/dist/{chunk-CEXSU4S6.js → chunk-6MUGR4LS.js} +775 -89
- package/dist/{chunk-QSXK2RGH.js → chunk-6OJQOPUN.js} +5 -3
- package/dist/{chunk-MVZVGYXH.js → chunk-6PPOWY5E.js} +84 -14
- package/dist/{chunk-7EKXJZ2Y.js → chunk-6ZGLAACW.js} +299 -38
- package/dist/chunk-6ZJ5A7AF.js +222 -0
- package/dist/{chunk-L24C55VN.js → chunk-745DAZ4L.js} +142 -30
- package/dist/{chunk-Q4D5X7QB.js → chunk-7ASNL4Q4.js} +46 -24
- package/dist/chunk-7BR7IPOH.js +35 -0
- package/dist/{chunk-7WD63HU2.js → chunk-7DOR27HL.js} +6 -6
- package/dist/chunk-7FTTA3HD.js +341 -0
- package/dist/{chunk-OLQJTJJU.js → chunk-7GJS6DNX.js} +12 -3
- package/dist/{chunk-7JUKDRSZ.js → chunk-7IGKHZ4Z.js} +347 -226
- package/dist/{chunk-G2B4ZQGH.js → chunk-7IOWA5D5.js} +11 -6
- package/dist/{chunk-EBEW3XXQ.js → chunk-7JITZFJC.js} +7 -5
- package/dist/chunk-7MJSQTL5.js +174 -0
- package/dist/{chunk-I4JRCD6M.js → chunk-7UOMVMFG.js} +25 -17
- package/dist/{chunk-YY6ZQQBP.js → chunk-AFWUDXEM.js} +19 -7
- package/dist/chunk-AFZ3QA6W.js +47 -0
- package/dist/chunk-AJQ72EBM.js +420 -0
- package/dist/chunk-AJYXNTNZ.js +317 -0
- package/dist/chunk-AVTBS4OW.js +49 -0
- package/dist/{chunk-FHH4I4ZV.js → chunk-BDKC6H5P.js} +12 -6
- package/dist/{chunk-IPKHE5D6.js → chunk-BEVTIBRH.js} +1526 -875
- package/dist/chunk-BHT7QK4G.js +162 -0
- package/dist/{chunk-6JUBZBMW.js → chunk-BIGDQDHD.js} +6 -4
- package/dist/{chunk-2BFCZ3RK.js → chunk-BO4KX37X.js} +20 -18
- package/dist/chunk-BPK5P77O.js +115 -0
- package/dist/chunk-BQ4SQQC6.js +59 -0
- package/dist/chunk-BQGDB6ZL.js +7342 -0
- package/dist/{chunk-2ANOROX3.js → chunk-BTGPE6CR.js} +11 -9
- package/dist/chunk-BTY3HZBO.js +93 -0
- package/dist/{chunk-DPZHZQKE.js → chunk-BXYMYEJ4.js} +44 -10
- package/dist/{chunk-2RMYG36Q.js → chunk-C3RY6KU6.js} +309 -346
- package/dist/chunk-C4R4JLHE.js +40 -0
- package/dist/chunk-CADWLE2T.js +18 -0
- package/dist/chunk-CBGGOQWU.js +82 -0
- package/dist/chunk-CMDXGQTS.js +47 -0
- package/dist/{chunk-MC3H7736.js → chunk-CQDGOPGH.js} +58 -18
- package/dist/{chunk-HFQCFSJR.js → chunk-CX4RZ2KS.js} +4 -3
- package/dist/{chunk-PAMPPUR6.js → chunk-CXJ6FUKN.js} +1852 -207
- package/dist/{chunk-ABWHYWNI.js → chunk-D73UEXYW.js} +3 -3
- package/dist/chunk-DCZWQLYT.js +1205 -0
- package/dist/chunk-DLOBCJL6.js +48 -0
- package/dist/{chunk-4BN53TG3.js → chunk-DOBQKHIF.js} +31 -23
- package/dist/chunk-E3ISYXO3.js +423 -0
- package/dist/{chunk-7SW2QWLU.js → chunk-E6ZIEVOI.js} +16 -13
- package/dist/chunk-EBL2R3YG.js +43 -0
- package/dist/{chunk-AIOQPLA5.js → chunk-EGQRREQH.js} +41 -15
- package/dist/chunk-EHA7RMZ3.js +58 -0
- package/dist/{chunk-M766ZJ6W.js → chunk-EIPTLPFC.js} +38 -27
- package/dist/chunk-EMRIQUND.js +517 -0
- package/dist/{chunk-DFBCSHVT.js → chunk-EPCJQQWD.js} +6 -4
- package/dist/{chunk-RZPYKDWS.js → chunk-F3P7N7SF.js} +1 -1
- package/dist/{chunk-R77KLRB3.js → chunk-FFLNS6VP.js} +21 -18
- package/dist/chunk-FFTRTL6R.js +26 -0
- package/dist/{chunk-IHMLFRVV.js → chunk-FPJPLKDW.js} +62 -16
- package/dist/{chunk-JCZIKA4C.js → chunk-FSMSFDLD.js} +5 -4
- package/dist/{chunk-AVJDSJ2F.js → chunk-FUJM6RTF.js} +8 -7
- package/dist/chunk-FZOJFH7Q.js +132 -0
- package/dist/{chunk-QRAOANID.js → chunk-G6X77UJZ.js} +23 -21
- package/dist/{chunk-3ALD2W2A.js → chunk-GCWRWSU7.js} +10 -4
- package/dist/chunk-GIW4ZVDB.js +39 -0
- package/dist/{chunk-B2WLAWNJ.js → chunk-GUX3PTYM.js} +3 -3
- package/dist/{chunk-DPUYDDQG.js → chunk-GZY6RUZ6.js} +25 -3
- package/dist/chunk-H4VO6WG3.js +10 -0
- package/dist/{chunk-MMOSZPWJ.js → chunk-H5HBGNXU.js} +5 -4
- package/dist/chunk-H7KM7LZE.js +229 -0
- package/dist/{chunk-AA6YULYB.js → chunk-HNM2YQT2.js} +7 -7
- package/dist/{chunk-3C7EYRFZ.js → chunk-HYU5VLGA.js} +1 -1
- package/dist/{chunk-XY3P7JW7.js → chunk-IDKCWSM5.js} +1032 -172
- package/dist/chunk-IFKEI4SA.js +237 -0
- package/dist/{chunk-F7HYOUFF.js → chunk-IGOE6KPG.js} +67 -22
- package/dist/{chunk-TDD55CJH.js → chunk-IH2XURVQ.js} +6 -5
- package/dist/chunk-INQHESFB.js +38 -0
- package/dist/chunk-IP74LCOQ.js +57 -0
- package/dist/chunk-IPHHDDC6.js +45 -0
- package/dist/{chunk-T6AIP7H5.js → chunk-ISUAAMGS.js} +110 -153
- package/dist/{chunk-A7UPUQFY.js → chunk-IUPG3UKI.js} +624 -319
- package/dist/{chunk-L6SGQIR3.js → chunk-IVBOQAGB.js} +7 -3
- package/dist/{chunk-27NKUS3X.js → chunk-J2JAKCIW.js} +2205 -796
- package/dist/{chunk-V2YTAIQO.js → chunk-J3IOLXWM.js} +24 -7
- package/dist/chunk-JA5FI3QV.js +142 -0
- package/dist/chunk-JAVI7JSP.js +49 -0
- package/dist/{chunk-HLJAXMM4.js → chunk-JB5LINEM.js} +6 -2
- package/dist/{chunk-2VHF7OXM.js → chunk-JENSEMXF.js} +1 -1
- package/dist/{chunk-6BVONWJ7.js → chunk-JJF6HALY.js} +4 -3
- package/dist/chunk-JLUCKAEL.js +38 -0
- package/dist/chunk-JRBFANFE.js +221 -0
- package/dist/{chunk-R6XF6UR3.js → chunk-KDNTAPLX.js} +4 -4
- package/dist/chunk-KFAVQZSX.js +33 -0
- package/dist/chunk-KJXD4PNL.js +50 -0
- package/dist/chunk-KN5BXMWN.js +44 -0
- package/dist/{chunk-PCVDV3UZ.js → chunk-KQGURXFU.js} +9 -7
- package/dist/{chunk-WY6PP6AA.js → chunk-KXITGYLF.js} +40 -6
- package/dist/{chunk-66YVAGGM.js → chunk-L27SV425.js} +22 -20
- package/dist/{chunk-NE2W3OO7.js → chunk-LDLL7LB3.js} +24 -22
- package/dist/chunk-LFGTYX5U.js +379 -0
- package/dist/{chunk-EYWIDJFU.js → chunk-LGBMTVNT.js} +7 -5
- package/dist/{chunk-Q5AMFEWD.js → chunk-LGFL54FP.js} +3 -2
- package/dist/{chunk-6TACQN3N.js → chunk-LJXMYKZJ.js} +7 -5
- package/dist/{chunk-BKVWTZ6A.js → chunk-LKOR5UET.js} +29 -26
- package/dist/chunk-LNBQOP7D.js +294 -0
- package/dist/{chunk-O3WI5KWW.js → chunk-LO6P2VYG.js} +2 -2
- package/dist/chunk-LQJPN536.js +122 -0
- package/dist/{chunk-YEDDOYSC.js → chunk-LYBNHMVZ.js} +29 -5
- package/dist/{chunk-A3GY6SOA.js → chunk-LZ7NUC62.js} +20 -18
- package/dist/chunk-M32BLWDH.js +37 -0
- package/dist/{chunk-VYDBJ73M.js → chunk-M3NANSDZ.js} +17 -15
- package/dist/chunk-MFHL5URL.js +31 -0
- package/dist/chunk-MFR5OPBD.js +109 -0
- package/dist/{chunk-DE76JROX.js → chunk-MG4ZL5RW.js} +32 -20
- package/dist/{chunk-N7IXEHK3.js → chunk-MO6P2JZH.js} +10 -8
- package/dist/{chunk-65NYPGRC.js → chunk-MUWDB4R4.js} +1 -1
- package/dist/chunk-MZCGSXVR.js +34 -0
- package/dist/{chunk-MGYZM27B.js → chunk-N3XIYPU3.js} +51 -23
- package/dist/chunk-NDCF4R6S.js +172 -0
- package/dist/{chunk-DHPACDG7.js → chunk-NR2UJWOC.js} +10 -8
- package/dist/chunk-NXJ5URP6.js +51 -0
- package/dist/{chunk-UNM6ZOKP.js → chunk-NY2XSR5K.js} +147 -18
- package/dist/{chunk-5GDRCDQB.js → chunk-O45HFTFS.js} +3 -2
- package/dist/{chunk-ONLS5QA5.js → chunk-O4GXGGBX.js} +14 -12
- package/dist/chunk-O5PXQR67.js +249 -0
- package/dist/{chunk-PRPPGYZG.js → chunk-O66DM6DJ.js} +6 -4
- package/dist/{chunk-KEM4SDDP.js → chunk-O7FDLW7L.js} +11 -8
- package/dist/{chunk-T2UYZSCQ.js → chunk-O7XLYEFA.js} +169 -29
- package/dist/{chunk-FITWGOJ2.js → chunk-OHNCWBKF.js} +4 -4
- package/dist/{chunk-652QWTLZ.js → chunk-OHVTTOPS.js} +3 -3
- package/dist/chunk-OMI6FPPN.js +43 -0
- package/dist/chunk-ONUPY3H6.js +53 -0
- package/dist/chunk-OTLDSZDV.js +69 -0
- package/dist/chunk-OUBGGKDU.js +22 -0
- package/dist/chunk-OWNCM4ZN.js +330 -0
- package/dist/{chunk-YISXLPHN.js → chunk-P2V7QW4D.js} +235 -35
- package/dist/{chunk-CNUKCS2T.js → chunk-P32RUW3B.js} +1632 -93
- package/dist/{chunk-BWZ77RQT.js → chunk-PAQ2AS5E.js} +1 -1
- package/dist/{chunk-ZMAINWBW.js → chunk-PBPBMP2B.js} +4 -3
- package/dist/{chunk-ZVM6AKKM.js → chunk-PJSJPMJI.js} +3 -3
- package/dist/{chunk-EFHQ3TWP.js → chunk-PKBHS4AA.js} +233 -56
- package/dist/{chunk-GT4I4ZWM.js → chunk-PMUAJGLI.js} +20 -16
- package/dist/chunk-PPD7HTDP.js +152 -0
- package/dist/chunk-PSOFBQWB.js +42 -0
- package/dist/{chunk-S2A7YC3Q.js → chunk-Q2TECDIT.js} +10694 -8896
- package/dist/chunk-Q4K5WB2W.js +42 -0
- package/dist/{chunk-UGLN7QHV.js → chunk-Q6DFNVRJ.js} +14 -12
- package/dist/chunk-QA4O2GGC.js +265 -0
- package/dist/chunk-QAXW2N3B.js +1216 -0
- package/dist/chunk-QBNWF5ZU.js +19 -0
- package/dist/chunk-QFWA2DPH.js +224 -0
- package/dist/{chunk-23W7VWKR.js → chunk-QKUJMING.js} +1363 -114
- package/dist/{chunk-NXFPS2LR.js → chunk-R44CYFPV.js} +16 -13
- package/dist/{chunk-CKSITVS4.js → chunk-R7NYHMUU.js} +2687 -786
- package/dist/chunk-REWWDVQB.js +162 -0
- package/dist/{chunk-JMSCMUCA.js → chunk-RGERPOIV.js} +145 -21
- package/dist/{chunk-6JCPI55B.js → chunk-RLDGWLQ7.js} +49 -7
- package/dist/chunk-RUGXBPWI.js +384 -0
- package/dist/{chunk-CPA6HPVR.js → chunk-RXPJMEOW.js} +18 -12
- package/dist/{chunk-3HI6Z55Y.js → chunk-S4PMJU6H.js} +339 -43
- package/dist/chunk-S5LSDXV4.js +40 -0
- package/dist/chunk-S7FCV7S5.js +51 -0
- package/dist/{chunk-5GGOLAKC.js → chunk-SDKLKB6I.js} +28 -26
- package/dist/chunk-SFSPGFCV.js +62 -0
- package/dist/{chunk-ZNCEKR4J.js → chunk-SFUP7TTO.js} +12 -8
- package/dist/{chunk-64EQYWCA.js → chunk-SLXZOMA6.js} +69 -19
- package/dist/chunk-SQV37XO2.js +273 -0
- package/dist/{chunk-K65BJIMT.js → chunk-SU74NDMQ.js} +19 -17
- package/dist/chunk-SYV5LHML.js +165 -0
- package/dist/chunk-T4YPZIQN.js +36 -0
- package/dist/chunk-T6DQR5TV.js +323 -0
- package/dist/{chunk-JIITQXFB.js → chunk-THDMSZ3R.js} +3075 -802
- package/dist/{chunk-JGZCG5VM.js → chunk-TNR75OXT.js} +7 -7
- package/dist/{chunk-D3PO4DOZ.js → chunk-TUULXQRW.js} +174 -15
- package/dist/chunk-TYQBFTBR.js +516 -0
- package/dist/chunk-U76OWS3Q.js +49 -0
- package/dist/{chunk-YYQN46FY.js → chunk-UCROWG2D.js} +27 -24
- package/dist/{chunk-6M5NK3K3.js → chunk-UF4AZOZB.js} +50 -10
- package/dist/chunk-UF4GLFBX.js +345 -0
- package/dist/{chunk-OT2O76OS.js → chunk-UGUP3ZM6.js} +240 -39
- package/dist/chunk-UNAGJYET.js +45 -0
- package/dist/{chunk-5WG5YP4T.js → chunk-UOR2DTHH.js} +16 -14
- package/dist/{chunk-S3QNRWYR.js → chunk-UPOY4YSQ.js} +14 -12
- package/dist/{chunk-EPTBZ3GP.js → chunk-UPS2DGFC.js} +1 -1
- package/dist/{chunk-4AK7IBVP.js → chunk-UUU4CLJG.js} +4 -2
- package/dist/{chunk-W3BQAZ3Q.js → chunk-UWMJKX5U.js} +14 -12
- package/dist/{chunk-QZSX3F7C.js → chunk-UWOJLHFT.js} +27 -23
- package/dist/chunk-VBTP56CE.js +535 -0
- package/dist/chunk-VCTWPLAO.js +211 -0
- package/dist/chunk-VNU26RCO.js +1215 -0
- package/dist/{chunk-CSZ4C5N6.js → chunk-VTZMAJ3W.js} +93 -10
- package/dist/{chunk-TXLH6WHQ.js → chunk-VW62D6QE.js} +14 -12
- package/dist/{chunk-CAQZUNOH.js → chunk-VY72R2XZ.js} +610 -3
- package/dist/{chunk-2DIQQJCB.js → chunk-VZALIKIF.js} +18 -15
- package/dist/{chunk-TYAID7B7.js → chunk-W62H2XSO.js} +25 -23
- package/dist/{chunk-HVUPKCGJ.js → chunk-W7VWI2YH.js} +9 -6
- package/dist/{chunk-TANFV3MZ.js → chunk-WERM6KPJ.js} +7 -4
- package/dist/{chunk-BOHJZTEQ.js → chunk-WF7B67YE.js} +208 -87
- package/dist/{chunk-7FBGJFEE.js → chunk-WG5U3F5V.js} +14 -12
- package/dist/{chunk-4NDAAFQT.js → chunk-WJF2CS6M.js} +8 -5
- package/dist/{chunk-XAKWMCXI.js → chunk-WKUJ3KPV.js} +4 -4
- package/dist/chunk-WMTRHUI5.js +103 -0
- package/dist/chunk-WNKPRSGI.js +36 -0
- package/dist/chunk-WXFLMQSR.js +223 -0
- package/dist/{chunk-RRTKKSLQ.js → chunk-X3WY6MN3.js} +8 -6
- package/dist/{chunk-ZCJDOK2S.js → chunk-X4CXA2IL.js} +47 -23
- package/dist/{chunk-UBBC2XSG.js → chunk-X5F3Q5LV.js} +560 -105
- package/dist/{chunk-C6EKFH7J.js → chunk-XD75VZIH.js} +9 -7
- package/dist/chunk-XERYFDB5.js +297 -0
- package/dist/chunk-XI2LCCYQ.js +420 -0
- package/dist/chunk-Y2EQNWKV.js +933 -0
- package/dist/{chunk-LR3R6Z2A.js → chunk-YHCMZC2J.js} +7 -7
- package/dist/chunk-YYY3AZFE.js +122 -0
- package/dist/{chunk-GDLULHTC.js → chunk-Z5IA3WDR.js} +19 -31
- package/dist/{chunk-T6XQL73F.js → chunk-ZFFYNRIO.js} +63 -23
- package/dist/{chunk-EDS2L2LD.js → chunk-ZFL7NY4F.js} +56 -27
- package/dist/{chunk-YMZ3TNYB.js → chunk-ZGULQAPZ.js} +7 -4
- package/dist/{chunk-4SKZDI62.js → chunk-ZIUFM5HT.js} +3 -1
- package/dist/{chunk-DKJA76CJ.js → chunk-ZJFRMSJT.js} +15 -13
- package/dist/chunk-ZJXLNDH4.js +239 -0
- package/dist/chunk-ZQ3ON2VQ.js +252 -0
- package/dist/{chunk-HITVBIJS.js → chunk-ZQ7SKESZ.js} +150 -415
- package/dist/{chunk-6QSERWXH.js → chunk-ZQXZ2SES.js} +24 -21
- package/dist/chunk-ZX57KSEQ.js +39 -0
- package/dist/{chunk-I3E7CX4H.js → chunk-ZZK56BLP.js} +5 -3
- package/dist/files.json +664 -368
- package/dist/packages/naysayer/constitution/constitution.yaml +19 -0
- package/dist/packages/sdk/npm/index.js +6194 -2542
- package/dist/packages/ui/dist/assets/{elk-api-tma0MCuh.js → elk-api-CIf-0gz5.js} +2 -2
- package/dist/packages/ui/dist/assets/index-CoK_Dl0D.js +47 -0
- package/dist/packages/ui/dist/assets/index-Dw20IRhQ.css +1 -0
- package/dist/packages/ui/dist/index.html +3 -2
- package/package.json +5 -5
- package/dist/chunk-2BF2EYJV.js +0 -174
- package/dist/chunk-42EVSKME.js +0 -37
- package/dist/chunk-4BKGMUIK.js +0 -53
- package/dist/chunk-5KLGBXG5.js +0 -48
- package/dist/chunk-6BNC6EQ4.js +0 -41
- package/dist/chunk-6D2CQ5II.js +0 -41
- package/dist/chunk-7AVHLZGD.js +0 -45
- package/dist/chunk-D46WUV3T.js +0 -33
- package/dist/chunk-E36QOJMD.js +0 -47
- package/dist/chunk-FQXYPF7E.js +0 -147
- package/dist/chunk-GBRFVT63.js +0 -45
- package/dist/chunk-ICY36XCH.js +0 -34
- package/dist/chunk-J4K3KVLD.js +0 -131
- package/dist/chunk-JZU3H6UQ.js +0 -54
- package/dist/chunk-NN2ZQEB4.js +0 -38
- package/dist/chunk-PCOCACY3.js +0 -385
- package/dist/chunk-RTEYOSJY.js +0 -29
- package/dist/chunk-RUVFXE2L.js +0 -666
- package/dist/chunk-RVH5SUW5.js +0 -69
- package/dist/chunk-VN3DP4LQ.js +0 -43
- package/dist/chunk-W5BVQF44.js +0 -36
- package/dist/chunk-WVPJSRRJ.js +0 -52
- package/dist/chunk-XPTOBS2D.js +0 -48
- package/dist/packages/ui/dist/assets/index-B54fm9FQ.js +0 -29
- package/dist/packages/ui/dist/assets/index-DD_hufQz.css +0 -1
|
@@ -7,18 +7,19 @@ import {
|
|
|
7
7
|
featuresForScan,
|
|
8
8
|
namingHint,
|
|
9
9
|
syncSources
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-745DAZ4L.js";
|
|
11
11
|
import {
|
|
12
12
|
dirSource,
|
|
13
|
+
rereadPlan,
|
|
13
14
|
sameCommit,
|
|
14
15
|
verifyFacts
|
|
15
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-CXJ6FUKN.js";
|
|
16
17
|
import {
|
|
17
18
|
scanInputs
|
|
18
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-QKUJMING.js";
|
|
19
20
|
import {
|
|
20
21
|
listIngestors
|
|
21
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-6ZGLAACW.js";
|
|
22
23
|
import {
|
|
23
24
|
BUILT_IN_EXTRACTORS,
|
|
24
25
|
MODEL_READERS,
|
|
@@ -33,12 +34,7 @@ import {
|
|
|
33
34
|
runDialectTests,
|
|
34
35
|
runFrameworkTests,
|
|
35
36
|
unmodelledFrameworks
|
|
36
|
-
} from "./chunk-
|
|
37
|
-
import {
|
|
38
|
-
baseClonePath,
|
|
39
|
-
changesSince,
|
|
40
|
-
checkoutState
|
|
41
|
-
} from "./chunk-EFHQ3TWP.js";
|
|
37
|
+
} from "./chunk-CQDGOPGH.js";
|
|
42
38
|
import {
|
|
43
39
|
DEFAULT_IGNORES,
|
|
44
40
|
configHash,
|
|
@@ -50,509 +46,321 @@ import {
|
|
|
50
46
|
sandboxedExtractor,
|
|
51
47
|
scanRepo,
|
|
52
48
|
withParseCache
|
|
53
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-3NFFJL5D.js";
|
|
50
|
+
import {
|
|
51
|
+
baseClonePath,
|
|
52
|
+
changesSince,
|
|
53
|
+
checkoutState,
|
|
54
|
+
inPlaceDir
|
|
55
|
+
} from "./chunk-PKBHS4AA.js";
|
|
54
56
|
import {
|
|
55
57
|
buildContextPack,
|
|
56
58
|
toRunSummary
|
|
57
|
-
} from "./chunk-
|
|
59
|
+
} from "./chunk-P32RUW3B.js";
|
|
58
60
|
import {
|
|
59
61
|
AgentDefinition,
|
|
60
62
|
DECLARED_CALLER,
|
|
61
63
|
DEFAULT_SPEC_TEMPLATE,
|
|
62
64
|
HOSTS_NONE,
|
|
65
|
+
HOSTS_OUTSIDE_INTERNAL,
|
|
66
|
+
HOSTS_OUTSIDE_VENDOR,
|
|
63
67
|
IsoDateTime,
|
|
64
68
|
NEVER_CHECKED_STEPS,
|
|
69
|
+
NO_TARGET,
|
|
65
70
|
PluginExpected,
|
|
66
71
|
REPO_TYPE_SCHEMA_VERSION,
|
|
67
72
|
RecordId,
|
|
68
73
|
SCAN_SETTINGS_SCHEMA_VERSION,
|
|
74
|
+
TARGETS_ANOTHER_LABEL,
|
|
75
|
+
TARGETS_SPLIT_LABEL,
|
|
76
|
+
TARGETS_STEP_ID,
|
|
77
|
+
TOPICS_OUTSIDE_LABEL,
|
|
78
|
+
TOPICS_STEP_ID,
|
|
69
79
|
applyCurated,
|
|
80
|
+
applyLinkEvidence,
|
|
81
|
+
applySharedMaps,
|
|
82
|
+
callHostOf,
|
|
70
83
|
callTargetQuestion,
|
|
71
84
|
callerRecords,
|
|
72
85
|
catalogDirOf,
|
|
86
|
+
cmpNatural,
|
|
87
|
+
cmpText,
|
|
88
|
+
conventionsOf,
|
|
73
89
|
declaredCallsOf,
|
|
74
90
|
deserializeRecord,
|
|
91
|
+
effectiveComponentTypes,
|
|
75
92
|
external_exports,
|
|
76
93
|
factIdPart,
|
|
77
94
|
factsDirOf,
|
|
78
95
|
factsRelOf,
|
|
79
96
|
fieldKey,
|
|
80
97
|
flowFiles,
|
|
98
|
+
helperOn,
|
|
81
99
|
hostMatches,
|
|
82
100
|
hostQuestionText,
|
|
83
101
|
identifierWords,
|
|
102
|
+
isAiBy,
|
|
103
|
+
isGuessedJoin,
|
|
104
|
+
isPathMatch,
|
|
84
105
|
isPrototypeType,
|
|
85
106
|
joinCallers,
|
|
107
|
+
joinConventions,
|
|
86
108
|
joinFacts,
|
|
109
|
+
joinFlagsOf,
|
|
110
|
+
joinGuessesOf,
|
|
87
111
|
joinRecords,
|
|
112
|
+
joinSharedMaps,
|
|
113
|
+
joinTerms,
|
|
88
114
|
lineNamesField,
|
|
89
115
|
lineNamesRoute,
|
|
90
116
|
lineNamesWord,
|
|
117
|
+
linkEvidenceOf,
|
|
91
118
|
listQuestions,
|
|
92
119
|
loadOrgPack,
|
|
93
120
|
mergeCatalog,
|
|
94
121
|
normalizePath,
|
|
95
122
|
orgPackDirOf,
|
|
123
|
+
outsideSystemOf,
|
|
124
|
+
pairsFromCorrections,
|
|
125
|
+
parseCorrections,
|
|
96
126
|
parseFacts,
|
|
97
127
|
parseRecord,
|
|
98
128
|
projectFacts,
|
|
99
129
|
questionId,
|
|
100
130
|
readParsedFiles,
|
|
101
131
|
recordPath,
|
|
102
|
-
require_dist,
|
|
103
132
|
serializeRecord,
|
|
133
|
+
sharedJoinAbout,
|
|
134
|
+
singularNoun,
|
|
104
135
|
slugify,
|
|
105
136
|
specFileHash,
|
|
106
137
|
specFindingFingerprint,
|
|
138
|
+
stackHelpersOff,
|
|
139
|
+
stripLinkEvidence,
|
|
140
|
+
stripSharedMaps,
|
|
141
|
+
termGrantRecords,
|
|
107
142
|
throwIfCancelled,
|
|
108
143
|
toCanonicalYaml,
|
|
144
|
+
topicOfQuestion,
|
|
145
|
+
topicQuestionText,
|
|
109
146
|
urlHostPort
|
|
110
|
-
} from "./chunk-
|
|
147
|
+
} from "./chunk-Q2TECDIT.js";
|
|
111
148
|
import {
|
|
112
149
|
SCHEMA_VERSION,
|
|
113
150
|
writeFileAtomic
|
|
114
|
-
} from "./chunk-
|
|
151
|
+
} from "./chunk-TUULXQRW.js";
|
|
152
|
+
import {
|
|
153
|
+
require_dist
|
|
154
|
+
} from "./chunk-BQGDB6ZL.js";
|
|
115
155
|
import {
|
|
116
156
|
__toESM
|
|
117
157
|
} from "./chunk-TV45EUGM.js";
|
|
118
158
|
|
|
119
|
-
// packages/
|
|
120
|
-
|
|
121
|
-
import {
|
|
122
|
-
import {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
var
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
159
|
+
// packages/scanner/dist/scan.js
|
|
160
|
+
import { stat } from "node:fs/promises";
|
|
161
|
+
import { dirname as dirname3, isAbsolute, join as join9, resolve as resolve2 } from "node:path";
|
|
162
|
+
import { fileURLToPath } from "node:url";
|
|
163
|
+
|
|
164
|
+
// packages/glossary/dist/words.js
|
|
165
|
+
var STOP_TOKENS = /* @__PURE__ */ new Set([
|
|
166
|
+
"a",
|
|
167
|
+
"an",
|
|
168
|
+
"and",
|
|
169
|
+
"api",
|
|
170
|
+
"at",
|
|
171
|
+
"by",
|
|
172
|
+
"create",
|
|
173
|
+
"created",
|
|
174
|
+
"delete",
|
|
175
|
+
"for",
|
|
176
|
+
"get",
|
|
177
|
+
"id",
|
|
178
|
+
"ids",
|
|
179
|
+
"in",
|
|
180
|
+
"is",
|
|
181
|
+
"list",
|
|
182
|
+
"of",
|
|
183
|
+
"on",
|
|
184
|
+
"or",
|
|
185
|
+
"patch",
|
|
186
|
+
"post",
|
|
187
|
+
"put",
|
|
188
|
+
"the",
|
|
189
|
+
"to",
|
|
190
|
+
"ts",
|
|
191
|
+
"update",
|
|
192
|
+
"updated",
|
|
193
|
+
"uuid"
|
|
194
|
+
]);
|
|
195
|
+
var QUALIFIERS = /* @__PURE__ */ new Set([
|
|
196
|
+
"cur",
|
|
197
|
+
"curr",
|
|
198
|
+
"current",
|
|
199
|
+
"has",
|
|
200
|
+
"max",
|
|
201
|
+
"min",
|
|
202
|
+
"new",
|
|
203
|
+
"old",
|
|
204
|
+
"prev",
|
|
205
|
+
"previous"
|
|
206
|
+
]);
|
|
207
|
+
var splitWords = (s) => identifierWords(s);
|
|
208
|
+
var singular = (w) => singularNoun(w);
|
|
209
|
+
var isNoise = (w) => STOP_TOKENS.has(w) || /^v\d+$/.test(w) || /^\d+$/.test(w);
|
|
210
|
+
function businessWords(s) {
|
|
211
|
+
return splitWords(s).filter((w) => !isNoise(w)).map(singular);
|
|
148
212
|
}
|
|
149
|
-
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
return
|
|
157
|
-
|
|
158
|
-
for (const f of files) {
|
|
159
|
-
const file = `glossaryterm/${f}`;
|
|
160
|
-
let data;
|
|
161
|
-
try {
|
|
162
|
-
data = (0, import_yaml.parse)(await readFile(join(dir, file), "utf8"));
|
|
163
|
-
} catch (e) {
|
|
164
|
-
problems.push({ file, message: `invalid YAML: ${e.message.split("\n")[0]}` });
|
|
165
|
-
continue;
|
|
166
|
-
}
|
|
167
|
-
const p = DefinitionProposalFile.safeParse(data);
|
|
168
|
-
if (!p.success) {
|
|
169
|
-
problems.push({
|
|
170
|
-
file,
|
|
171
|
-
message: p.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")
|
|
172
|
-
});
|
|
173
|
-
continue;
|
|
174
|
-
}
|
|
175
|
-
const { schemaVersion: _v, ...rest } = p.data;
|
|
176
|
-
void _v;
|
|
177
|
-
proposals.push(rest);
|
|
178
|
-
}
|
|
179
|
-
return { proposals, problems };
|
|
213
|
+
function compoundOf(name) {
|
|
214
|
+
const w = splitWords(name).filter((x) => !/^\d+$/.test(x));
|
|
215
|
+
while (w.length && (isNoise(w[0]) || QUALIFIERS.has(w[0])))
|
|
216
|
+
w.shift();
|
|
217
|
+
while (w.length && isNoise(w.at(-1)))
|
|
218
|
+
w.pop();
|
|
219
|
+
if (w.length < 2 || w.some(isNoise))
|
|
220
|
+
return void 0;
|
|
221
|
+
return [...w.slice(0, -1), singular(w.at(-1))].join(" ");
|
|
180
222
|
}
|
|
181
|
-
function
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
return own;
|
|
185
|
-
return AgentDefinition.parse({
|
|
186
|
-
schemaVersion: 1,
|
|
187
|
-
id: GLOSSARY_DEFINER,
|
|
188
|
-
role: "business-analyst",
|
|
189
|
-
description: "Proposes a one-sentence business definition for each glossary term found only in code, from where the code uses it.",
|
|
190
|
-
inputs: ["glossary", "catalog-slice"],
|
|
191
|
-
// C5 has no output kind for catalog proposals (see ADR 0031); a definition seeds specs.
|
|
192
|
-
outputArtifact: "specs",
|
|
193
|
-
tokenBudget: orgPack?.runtime.budgets.defaultTokens ?? 15e4,
|
|
194
|
-
allowedRuntimes: ["hand-off", "model", "headless-claude-code", "mock", "replay"]
|
|
195
|
-
});
|
|
223
|
+
function singleOf(name) {
|
|
224
|
+
const w = businessWords(name).filter((x) => !QUALIFIERS.has(x));
|
|
225
|
+
return w.length === 1 ? w[0] : void 0;
|
|
196
226
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
- evidence: the record ids from that term's block your definition relies on.
|
|
203
|
-
|
|
204
|
-
Don't invent systems, rules or numbers. A proposal that cites no record id from its term is discarded. Write YAML and nothing else:
|
|
205
|
-
|
|
206
|
-
terms:
|
|
207
|
-
- id: <term id>
|
|
208
|
-
definition: <one sentence>
|
|
209
|
-
evidence: [<record id>, ...]`;
|
|
210
|
-
var Proposal = external_exports.object({
|
|
211
|
-
id: external_exports.string().min(1),
|
|
212
|
-
definition: external_exports.string().trim().min(3).max(400),
|
|
213
|
-
evidence: external_exports.array(external_exports.coerce.string()).default([])
|
|
214
|
-
});
|
|
215
|
-
function block(e) {
|
|
216
|
-
return toCanonicalYaml({
|
|
217
|
-
id: e.id,
|
|
218
|
-
term: e.term,
|
|
219
|
-
usedBy: (e.code?.uses ?? []).map((u) => ({
|
|
220
|
-
id: u.record,
|
|
221
|
-
kind: u.kind,
|
|
222
|
-
name: u.name,
|
|
223
|
-
...u.evidence.path ? {
|
|
224
|
-
code: `${u.evidence.repo ? `${u.evidence.repo}:` : ""}${u.evidence.path}${u.evidence.line ? `:${u.evidence.line}` : ""}`
|
|
225
|
-
} : {}
|
|
226
|
-
}))
|
|
227
|
-
}, ["id", "term", "usedBy", "id", "kind", "name", "code"]).trimEnd();
|
|
227
|
+
function termKey(term) {
|
|
228
|
+
const w = term.toLowerCase().replace(/[`*_"'’]/g, "").split(/[^a-z0-9]+/).filter(Boolean);
|
|
229
|
+
if (!w.length)
|
|
230
|
+
return "";
|
|
231
|
+
return [...w.slice(0, -1), singular(w.at(-1))].join(" ");
|
|
228
232
|
}
|
|
229
|
-
function
|
|
230
|
-
|
|
231
|
-
try {
|
|
232
|
-
data = (0, import_yaml.parse)(output.replace(/^```(?:ya?ml)?\s*\n|\n```\s*$/g, ""));
|
|
233
|
-
} catch (e) {
|
|
234
|
-
return {
|
|
235
|
-
proposals: [],
|
|
236
|
-
discarded: [{ reason: `the output is not YAML: ${e.message.split("\n")[0]}` }]
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
const list = data?.terms;
|
|
240
|
-
if (!Array.isArray(list))
|
|
241
|
-
return { proposals: [], discarded: [{ reason: "the output has no terms list" }] };
|
|
242
|
-
const byId = new Map(entries.map((e) => [e.id, e]));
|
|
243
|
-
const out = { proposals: [], discarded: [] };
|
|
244
|
-
for (const raw of list) {
|
|
245
|
-
const p = Proposal.safeParse(raw);
|
|
246
|
-
const id = typeof raw?.id === "string" ? raw.id : void 0;
|
|
247
|
-
if (!p.success) {
|
|
248
|
-
out.discarded.push({ ...id ? { term: id } : {}, reason: "incomplete proposal" });
|
|
249
|
-
continue;
|
|
250
|
-
}
|
|
251
|
-
const e = byId.get(p.data.id);
|
|
252
|
-
if (!e) {
|
|
253
|
-
out.discarded.push({
|
|
254
|
-
term: p.data.id,
|
|
255
|
-
reason: `${p.data.id} isn't one of the terms it was given`
|
|
256
|
-
});
|
|
257
|
-
continue;
|
|
258
|
-
}
|
|
259
|
-
if (out.proposals.some((x) => x.term === e.id)) {
|
|
260
|
-
out.discarded.push({ term: e.id, reason: "a second proposal for the same term" });
|
|
261
|
-
continue;
|
|
262
|
-
}
|
|
263
|
-
const known = new Set((e.code?.uses ?? []).map((u) => u.record));
|
|
264
|
-
const evidence = [...new Set(p.data.evidence.filter((x) => known.has(x)))];
|
|
265
|
-
if (!evidence.length) {
|
|
266
|
-
out.discarded.push({ term: e.id, reason: "it cites no record that uses the term" });
|
|
267
|
-
continue;
|
|
268
|
-
}
|
|
269
|
-
out.proposals.push({
|
|
270
|
-
term: e.id,
|
|
271
|
-
definition: p.data.definition.replace(/\s+/g, " "),
|
|
272
|
-
evidence,
|
|
273
|
-
agent: meta.agent,
|
|
274
|
-
at: meta.at,
|
|
275
|
-
...meta.runId ? { runId: meta.runId } : {}
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
return out;
|
|
233
|
+
function termId(term) {
|
|
234
|
+
return `term.${termKey(term).replace(/\s+/g, "-")}`.slice(0, 120);
|
|
279
235
|
}
|
|
280
|
-
function
|
|
281
|
-
|
|
236
|
+
function displayTerm(term) {
|
|
237
|
+
const t = term.trim().replace(/\s+/g, " ");
|
|
238
|
+
return t.charAt(0).toUpperCase() + t.slice(1);
|
|
282
239
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
if (!todo.length)
|
|
286
|
-
return { proposals: [], discarded: [] };
|
|
287
|
-
const agent = glossaryDefinerAgent(o.orgPack);
|
|
288
|
-
const now2 = o.now ?? /* @__PURE__ */ new Date();
|
|
289
|
-
const at3 = now2.toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
290
|
-
const levels = o.orgPack?.pack?.classification ?? [
|
|
291
|
-
"public",
|
|
292
|
-
"internal",
|
|
293
|
-
"confidential",
|
|
294
|
-
"restricted"
|
|
295
|
-
];
|
|
296
|
-
const sources = [
|
|
297
|
-
{
|
|
298
|
-
id: "0-instructions",
|
|
299
|
-
kind: "instruction",
|
|
300
|
-
origin: "atticus/glossary",
|
|
301
|
-
text: INSTRUCTIONS,
|
|
302
|
-
classification: "public",
|
|
303
|
-
trusted: true
|
|
304
|
-
},
|
|
305
|
-
...todo.map((e) => ({
|
|
306
|
-
id: e.id,
|
|
307
|
-
kind: "catalog",
|
|
308
|
-
origin: `catalog/generated/glossaryterm/${e.id}.yaml`,
|
|
309
|
-
text: block(e),
|
|
310
|
-
classification: "internal",
|
|
311
|
-
priority: 10
|
|
312
|
-
}))
|
|
313
|
-
];
|
|
314
|
-
const pack = buildContextPack({
|
|
315
|
-
change: GLOSSARY_CHANGE,
|
|
316
|
-
agent: agent.id,
|
|
317
|
-
budget: o.orgPack?.runtime.budgets.perAgent[agent.id] ?? agent.tokenBudget,
|
|
318
|
-
sources,
|
|
319
|
-
levels,
|
|
320
|
-
maxClassification: levels.includes("internal") ? "internal" : levels.at(-1),
|
|
321
|
-
now: now2
|
|
322
|
-
});
|
|
323
|
-
const sent = todo.filter((e) => pack.manifest.sources.some((s) => s.id === e.id));
|
|
324
|
-
const discarded = todo.filter((e) => !sent.includes(e)).map((e) => ({
|
|
325
|
-
term: e.id,
|
|
326
|
-
reason: "left out of the context pack (budget or classification)"
|
|
327
|
-
}));
|
|
328
|
-
const prepared = await runtime.prepare({
|
|
329
|
-
change: GLOSSARY_CHANGE,
|
|
330
|
-
agent,
|
|
331
|
-
pack,
|
|
332
|
-
outputPath: GLOSSARY_OUTPUT
|
|
333
|
-
});
|
|
334
|
-
const run = await runtime.run(prepared.runId);
|
|
335
|
-
if (run.state === "waiting" || run.state === "prepared")
|
|
336
|
-
return {
|
|
337
|
-
proposals: [],
|
|
338
|
-
discarded,
|
|
339
|
-
waiting: run.runId,
|
|
340
|
-
promptSha256: pack.manifest.promptSha256
|
|
341
|
-
};
|
|
342
|
-
if (run.state !== "validated" || run.output === void 0)
|
|
343
|
-
return {
|
|
344
|
-
proposals: [],
|
|
345
|
-
discarded: [
|
|
346
|
-
...discarded,
|
|
347
|
-
{ reason: `the glossary definer ${run.state}: ${run.errors.join("; ") || "no output"}` }
|
|
348
|
-
],
|
|
349
|
-
run: toRunSummary(run),
|
|
350
|
-
promptSha256: pack.manifest.promptSha256
|
|
351
|
-
};
|
|
352
|
-
const parsed = parseDefinerOutput(run.output, sent, { agent: agent.id, at: at3, runId: run.runId });
|
|
353
|
-
return {
|
|
354
|
-
proposals: parsed.proposals,
|
|
355
|
-
discarded: [...discarded, ...parsed.discarded],
|
|
356
|
-
run: toRunSummary(run),
|
|
357
|
-
promptSha256: pack.manifest.promptSha256
|
|
358
|
-
};
|
|
240
|
+
function definitionKey(d) {
|
|
241
|
+
return d.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
|
|
359
242
|
}
|
|
360
243
|
|
|
361
|
-
// packages/glossary/dist/
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
"created",
|
|
376
|
-
"delete",
|
|
377
|
-
"for",
|
|
378
|
-
"get",
|
|
379
|
-
"id",
|
|
380
|
-
"ids",
|
|
381
|
-
"in",
|
|
382
|
-
"is",
|
|
383
|
-
"list",
|
|
384
|
-
"of",
|
|
385
|
-
"on",
|
|
386
|
-
"or",
|
|
387
|
-
"patch",
|
|
388
|
-
"post",
|
|
389
|
-
"put",
|
|
390
|
-
"the",
|
|
391
|
-
"to",
|
|
392
|
-
"ts",
|
|
393
|
-
"update",
|
|
394
|
-
"updated",
|
|
395
|
-
"uuid"
|
|
396
|
-
]);
|
|
397
|
-
var QUALIFIERS = /* @__PURE__ */ new Set([
|
|
398
|
-
"cur",
|
|
399
|
-
"curr",
|
|
400
|
-
"current",
|
|
401
|
-
"has",
|
|
402
|
-
"max",
|
|
403
|
-
"min",
|
|
404
|
-
"new",
|
|
405
|
-
"old",
|
|
406
|
-
"prev",
|
|
407
|
-
"previous"
|
|
408
|
-
]);
|
|
409
|
-
var splitWords = (s) => identifierWords(s);
|
|
410
|
-
function singular(w) {
|
|
411
|
-
if (w.length <= 3)
|
|
412
|
-
return w;
|
|
413
|
-
if (/ies$/.test(w))
|
|
414
|
-
return `${w.slice(0, -3)}y`;
|
|
415
|
-
if (/(ss|us|is)$/.test(w))
|
|
416
|
-
return w;
|
|
417
|
-
if (/(sses|shes|ches|xes|zes)$/.test(w))
|
|
418
|
-
return w.slice(0, -2);
|
|
419
|
-
if (/ses$/.test(w))
|
|
420
|
-
return w.slice(0, -1);
|
|
421
|
-
if (/s$/.test(w))
|
|
422
|
-
return w.slice(0, -1);
|
|
423
|
-
return w;
|
|
424
|
-
}
|
|
425
|
-
var isNoise = (w) => STOP_TOKENS.has(w) || /^v\d+$/.test(w) || /^\d+$/.test(w);
|
|
426
|
-
function businessWords(s) {
|
|
427
|
-
return splitWords(s).filter((w) => !isNoise(w)).map(singular);
|
|
244
|
+
// packages/glossary/dist/wiki.js
|
|
245
|
+
var GLOSSARY = /\b(glossary|glossaries|definitions|terminology|terms|vocabulary)\b/i;
|
|
246
|
+
var META = /^(owner|owners|last updated|updated|status|version|author|date|classification|approver|approvers|reviewed by|reviewer|lead time|typical lead time|title|tags|see also|note|notes|example|examples|source|link|links|contact|team)$/i;
|
|
247
|
+
var strip = (s) => s.replace(/^\*\*(.+)\*\*$/, "$1").replace(/^__(.+)__$/, "$1").replace(/[`]/g, "").trim();
|
|
248
|
+
function isTermLike(raw) {
|
|
249
|
+
const t = strip(raw);
|
|
250
|
+
if (!t || t.length > 60 || !/^[A-Za-z]/.test(t))
|
|
251
|
+
return false;
|
|
252
|
+
if (/:\/\/|[|?!;]/.test(t) || /[.,]$/.test(t))
|
|
253
|
+
return false;
|
|
254
|
+
if (META.test(t))
|
|
255
|
+
return false;
|
|
256
|
+
const words2 = t.split(/\s+/);
|
|
257
|
+
return words2.length >= 1 && words2.length <= 5;
|
|
428
258
|
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
259
|
+
var cleanDefinition = (s) => s.replace(/\s+/g, " ").trim();
|
|
260
|
+
var BOLD = /^(?:\*\*([^*]+?)\*\*|__([^_]+?)__)\s*(?::|\s[—–-]\s)\s*(.+)$/;
|
|
261
|
+
var PLAIN = /^([A-Za-z][^:]{0,59}?)\s*(?::|\s[—–-]\s)\s*(.+)$/;
|
|
262
|
+
function lineDefinition(text2, scoped) {
|
|
263
|
+
const b = BOLD.exec(text2);
|
|
264
|
+
if (b) {
|
|
265
|
+
const term = (b[1] ?? b[2]).trim();
|
|
266
|
+
const definition = cleanDefinition(b[3]);
|
|
267
|
+
if (isTermLike(term) && definition.length >= 3)
|
|
268
|
+
return { term, definition, markup: "bold" };
|
|
436
269
|
return void 0;
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
return [...w.slice(0, -1), singular(w.at(-1))].join(" ");
|
|
270
|
+
}
|
|
271
|
+
if (!scoped)
|
|
272
|
+
return void 0;
|
|
273
|
+
const p = PLAIN.exec(text2);
|
|
274
|
+
if (p && isTermLike(p[1])) {
|
|
275
|
+
const definition = cleanDefinition(p[2]);
|
|
276
|
+
if (definition.length >= 3)
|
|
277
|
+
return { term: strip(p[1]), definition, markup: "line" };
|
|
278
|
+
}
|
|
279
|
+
return void 0;
|
|
448
280
|
}
|
|
449
|
-
|
|
450
|
-
|
|
281
|
+
var TERM_HEADER = /^(term|terms|name|word|phrase|concept)$/i;
|
|
282
|
+
var DEF_HEADER = /^(definition|definitions|meaning|description|what it means)$/i;
|
|
283
|
+
function scopes(page) {
|
|
284
|
+
const fmType = [page.frontMatter.type, page.frontMatter.kind].filter((v) => typeof v === "string").join(" ");
|
|
285
|
+
const pageWide = GLOSSARY.test(page.title) || GLOSSARY.test(fmType);
|
|
286
|
+
const out = [];
|
|
287
|
+
let openLevel;
|
|
288
|
+
for (const s of page.sections) {
|
|
289
|
+
if (openLevel !== void 0 && s.level > 0 && s.level <= openLevel)
|
|
290
|
+
openLevel = void 0;
|
|
291
|
+
if (openLevel === void 0 && s.level > 0 && GLOSSARY.test(s.heading))
|
|
292
|
+
openLevel = s.level;
|
|
293
|
+
out.push(pageWide || openLevel !== void 0);
|
|
294
|
+
}
|
|
295
|
+
return out;
|
|
451
296
|
}
|
|
452
|
-
function
|
|
453
|
-
const
|
|
454
|
-
|
|
297
|
+
function pageDefinitions(page) {
|
|
298
|
+
const out = [];
|
|
299
|
+
const add = (term, definition, line, markup) => {
|
|
300
|
+
const t = displayTerm(strip(term));
|
|
301
|
+
const key = termKey(t);
|
|
302
|
+
if (!key)
|
|
303
|
+
return;
|
|
304
|
+
out.push({
|
|
305
|
+
term: t,
|
|
306
|
+
key,
|
|
307
|
+
definition,
|
|
308
|
+
path: page.path,
|
|
309
|
+
line,
|
|
310
|
+
markup,
|
|
311
|
+
pageTitle: page.title,
|
|
312
|
+
...page.owner ? { owner: page.owner } : {}
|
|
313
|
+
});
|
|
314
|
+
};
|
|
315
|
+
const inScope = scopes(page);
|
|
316
|
+
page.sections.forEach((s, si) => {
|
|
317
|
+
const scoped = inScope[si];
|
|
318
|
+
if (scoped && s.level >= 2 && !GLOSSARY.test(s.heading) && isTermLike(s.heading)) {
|
|
319
|
+
const first = s.blocks[0];
|
|
320
|
+
if (first?.type === "paragraph" && !lineDefinition(first.text.split("\n")[0], true))
|
|
321
|
+
add(s.heading, cleanDefinition(first.text), first.line, "heading");
|
|
322
|
+
}
|
|
323
|
+
for (const b of s.blocks) {
|
|
324
|
+
if (b.type === "paragraph") {
|
|
325
|
+
const lines = b.text.split("\n");
|
|
326
|
+
for (let k = 0; k < lines.length; k++) {
|
|
327
|
+
const l = lines[k];
|
|
328
|
+
const next = lines[k + 1];
|
|
329
|
+
if (next !== void 0 && /^:\s+\S/.test(next) && isTermLike(l)) {
|
|
330
|
+
add(l, cleanDefinition(next.replace(/^:\s+/, "")), b.line + k, "definition-list");
|
|
331
|
+
k++;
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
const d = lineDefinition(l, scoped);
|
|
335
|
+
if (d)
|
|
336
|
+
add(d.term, d.definition, b.line + k, d.markup);
|
|
337
|
+
}
|
|
338
|
+
} else if (b.type === "list") {
|
|
339
|
+
for (const it of b.items) {
|
|
340
|
+
const d = lineDefinition(it.text.split("\n")[0], scoped);
|
|
341
|
+
if (d)
|
|
342
|
+
add(d.term, cleanDefinition([d.definition, ...it.text.split("\n").slice(1)].join(" ")), it.line, d.markup === "line" ? "list" : d.markup);
|
|
343
|
+
}
|
|
344
|
+
} else if (b.type === "table" && scoped) {
|
|
345
|
+
const ti = b.header.findIndex((h) => TERM_HEADER.test(strip(h)));
|
|
346
|
+
const di = b.header.findIndex((h) => DEF_HEADER.test(strip(h)));
|
|
347
|
+
if (ti < 0 || di < 0)
|
|
348
|
+
continue;
|
|
349
|
+
for (const r of b.rows) {
|
|
350
|
+
const term = r.cells[ti] ?? "";
|
|
351
|
+
const def = cleanDefinition(r.cells[di] ?? "");
|
|
352
|
+
if (isTermLike(term) && def.length >= 3)
|
|
353
|
+
add(term, def, r.line, "table");
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
return out.sort((a, b) => a.line - b.line);
|
|
455
359
|
}
|
|
456
|
-
function
|
|
457
|
-
return
|
|
360
|
+
function wikiDefinitions(pages) {
|
|
361
|
+
return pages.flatMap(pageDefinitions).sort((a, b) => a.path === b.path ? a.line - b.line : a.path < b.path ? -1 : 1);
|
|
458
362
|
}
|
|
459
363
|
|
|
460
|
-
// packages/glossary/dist/decisions.js
|
|
461
|
-
var GlossaryError = class extends Error {
|
|
462
|
-
code;
|
|
463
|
-
constructor(code, message) {
|
|
464
|
-
super(message);
|
|
465
|
-
this.code = code;
|
|
466
|
-
this.name = "GlossaryError";
|
|
467
|
-
}
|
|
468
|
-
};
|
|
469
|
-
var now = () => (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
470
|
-
function decisionId(prefix, key) {
|
|
471
|
-
const id = `${prefix}.${key}`;
|
|
472
|
-
if (id.length <= 120)
|
|
473
|
-
return id;
|
|
474
|
-
return `${id.slice(0, 110)}-${createHash("sha256").update(id).digest("hex").slice(0, 8)}`;
|
|
475
|
-
}
|
|
476
|
-
function decision(id, target, action, value, reason, o) {
|
|
477
|
-
if (!o.by.trim())
|
|
478
|
-
throw new GlossaryError("invalid", "Say who decides (--by <handle>).");
|
|
479
|
-
const p = parseRecord({
|
|
480
|
-
schemaVersion: 2,
|
|
481
|
-
kind: "CuratedDecision",
|
|
482
|
-
id,
|
|
483
|
-
status: "current",
|
|
484
|
-
confidence: "asserted",
|
|
485
|
-
evidence: [{ source: "manual", by: o.by }],
|
|
486
|
-
target,
|
|
487
|
-
action,
|
|
488
|
-
value,
|
|
489
|
-
reason: o.reason?.trim() || reason,
|
|
490
|
-
by: o.by,
|
|
491
|
-
at: o.at ?? now()
|
|
492
|
-
});
|
|
493
|
-
if (!p.ok)
|
|
494
|
-
throw new GlossaryError("invalid", `That decision isn't valid: ${p.issues.map((i) => `${i.path}: ${i.message}`).join("; ")}`);
|
|
495
|
-
return p.record;
|
|
496
|
-
}
|
|
497
|
-
function resolveTermId(termOrId) {
|
|
498
|
-
const t = termOrId.trim();
|
|
499
|
-
if (/^term\.[a-z0-9][a-z0-9._-]*$/.test(t))
|
|
500
|
-
return t;
|
|
501
|
-
const id = termId(t);
|
|
502
|
-
if (id === "term.")
|
|
503
|
-
throw new GlossaryError("invalid", `"${termOrId}" is not a term.`);
|
|
504
|
-
return id;
|
|
505
|
-
}
|
|
506
|
-
function defineDecision(term, definition, o) {
|
|
507
|
-
const def = definition.trim().replace(/\s+/g, " ");
|
|
508
|
-
if (def.length < 3)
|
|
509
|
-
throw new GlossaryError("invalid", "A definition needs a sentence.");
|
|
510
|
-
const id = resolveTermId(term);
|
|
511
|
-
const name = o.existing?.term ?? (term.startsWith("term.") ? void 0 : displayTerm(term));
|
|
512
|
-
return decision(decisionId("glossary.define", id.slice("term.".length)), id, "assert", { ...name ? { term: name } : {}, definition: def, ...o.owner ? { owner: o.owner } : {} }, `Glossary definition of ${name ?? id}`, o);
|
|
513
|
-
}
|
|
514
|
-
function aliasDecision(term, aliases, o) {
|
|
515
|
-
const id = resolveTermId(term);
|
|
516
|
-
const byKey = /* @__PURE__ */ new Map();
|
|
517
|
-
for (const a of aliases.map((x) => x.trim().replace(/\s+/g, " ")).filter(Boolean))
|
|
518
|
-
if (!byKey.has(termKey(a)))
|
|
519
|
-
byKey.set(termKey(a), a);
|
|
520
|
-
const names = [...byKey.values()].sort();
|
|
521
|
-
if (!names.length)
|
|
522
|
-
throw new GlossaryError("invalid", "Give at least one other name.");
|
|
523
|
-
return decision(decisionId("glossary.alias", id.slice("term.".length)), id, "alias", names, `Other names for ${id}`, o);
|
|
524
|
-
}
|
|
525
|
-
function linkDecision(term, recordId, records, o) {
|
|
526
|
-
const byId = new Map(records.map((r) => [r.id, r]));
|
|
527
|
-
const [base2, field] = recordId.split("#");
|
|
528
|
-
const target = byId.get(base2);
|
|
529
|
-
if (!target)
|
|
530
|
-
throw new GlossaryError("not-found", `There is no record ${base2} in the catalog.`);
|
|
531
|
-
if (field !== void 0) {
|
|
532
|
-
if (target.kind !== "Schema" || !target.fields.some((f) => f.name === field))
|
|
533
|
-
throw new GlossaryError("not-found", `${base2} has no schema field ${field}.`);
|
|
534
|
-
} else if (target.kind === "Column") {
|
|
535
|
-
return decision(decisionId("glossary.link", target.id), target.id, "assert", { glossaryTerm: term.id }, `${target.id} holds the glossary term ${term.term}`, o);
|
|
536
|
-
}
|
|
537
|
-
const fields = [.../* @__PURE__ */ new Set([...term.fields, recordId])].sort();
|
|
538
|
-
return decision(decisionId("glossary.fields", term.id.slice("term.".length)), term.id, "assert", { fields }, `What the glossary term ${term.term} is linked to`, o);
|
|
539
|
-
}
|
|
540
|
-
async function writeDecisions(curatedDir, decisions) {
|
|
541
|
-
const out = [];
|
|
542
|
-
for (const d of decisions) {
|
|
543
|
-
const rel = recordPath(d);
|
|
544
|
-
const full = join2(curatedDir, ...rel.split("/"));
|
|
545
|
-
await mkdir2(dirname(full), { recursive: true });
|
|
546
|
-
await writeFile2(full, serializeRecord(d), "utf8");
|
|
547
|
-
out.push(rel);
|
|
548
|
-
}
|
|
549
|
-
return out;
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
// packages/glossary/dist/scan.js
|
|
553
|
-
import { mkdir as mkdir3, readdir as readdir2, readFile as readFile2, rm as rm2, writeFile as writeFile3 } from "node:fs/promises";
|
|
554
|
-
import { dirname as dirname2, join as join3, relative, sep } from "node:path";
|
|
555
|
-
|
|
556
364
|
// packages/glossary/dist/code.js
|
|
557
365
|
var firstEvidence = (r) => r.evidence[0];
|
|
558
366
|
function named(records) {
|
|
@@ -700,126 +508,6 @@ function fieldNames(records) {
|
|
|
700
508
|
return named(records).filter((n) => n.isField).map((n) => ({ record: n.record, name: n.name }));
|
|
701
509
|
}
|
|
702
510
|
|
|
703
|
-
// packages/glossary/dist/wiki.js
|
|
704
|
-
var GLOSSARY = /\b(glossary|glossaries|definitions|terminology|terms|vocabulary)\b/i;
|
|
705
|
-
var META = /^(owner|owners|last updated|updated|status|version|author|date|classification|approver|approvers|reviewed by|reviewer|lead time|typical lead time|title|tags|see also|note|notes|example|examples|source|link|links|contact|team)$/i;
|
|
706
|
-
var strip = (s) => s.replace(/^\*\*(.+)\*\*$/, "$1").replace(/^__(.+)__$/, "$1").replace(/[`]/g, "").trim();
|
|
707
|
-
function isTermLike(raw) {
|
|
708
|
-
const t = strip(raw);
|
|
709
|
-
if (!t || t.length > 60 || !/^[A-Za-z]/.test(t))
|
|
710
|
-
return false;
|
|
711
|
-
if (/:\/\/|[|?!;]/.test(t) || /[.,]$/.test(t))
|
|
712
|
-
return false;
|
|
713
|
-
if (META.test(t))
|
|
714
|
-
return false;
|
|
715
|
-
const words2 = t.split(/\s+/);
|
|
716
|
-
return words2.length >= 1 && words2.length <= 5;
|
|
717
|
-
}
|
|
718
|
-
var cleanDefinition = (s) => s.replace(/\s+/g, " ").trim();
|
|
719
|
-
var BOLD = /^(?:\*\*([^*]+?)\*\*|__([^_]+?)__)\s*(?::|\s[—–-]\s)\s*(.+)$/;
|
|
720
|
-
var PLAIN = /^([A-Za-z][^:]{0,59}?)\s*(?::|\s[—–-]\s)\s*(.+)$/;
|
|
721
|
-
function lineDefinition(text, scoped) {
|
|
722
|
-
const b = BOLD.exec(text);
|
|
723
|
-
if (b) {
|
|
724
|
-
const term = (b[1] ?? b[2]).trim();
|
|
725
|
-
const definition = cleanDefinition(b[3]);
|
|
726
|
-
if (isTermLike(term) && definition.length >= 3)
|
|
727
|
-
return { term, definition, markup: "bold" };
|
|
728
|
-
return void 0;
|
|
729
|
-
}
|
|
730
|
-
if (!scoped)
|
|
731
|
-
return void 0;
|
|
732
|
-
const p = PLAIN.exec(text);
|
|
733
|
-
if (p && isTermLike(p[1])) {
|
|
734
|
-
const definition = cleanDefinition(p[2]);
|
|
735
|
-
if (definition.length >= 3)
|
|
736
|
-
return { term: strip(p[1]), definition, markup: "line" };
|
|
737
|
-
}
|
|
738
|
-
return void 0;
|
|
739
|
-
}
|
|
740
|
-
var TERM_HEADER = /^(term|terms|name|word|phrase|concept)$/i;
|
|
741
|
-
var DEF_HEADER = /^(definition|definitions|meaning|description|what it means)$/i;
|
|
742
|
-
function scopes(page) {
|
|
743
|
-
const fmType = [page.frontMatter.type, page.frontMatter.kind].filter((v) => typeof v === "string").join(" ");
|
|
744
|
-
const pageWide = GLOSSARY.test(page.title) || GLOSSARY.test(fmType);
|
|
745
|
-
const out = [];
|
|
746
|
-
let openLevel;
|
|
747
|
-
for (const s of page.sections) {
|
|
748
|
-
if (openLevel !== void 0 && s.level > 0 && s.level <= openLevel)
|
|
749
|
-
openLevel = void 0;
|
|
750
|
-
if (openLevel === void 0 && s.level > 0 && GLOSSARY.test(s.heading))
|
|
751
|
-
openLevel = s.level;
|
|
752
|
-
out.push(pageWide || openLevel !== void 0);
|
|
753
|
-
}
|
|
754
|
-
return out;
|
|
755
|
-
}
|
|
756
|
-
function pageDefinitions(page) {
|
|
757
|
-
const out = [];
|
|
758
|
-
const add = (term, definition, line, markup) => {
|
|
759
|
-
const t = displayTerm(strip(term));
|
|
760
|
-
const key = termKey(t);
|
|
761
|
-
if (!key)
|
|
762
|
-
return;
|
|
763
|
-
out.push({
|
|
764
|
-
term: t,
|
|
765
|
-
key,
|
|
766
|
-
definition,
|
|
767
|
-
path: page.path,
|
|
768
|
-
line,
|
|
769
|
-
markup,
|
|
770
|
-
pageTitle: page.title,
|
|
771
|
-
...page.owner ? { owner: page.owner } : {}
|
|
772
|
-
});
|
|
773
|
-
};
|
|
774
|
-
const inScope = scopes(page);
|
|
775
|
-
page.sections.forEach((s, si) => {
|
|
776
|
-
const scoped = inScope[si];
|
|
777
|
-
if (scoped && s.level >= 2 && !GLOSSARY.test(s.heading) && isTermLike(s.heading)) {
|
|
778
|
-
const first = s.blocks[0];
|
|
779
|
-
if (first?.type === "paragraph" && !lineDefinition(first.text.split("\n")[0], true))
|
|
780
|
-
add(s.heading, cleanDefinition(first.text), first.line, "heading");
|
|
781
|
-
}
|
|
782
|
-
for (const b of s.blocks) {
|
|
783
|
-
if (b.type === "paragraph") {
|
|
784
|
-
const lines = b.text.split("\n");
|
|
785
|
-
for (let k = 0; k < lines.length; k++) {
|
|
786
|
-
const l = lines[k];
|
|
787
|
-
const next = lines[k + 1];
|
|
788
|
-
if (next !== void 0 && /^:\s+\S/.test(next) && isTermLike(l)) {
|
|
789
|
-
add(l, cleanDefinition(next.replace(/^:\s+/, "")), b.line + k, "definition-list");
|
|
790
|
-
k++;
|
|
791
|
-
continue;
|
|
792
|
-
}
|
|
793
|
-
const d = lineDefinition(l, scoped);
|
|
794
|
-
if (d)
|
|
795
|
-
add(d.term, d.definition, b.line + k, d.markup);
|
|
796
|
-
}
|
|
797
|
-
} else if (b.type === "list") {
|
|
798
|
-
for (const it of b.items) {
|
|
799
|
-
const d = lineDefinition(it.text.split("\n")[0], scoped);
|
|
800
|
-
if (d)
|
|
801
|
-
add(d.term, cleanDefinition([d.definition, ...it.text.split("\n").slice(1)].join(" ")), it.line, d.markup === "line" ? "list" : d.markup);
|
|
802
|
-
}
|
|
803
|
-
} else if (b.type === "table" && scoped) {
|
|
804
|
-
const ti = b.header.findIndex((h) => TERM_HEADER.test(strip(h)));
|
|
805
|
-
const di = b.header.findIndex((h) => DEF_HEADER.test(strip(h)));
|
|
806
|
-
if (ti < 0 || di < 0)
|
|
807
|
-
continue;
|
|
808
|
-
for (const r of b.rows) {
|
|
809
|
-
const term = r.cells[ti] ?? "";
|
|
810
|
-
const def = cleanDefinition(r.cells[di] ?? "");
|
|
811
|
-
if (isTermLike(term) && def.length >= 3)
|
|
812
|
-
add(term, def, r.line, "table");
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
});
|
|
817
|
-
return out.sort((a, b) => a.line - b.line);
|
|
818
|
-
}
|
|
819
|
-
function wikiDefinitions(pages) {
|
|
820
|
-
return pages.flatMap(pageDefinitions).sort((a, b) => a.path === b.path ? a.line - b.line : a.path < b.path ? -1 : 1);
|
|
821
|
-
}
|
|
822
|
-
|
|
823
511
|
// packages/glossary/dist/build.js
|
|
824
512
|
var isDecision = (r) => r.kind === "CuratedDecision" && r.status === "current";
|
|
825
513
|
var isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
@@ -1021,38 +709,377 @@ function buildGlossary(input) {
|
|
|
1021
709
|
for (const col of linkedColumns)
|
|
1022
710
|
columnLinks.push({ column: col, term: id, how: "decision" });
|
|
1023
711
|
}
|
|
1024
|
-
const decided = new Set(columnLinks.filter((l) => l.how === "decision").map((l) => l.column));
|
|
1025
|
-
const links = columnLinks.filter((l) => l.how === "decision" || !decided.has(l.column)).sort((a, b) => a.column.localeCompare(b.column) || a.term.localeCompare(b.term));
|
|
1026
|
-
const covered = /* @__PURE__ */ new Set([...terms.flatMap((t) => t.fields), ...links.map((l) => l.column)]);
|
|
1027
|
-
const names = entries.map((e) => ({ id: e.id, term: e.term, aliases: e.aliases }));
|
|
1028
|
-
const hints = fieldNames(records).filter((f) => !covered.has(f.record)).map((f) => namingHint(f.record, f.name, names)).filter((h) => !!h);
|
|
1029
|
-
return {
|
|
1030
|
-
terms,
|
|
1031
|
-
entries,
|
|
1032
|
-
columnLinks: links,
|
|
1033
|
-
conflicts: conflicts.sort((a, b) => a.term.localeCompare(b.term) || a.kind.localeCompare(b.kind)),
|
|
1034
|
-
hints,
|
|
1035
|
-
problems
|
|
1036
|
-
};
|
|
712
|
+
const decided = new Set(columnLinks.filter((l) => l.how === "decision").map((l) => l.column));
|
|
713
|
+
const links = columnLinks.filter((l) => l.how === "decision" || !decided.has(l.column)).sort((a, b) => a.column.localeCompare(b.column) || a.term.localeCompare(b.term));
|
|
714
|
+
const covered = /* @__PURE__ */ new Set([...terms.flatMap((t) => t.fields), ...links.map((l) => l.column)]);
|
|
715
|
+
const names = entries.map((e) => ({ id: e.id, term: e.term, aliases: e.aliases }));
|
|
716
|
+
const hints = fieldNames(records).filter((f) => !covered.has(f.record)).map((f) => namingHint(f.record, f.name, names)).filter((h) => !!h);
|
|
717
|
+
return {
|
|
718
|
+
terms,
|
|
719
|
+
entries,
|
|
720
|
+
columnLinks: links,
|
|
721
|
+
conflicts: conflicts.sort((a, b) => a.term.localeCompare(b.term) || a.kind.localeCompare(b.kind)),
|
|
722
|
+
hints,
|
|
723
|
+
problems
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
function applyGlossary(records, build) {
|
|
727
|
+
const link = /* @__PURE__ */ new Map();
|
|
728
|
+
for (const l of build.columnLinks)
|
|
729
|
+
if (!link.has(l.column))
|
|
730
|
+
link.set(l.column, l.term);
|
|
731
|
+
const out = [];
|
|
732
|
+
for (const r of records) {
|
|
733
|
+
if (r.kind === "GlossaryTerm")
|
|
734
|
+
continue;
|
|
735
|
+
if (r.kind === "Column" && !r.glossaryTerm && link.has(r.id))
|
|
736
|
+
out.push({ ...r, glossaryTerm: link.get(r.id) });
|
|
737
|
+
else
|
|
738
|
+
out.push(r);
|
|
739
|
+
}
|
|
740
|
+
return [...out, ...build.terms];
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// packages/glossary/dist/proposals.js
|
|
744
|
+
var import_yaml = __toESM(require_dist(), 1);
|
|
745
|
+
import { mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
746
|
+
import { join } from "node:path";
|
|
747
|
+
var GLOSSARY_DEFINER = "glossary-definer";
|
|
748
|
+
var GLOSSARY_CHANGE = "glossary";
|
|
749
|
+
var GLOSSARY_OUTPUT = "catalog/proposals/glossary-definitions.yaml";
|
|
750
|
+
var DefinitionProposalFile = external_exports.strictObject({
|
|
751
|
+
schemaVersion: external_exports.literal(1),
|
|
752
|
+
term: RecordId,
|
|
753
|
+
definition: external_exports.string().min(1),
|
|
754
|
+
evidence: external_exports.array(external_exports.string().min(1)).min(1),
|
|
755
|
+
agent: RecordId,
|
|
756
|
+
runId: external_exports.string().optional(),
|
|
757
|
+
at: IsoDateTime
|
|
758
|
+
});
|
|
759
|
+
var LEADING = ["schemaVersion", "term", "definition", "evidence"];
|
|
760
|
+
async function writeDefinitionProposals(dir, proposals) {
|
|
761
|
+
const written = [];
|
|
762
|
+
if (!proposals.length)
|
|
763
|
+
return written;
|
|
764
|
+
await mkdir(join(dir, "glossaryterm"), { recursive: true });
|
|
765
|
+
for (const p of proposals) {
|
|
766
|
+
const file = `glossaryterm/${p.term}.yaml`;
|
|
767
|
+
const body = DefinitionProposalFile.parse({ schemaVersion: 1, ...p });
|
|
768
|
+
await writeFile(join(dir, file), toCanonicalYaml(body, LEADING), "utf8");
|
|
769
|
+
written.push(file);
|
|
770
|
+
}
|
|
771
|
+
return written;
|
|
772
|
+
}
|
|
773
|
+
async function readDefinitionProposals(dir) {
|
|
774
|
+
const proposals = [];
|
|
775
|
+
const problems = [];
|
|
776
|
+
let files;
|
|
777
|
+
try {
|
|
778
|
+
files = (await readdir(join(dir, "glossaryterm"))).filter((f) => /\.ya?ml$/.test(f)).sort();
|
|
779
|
+
} catch {
|
|
780
|
+
return { proposals, problems };
|
|
781
|
+
}
|
|
782
|
+
for (const f of files) {
|
|
783
|
+
const file = `glossaryterm/${f}`;
|
|
784
|
+
let data;
|
|
785
|
+
try {
|
|
786
|
+
data = (0, import_yaml.parse)(await readFile(join(dir, file), "utf8"));
|
|
787
|
+
} catch (e) {
|
|
788
|
+
problems.push({ file, message: `invalid YAML: ${e.message.split("\n")[0]}` });
|
|
789
|
+
continue;
|
|
790
|
+
}
|
|
791
|
+
const p = DefinitionProposalFile.safeParse(data);
|
|
792
|
+
if (!p.success) {
|
|
793
|
+
problems.push({
|
|
794
|
+
file,
|
|
795
|
+
message: p.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")
|
|
796
|
+
});
|
|
797
|
+
continue;
|
|
798
|
+
}
|
|
799
|
+
const { schemaVersion: _v, ...rest } = p.data;
|
|
800
|
+
void _v;
|
|
801
|
+
proposals.push(rest);
|
|
802
|
+
}
|
|
803
|
+
return { proposals, problems };
|
|
804
|
+
}
|
|
805
|
+
function glossaryDefinerAgent(orgPack) {
|
|
806
|
+
const own = orgPack?.agents.find((a) => a.id === GLOSSARY_DEFINER);
|
|
807
|
+
if (own)
|
|
808
|
+
return own;
|
|
809
|
+
return AgentDefinition.parse({
|
|
810
|
+
schemaVersion: 1,
|
|
811
|
+
id: GLOSSARY_DEFINER,
|
|
812
|
+
role: "business-analyst",
|
|
813
|
+
description: "Proposes a one-sentence business definition for each glossary term found only in code, from where the code uses it.",
|
|
814
|
+
inputs: ["glossary", "catalog-slice"],
|
|
815
|
+
// C5 has no output kind for catalog proposals (see ADR 0031); a definition seeds specs.
|
|
816
|
+
outputArtifact: "specs",
|
|
817
|
+
tokenBudget: orgPack?.runtime.budgets.defaultTokens ?? 15e4,
|
|
818
|
+
allowedRuntimes: ["hand-off", "model", "headless-claude-code", "mock", "replay"]
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
var INSTRUCTIONS = `# Glossary definer
|
|
822
|
+
|
|
823
|
+
You propose business definitions for words an estate's code uses that no wiki page defines. Each term below is a data block: its id, the word, and the records (endpoints, topics, tables, columns, schema fields) that use it. For each term propose:
|
|
824
|
+
|
|
825
|
+
- definition: one sentence a product owner would accept, saying what the word means to the business, only from the evidence;
|
|
826
|
+
- evidence: the record ids from that term's block your definition relies on.
|
|
827
|
+
|
|
828
|
+
Don't invent systems, rules or numbers. A proposal that cites no record id from its term is discarded. Write YAML and nothing else:
|
|
829
|
+
|
|
830
|
+
terms:
|
|
831
|
+
- id: <term id>
|
|
832
|
+
definition: <one sentence>
|
|
833
|
+
evidence: [<record id>, ...]`;
|
|
834
|
+
var Proposal = external_exports.object({
|
|
835
|
+
id: external_exports.string().min(1),
|
|
836
|
+
definition: external_exports.string().trim().min(3).max(400),
|
|
837
|
+
evidence: external_exports.array(external_exports.coerce.string()).default([])
|
|
838
|
+
});
|
|
839
|
+
function block(e) {
|
|
840
|
+
return toCanonicalYaml({
|
|
841
|
+
id: e.id,
|
|
842
|
+
term: e.term,
|
|
843
|
+
usedBy: (e.code?.uses ?? []).map((u) => ({
|
|
844
|
+
id: u.record,
|
|
845
|
+
kind: u.kind,
|
|
846
|
+
name: u.name,
|
|
847
|
+
...u.evidence.path ? {
|
|
848
|
+
code: `${u.evidence.repo ? `${u.evidence.repo}:` : ""}${u.evidence.path}${u.evidence.line ? `:${u.evidence.line}` : ""}`
|
|
849
|
+
} : {}
|
|
850
|
+
}))
|
|
851
|
+
}, ["id", "term", "usedBy", "id", "kind", "name", "code"]).trimEnd();
|
|
852
|
+
}
|
|
853
|
+
function parseDefinerOutput(output, entries, meta) {
|
|
854
|
+
let data;
|
|
855
|
+
try {
|
|
856
|
+
data = (0, import_yaml.parse)(output.replace(/^```(?:ya?ml)?\s*\n|\n```\s*$/g, ""));
|
|
857
|
+
} catch (e) {
|
|
858
|
+
return {
|
|
859
|
+
proposals: [],
|
|
860
|
+
discarded: [{ reason: `the output is not YAML: ${e.message.split("\n")[0]}` }]
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
const list = data?.terms;
|
|
864
|
+
if (!Array.isArray(list))
|
|
865
|
+
return { proposals: [], discarded: [{ reason: "the output has no terms list" }] };
|
|
866
|
+
const byId = new Map(entries.map((e) => [e.id, e]));
|
|
867
|
+
const out = { proposals: [], discarded: [] };
|
|
868
|
+
for (const raw of list) {
|
|
869
|
+
const p = Proposal.safeParse(raw);
|
|
870
|
+
const id = typeof raw?.id === "string" ? raw.id : void 0;
|
|
871
|
+
if (!p.success) {
|
|
872
|
+
out.discarded.push({ ...id ? { term: id } : {}, reason: "incomplete proposal" });
|
|
873
|
+
continue;
|
|
874
|
+
}
|
|
875
|
+
const e = byId.get(p.data.id);
|
|
876
|
+
if (!e) {
|
|
877
|
+
out.discarded.push({
|
|
878
|
+
term: p.data.id,
|
|
879
|
+
reason: `${p.data.id} isn't one of the terms it was given`
|
|
880
|
+
});
|
|
881
|
+
continue;
|
|
882
|
+
}
|
|
883
|
+
if (out.proposals.some((x) => x.term === e.id)) {
|
|
884
|
+
out.discarded.push({ term: e.id, reason: "a second proposal for the same term" });
|
|
885
|
+
continue;
|
|
886
|
+
}
|
|
887
|
+
const known = new Set((e.code?.uses ?? []).map((u) => u.record));
|
|
888
|
+
const evidence = [...new Set(p.data.evidence.filter((x) => known.has(x)))];
|
|
889
|
+
if (!evidence.length) {
|
|
890
|
+
out.discarded.push({ term: e.id, reason: "it cites no record that uses the term" });
|
|
891
|
+
continue;
|
|
892
|
+
}
|
|
893
|
+
out.proposals.push({
|
|
894
|
+
term: e.id,
|
|
895
|
+
definition: p.data.definition.replace(/\s+/g, " "),
|
|
896
|
+
evidence,
|
|
897
|
+
agent: meta.agent,
|
|
898
|
+
at: meta.at,
|
|
899
|
+
...meta.runId ? { runId: meta.runId } : {}
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
return out;
|
|
903
|
+
}
|
|
904
|
+
function undefinedCodeTerms(entries) {
|
|
905
|
+
return entries.filter((e) => e.sources.length === 1 && e.sources[0] === "code" && (e.code?.uses.length ?? 0) > 0);
|
|
906
|
+
}
|
|
907
|
+
async function proposeDefinitions(entries, runtime, o = {}) {
|
|
908
|
+
const todo = [...entries].sort((a, b) => a.id.localeCompare(b.id));
|
|
909
|
+
if (!todo.length)
|
|
910
|
+
return { proposals: [], discarded: [] };
|
|
911
|
+
const agent = glossaryDefinerAgent(o.orgPack);
|
|
912
|
+
const now2 = o.now ?? /* @__PURE__ */ new Date();
|
|
913
|
+
const at3 = now2.toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
914
|
+
const levels = o.orgPack?.pack?.classification ?? [
|
|
915
|
+
"public",
|
|
916
|
+
"internal",
|
|
917
|
+
"confidential",
|
|
918
|
+
"restricted"
|
|
919
|
+
];
|
|
920
|
+
const sources = [
|
|
921
|
+
{
|
|
922
|
+
id: "0-instructions",
|
|
923
|
+
kind: "instruction",
|
|
924
|
+
origin: "atticus/glossary",
|
|
925
|
+
text: INSTRUCTIONS,
|
|
926
|
+
classification: "public",
|
|
927
|
+
trusted: true
|
|
928
|
+
},
|
|
929
|
+
...todo.map((e) => ({
|
|
930
|
+
id: e.id,
|
|
931
|
+
kind: "catalog",
|
|
932
|
+
origin: `catalog/generated/glossaryterm/${e.id}.yaml`,
|
|
933
|
+
text: block(e),
|
|
934
|
+
classification: "internal",
|
|
935
|
+
priority: 10
|
|
936
|
+
}))
|
|
937
|
+
];
|
|
938
|
+
const pack = buildContextPack({
|
|
939
|
+
change: GLOSSARY_CHANGE,
|
|
940
|
+
agent: agent.id,
|
|
941
|
+
budget: o.orgPack?.runtime.budgets.perAgent[agent.id] ?? agent.tokenBudget,
|
|
942
|
+
sources,
|
|
943
|
+
levels,
|
|
944
|
+
maxClassification: levels.includes("internal") ? "internal" : levels.at(-1),
|
|
945
|
+
now: now2
|
|
946
|
+
});
|
|
947
|
+
const sent = todo.filter((e) => pack.manifest.sources.some((s) => s.id === e.id));
|
|
948
|
+
const discarded = todo.filter((e) => !sent.includes(e)).map((e) => ({
|
|
949
|
+
term: e.id,
|
|
950
|
+
reason: "left out of the context pack (budget or classification)"
|
|
951
|
+
}));
|
|
952
|
+
const prepared = await runtime.prepare({
|
|
953
|
+
change: GLOSSARY_CHANGE,
|
|
954
|
+
agent,
|
|
955
|
+
pack,
|
|
956
|
+
outputPath: GLOSSARY_OUTPUT
|
|
957
|
+
});
|
|
958
|
+
const run = await runtime.run(prepared.runId);
|
|
959
|
+
if (run.state === "waiting" || run.state === "prepared")
|
|
960
|
+
return {
|
|
961
|
+
proposals: [],
|
|
962
|
+
discarded,
|
|
963
|
+
waiting: run.runId,
|
|
964
|
+
promptSha256: pack.manifest.promptSha256
|
|
965
|
+
};
|
|
966
|
+
if (run.state !== "validated" || run.output === void 0)
|
|
967
|
+
return {
|
|
968
|
+
proposals: [],
|
|
969
|
+
discarded: [
|
|
970
|
+
...discarded,
|
|
971
|
+
{ reason: `the glossary definer ${run.state}: ${run.errors.join("; ") || "no output"}` }
|
|
972
|
+
],
|
|
973
|
+
run: toRunSummary(run),
|
|
974
|
+
promptSha256: pack.manifest.promptSha256
|
|
975
|
+
};
|
|
976
|
+
const parsed = parseDefinerOutput(run.output, sent, { agent: agent.id, at: at3, runId: run.runId });
|
|
977
|
+
return {
|
|
978
|
+
proposals: parsed.proposals,
|
|
979
|
+
discarded: [...discarded, ...parsed.discarded],
|
|
980
|
+
run: toRunSummary(run),
|
|
981
|
+
promptSha256: pack.manifest.promptSha256
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
// packages/glossary/dist/decisions.js
|
|
986
|
+
import { createHash } from "node:crypto";
|
|
987
|
+
import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
|
|
988
|
+
import { dirname, join as join2 } from "node:path";
|
|
989
|
+
var GlossaryError = class extends Error {
|
|
990
|
+
code;
|
|
991
|
+
constructor(code, message) {
|
|
992
|
+
super(message);
|
|
993
|
+
this.code = code;
|
|
994
|
+
this.name = "GlossaryError";
|
|
995
|
+
}
|
|
996
|
+
};
|
|
997
|
+
var now = () => (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
998
|
+
function decisionId(prefix, key) {
|
|
999
|
+
const id = `${prefix}.${key}`;
|
|
1000
|
+
if (id.length <= 120)
|
|
1001
|
+
return id;
|
|
1002
|
+
return `${id.slice(0, 110)}-${createHash("sha256").update(id).digest("hex").slice(0, 8)}`;
|
|
1003
|
+
}
|
|
1004
|
+
function decision(id, target, action, value, reason, o) {
|
|
1005
|
+
if (!o.by.trim())
|
|
1006
|
+
throw new GlossaryError("invalid", "Say who decides (--by <handle>).");
|
|
1007
|
+
const p = parseRecord({
|
|
1008
|
+
schemaVersion: 2,
|
|
1009
|
+
kind: "CuratedDecision",
|
|
1010
|
+
id,
|
|
1011
|
+
status: "current",
|
|
1012
|
+
confidence: "asserted",
|
|
1013
|
+
evidence: [{ source: "manual", by: o.by }],
|
|
1014
|
+
target,
|
|
1015
|
+
action,
|
|
1016
|
+
value,
|
|
1017
|
+
reason: o.reason?.trim() || reason,
|
|
1018
|
+
by: o.by,
|
|
1019
|
+
at: o.at ?? now()
|
|
1020
|
+
});
|
|
1021
|
+
if (!p.ok)
|
|
1022
|
+
throw new GlossaryError("invalid", `That decision isn't valid: ${p.issues.map((i) => `${i.path}: ${i.message}`).join("; ")}`);
|
|
1023
|
+
return p.record;
|
|
1024
|
+
}
|
|
1025
|
+
function resolveTermId(termOrId) {
|
|
1026
|
+
const t = termOrId.trim();
|
|
1027
|
+
if (/^term\.[a-z0-9][a-z0-9._-]*$/.test(t))
|
|
1028
|
+
return t;
|
|
1029
|
+
const id = termId(t);
|
|
1030
|
+
if (id === "term.")
|
|
1031
|
+
throw new GlossaryError("invalid", `"${termOrId}" is not a term.`);
|
|
1032
|
+
return id;
|
|
1033
|
+
}
|
|
1034
|
+
function defineDecision(term, definition, o) {
|
|
1035
|
+
const def = definition.trim().replace(/\s+/g, " ");
|
|
1036
|
+
if (def.length < 3)
|
|
1037
|
+
throw new GlossaryError("invalid", "A definition needs a sentence.");
|
|
1038
|
+
const id = resolveTermId(term);
|
|
1039
|
+
const name = o.existing?.term ?? (term.startsWith("term.") ? void 0 : displayTerm(term));
|
|
1040
|
+
return decision(decisionId("glossary.define", id.slice("term.".length)), id, "assert", { ...name ? { term: name } : {}, definition: def, ...o.owner ? { owner: o.owner } : {} }, `Glossary definition of ${name ?? id}`, o);
|
|
1041
|
+
}
|
|
1042
|
+
function aliasDecision(term, aliases, o) {
|
|
1043
|
+
const id = resolveTermId(term);
|
|
1044
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
1045
|
+
for (const a of aliases.map((x) => x.trim().replace(/\s+/g, " ")).filter(Boolean))
|
|
1046
|
+
if (!byKey.has(termKey(a)))
|
|
1047
|
+
byKey.set(termKey(a), a);
|
|
1048
|
+
const names = [...byKey.values()].sort();
|
|
1049
|
+
if (!names.length)
|
|
1050
|
+
throw new GlossaryError("invalid", "Give at least one other name.");
|
|
1051
|
+
return decision(decisionId("glossary.alias", id.slice("term.".length)), id, "alias", names, `Other names for ${id}`, o);
|
|
1052
|
+
}
|
|
1053
|
+
function linkDecision(term, recordId, records, o) {
|
|
1054
|
+
const byId = new Map(records.map((r) => [r.id, r]));
|
|
1055
|
+
const [base2, field] = recordId.split("#");
|
|
1056
|
+
const target = byId.get(base2);
|
|
1057
|
+
if (!target)
|
|
1058
|
+
throw new GlossaryError("not-found", `There is no record ${base2} in the catalog.`);
|
|
1059
|
+
if (field !== void 0) {
|
|
1060
|
+
if (target.kind !== "Schema" || !target.fields.some((f) => f.name === field))
|
|
1061
|
+
throw new GlossaryError("not-found", `${base2} has no schema field ${field}.`);
|
|
1062
|
+
} else if (target.kind === "Column") {
|
|
1063
|
+
return decision(decisionId("glossary.link", target.id), target.id, "assert", { glossaryTerm: term.id }, `${target.id} holds the glossary term ${term.term}`, o);
|
|
1064
|
+
}
|
|
1065
|
+
const fields = [.../* @__PURE__ */ new Set([...term.fields, recordId])].sort();
|
|
1066
|
+
return decision(decisionId("glossary.fields", term.id.slice("term.".length)), term.id, "assert", { fields }, `What the glossary term ${term.term} is linked to`, o);
|
|
1037
1067
|
}
|
|
1038
|
-
function
|
|
1039
|
-
const link = /* @__PURE__ */ new Map();
|
|
1040
|
-
for (const l of build.columnLinks)
|
|
1041
|
-
if (!link.has(l.column))
|
|
1042
|
-
link.set(l.column, l.term);
|
|
1068
|
+
async function writeDecisions(curatedDir, decisions) {
|
|
1043
1069
|
const out = [];
|
|
1044
|
-
for (const
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
out.push(r);
|
|
1070
|
+
for (const d of decisions) {
|
|
1071
|
+
const rel = recordPath(d);
|
|
1072
|
+
const full = join2(curatedDir, ...rel.split("/"));
|
|
1073
|
+
await mkdir2(dirname(full), { recursive: true });
|
|
1074
|
+
await writeFile2(full, serializeRecord(d), "utf8");
|
|
1075
|
+
out.push(rel);
|
|
1051
1076
|
}
|
|
1052
|
-
return
|
|
1077
|
+
return out;
|
|
1053
1078
|
}
|
|
1054
1079
|
|
|
1055
1080
|
// packages/glossary/dist/scan.js
|
|
1081
|
+
import { mkdir as mkdir3, readdir as readdir2, readFile as readFile2, rm as rm2, writeFile as writeFile3 } from "node:fs/promises";
|
|
1082
|
+
import { dirname as dirname2, join as join3, relative, sep } from "node:path";
|
|
1056
1083
|
async function readRecords(dir) {
|
|
1057
1084
|
const records = [];
|
|
1058
1085
|
const problems = [];
|
|
@@ -1174,16 +1201,16 @@ async function scanGlossary(root, o = {}) {
|
|
|
1174
1201
|
}
|
|
1175
1202
|
}
|
|
1176
1203
|
const before = new Map(g.base.map((r) => [`${r.kind}/${r.id}`, r]));
|
|
1177
|
-
const put = async (rel,
|
|
1204
|
+
const put = async (rel, text2) => {
|
|
1178
1205
|
const full = join3(generated, ...rel.split("/"));
|
|
1179
1206
|
await mkdir3(dirname2(full), { recursive: true });
|
|
1180
|
-
await writeFile3(full,
|
|
1207
|
+
await writeFile3(full, text2, "utf8");
|
|
1181
1208
|
written.push(rel);
|
|
1182
1209
|
};
|
|
1183
|
-
for (const [rel,
|
|
1210
|
+
for (const [rel, text2] of want) {
|
|
1184
1211
|
const cur = await readFile2(join3(generated, ...rel.split("/")), "utf8").catch(() => void 0);
|
|
1185
|
-
if (cur !==
|
|
1186
|
-
await put(rel,
|
|
1212
|
+
if (cur !== text2)
|
|
1213
|
+
await put(rel, text2);
|
|
1187
1214
|
}
|
|
1188
1215
|
const termIds = new Set(g.terms.map((t) => t.id));
|
|
1189
1216
|
for (const r of g.records) {
|
|
@@ -1221,6 +1248,49 @@ async function completeGlossaryDefinitions(root, run, o = {}) {
|
|
|
1221
1248
|
return { ...scanned, propose };
|
|
1222
1249
|
}
|
|
1223
1250
|
|
|
1251
|
+
// packages/scanner/dist/federate-loose.js
|
|
1252
|
+
function resolveFederatedLooseEnds(o) {
|
|
1253
|
+
const originOf = (r) => (r.flags ?? []).find((f) => f.startsWith("federated:"))?.slice("federated:".length);
|
|
1254
|
+
const loose = o.federated.filter((r) => r.kind === "Unresolved");
|
|
1255
|
+
if (!loose.length)
|
|
1256
|
+
return { federated: o.federated, resolved: /* @__PURE__ */ new Map() };
|
|
1257
|
+
const fedComponents = o.federated.filter((r) => r.kind === "Component");
|
|
1258
|
+
const result = o.merge([...o.raw, ...fedComponents, ...loose]);
|
|
1259
|
+
const have = new Set(o.local.map((r) => `${r.kind}/${r.id}`));
|
|
1260
|
+
const known = /* @__PURE__ */ new Set([
|
|
1261
|
+
...o.local.filter((r) => r.kind === "Component" || r.kind === "System").map((r) => r.id),
|
|
1262
|
+
...fedComponents.map((r) => r.id)
|
|
1263
|
+
]);
|
|
1264
|
+
const origin = new Map(fedComponents.map((c) => [c.id, originOf(c)]));
|
|
1265
|
+
const stillLoose = new Set(result.records.filter((r) => r.kind === "Unresolved").map((r) => r.id));
|
|
1266
|
+
const added = [];
|
|
1267
|
+
for (const r of result.records) {
|
|
1268
|
+
if (r.kind !== "Interface" || have.has(`Interface/${r.id}`))
|
|
1269
|
+
continue;
|
|
1270
|
+
const i = r;
|
|
1271
|
+
const from = origin.get(i.from) ?? origin.get(i.to);
|
|
1272
|
+
if (!from || !known.has(i.from) || !known.has(i.to))
|
|
1273
|
+
continue;
|
|
1274
|
+
added.push({ ...r, flags: [.../* @__PURE__ */ new Set([...r.flags ?? [], `federated:${from}`])] });
|
|
1275
|
+
}
|
|
1276
|
+
const key = (e) => JSON.stringify(e);
|
|
1277
|
+
const carried = new Set(added.flatMap((i) => i.evidence.map(key)));
|
|
1278
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
1279
|
+
const kept2 = o.federated.filter((r) => {
|
|
1280
|
+
if (r.kind !== "Unresolved" || stillLoose.has(r.id))
|
|
1281
|
+
return true;
|
|
1282
|
+
if (!r.evidence.some((e) => e.source !== "export" && carried.has(key(e))))
|
|
1283
|
+
return true;
|
|
1284
|
+
const id = originOf(r) ?? "";
|
|
1285
|
+
resolved.set(id, (resolved.get(id) ?? 0) + 1);
|
|
1286
|
+
return false;
|
|
1287
|
+
});
|
|
1288
|
+
return {
|
|
1289
|
+
federated: [...kept2, ...added].sort((a, b) => a.kind === b.kind ? cmpText(a.id, b.id) : cmpText(a.kind, b.kind)),
|
|
1290
|
+
resolved
|
|
1291
|
+
};
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1224
1294
|
// packages/scanner/dist/files.js
|
|
1225
1295
|
import { readdir as readdir3, readFile as readFile3, rm as rm3 } from "node:fs/promises";
|
|
1226
1296
|
import { join as join4, relative as relative2, resolve, sep as sep2 } from "node:path";
|
|
@@ -1259,7 +1329,7 @@ async function readCatalogFiles(dir, cacheRoot) {
|
|
|
1259
1329
|
} catch {
|
|
1260
1330
|
return;
|
|
1261
1331
|
}
|
|
1262
|
-
for (const e of entries.sort((a, b) => a.name
|
|
1332
|
+
for (const e of entries.sort((a, b) => cmpText(a.name, b.name))) {
|
|
1263
1333
|
const full = join4(d, e.name);
|
|
1264
1334
|
if (e.isDirectory())
|
|
1265
1335
|
await walk(full);
|
|
@@ -1274,8 +1344,8 @@ async function readCatalogFiles(dir, cacheRoot) {
|
|
|
1274
1344
|
name: "catalog",
|
|
1275
1345
|
version: CATALOG_CACHE_VERSION,
|
|
1276
1346
|
cacheRoot,
|
|
1277
|
-
parse: (
|
|
1278
|
-
const r = deserializeRecord(
|
|
1347
|
+
parse: (text2) => {
|
|
1348
|
+
const r = deserializeRecord(text2);
|
|
1279
1349
|
return r.ok ? { ok: true, value: r.record } : { ok: false, message: r.issues.map((i) => `${i.path}: ${i.message}`).join("; ") };
|
|
1280
1350
|
}
|
|
1281
1351
|
})) {
|
|
@@ -1284,7 +1354,7 @@ async function readCatalogFiles(dir, cacheRoot) {
|
|
|
1284
1354
|
else
|
|
1285
1355
|
problems.push({ file: p.rel, message: p.problem });
|
|
1286
1356
|
}
|
|
1287
|
-
records.sort((a, b) => a.kind === b.kind ? a.id
|
|
1357
|
+
records.sort((a, b) => a.kind === b.kind ? cmpText(a.id, b.id) : cmpText(a.kind, b.kind));
|
|
1288
1358
|
return { records, problems };
|
|
1289
1359
|
}
|
|
1290
1360
|
async function writeCatalog(dir, records) {
|
|
@@ -1292,14 +1362,14 @@ async function writeCatalog(dir, records) {
|
|
|
1292
1362
|
const wanted = new Map(records.map((r) => [recordPath(r), serializeRecord(r)]));
|
|
1293
1363
|
let written = 0;
|
|
1294
1364
|
let unchanged = 0;
|
|
1295
|
-
for (const [rel,
|
|
1365
|
+
for (const [rel, text2] of wanted) {
|
|
1296
1366
|
const full = join4(dir, ...rel.split("/"));
|
|
1297
1367
|
const current = await readFile3(full, "utf8").catch(() => void 0);
|
|
1298
|
-
if (current ===
|
|
1368
|
+
if (current?.replace(/\r\n?/g, "\n") === text2) {
|
|
1299
1369
|
unchanged++;
|
|
1300
1370
|
continue;
|
|
1301
1371
|
}
|
|
1302
|
-
await writeFileAtomic(full,
|
|
1372
|
+
await writeFileAtomic(full, text2);
|
|
1303
1373
|
written++;
|
|
1304
1374
|
}
|
|
1305
1375
|
let removed = 0;
|
|
@@ -1355,7 +1425,7 @@ function diffCatalogs(before, after) {
|
|
|
1355
1425
|
for (const [k, r] of a)
|
|
1356
1426
|
if (!b.has(k))
|
|
1357
1427
|
diff.removed.push({ kind: r.kind, id: r.id });
|
|
1358
|
-
const sort = (xs) => xs.sort((x, y) => x.kind === y.kind ? x.id
|
|
1428
|
+
const sort = (xs) => xs.sort((x, y) => x.kind === y.kind ? cmpText(x.id, y.id) : cmpText(x.kind, y.kind));
|
|
1359
1429
|
sort(diff.added);
|
|
1360
1430
|
sort(diff.removed);
|
|
1361
1431
|
sort(diff.changed);
|
|
@@ -1380,16 +1450,19 @@ function prototypeScanNotes(op, only) {
|
|
|
1380
1450
|
function isPrototypeRepo(op, repo) {
|
|
1381
1451
|
return estateRepos(op).some((r) => r.id === repo && isPrototypeType(r.type));
|
|
1382
1452
|
}
|
|
1383
|
-
function estateMergeInput(op, repos, records, modulesOf = (id) => repos.find((r) => r.id === id)?.modules ?? []) {
|
|
1453
|
+
function estateMergeInput(op, repos, records, modulesOf = (id) => repos.find((r) => r.id === id)?.modules ?? [], read) {
|
|
1384
1454
|
return {
|
|
1385
1455
|
records,
|
|
1456
|
+
// ADR 0180: the organisation's component types, so a cited type is never demoted in a merge.
|
|
1457
|
+
componentTypes: new Set(effectiveComponentTypes(op.componentTypes).map((t) => t.id)),
|
|
1386
1458
|
repos: repos.flatMap((r) => [
|
|
1387
1459
|
{
|
|
1388
1460
|
id: r.id,
|
|
1389
1461
|
type: r.type,
|
|
1390
1462
|
...r.component ? { component: r.component } : {},
|
|
1391
1463
|
...r.system ? { system: r.system } : {},
|
|
1392
|
-
hosts: r.hosts
|
|
1464
|
+
hosts: r.hosts,
|
|
1465
|
+
...read?.has(r.id) ? { read: true } : {}
|
|
1393
1466
|
},
|
|
1394
1467
|
// A module's hosts join calls to its own component (ADR 0057).
|
|
1395
1468
|
...modulesOf(r.id).filter((m) => m.hosts.length > 0).map((m) => ({
|
|
@@ -1405,17 +1478,348 @@ function estateMergeInput(op, repos, records, modulesOf = (id) => repos.find((r)
|
|
|
1405
1478
|
profilePath: op.profiles[0] ? `org-pack/profiles/${op.profiles[0].platform}/profile.yaml` : void 0
|
|
1406
1479
|
};
|
|
1407
1480
|
}
|
|
1408
|
-
function mergeEstate(op, repos, records, modulesOf) {
|
|
1409
|
-
return mergeCatalog(estateMergeInput(op, repos, records, modulesOf));
|
|
1481
|
+
function mergeEstate(op, repos, records, modulesOf, read) {
|
|
1482
|
+
return mergeCatalog(estateMergeInput(op, repos, records, modulesOf, read));
|
|
1410
1483
|
}
|
|
1411
1484
|
function curatedDirOf(root, op) {
|
|
1412
1485
|
return join5(root, ...op.sources.layout.catalog.split("/"), "curated");
|
|
1413
1486
|
}
|
|
1414
1487
|
|
|
1488
|
+
// packages/scanner/dist/facts-scan.js
|
|
1489
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
1490
|
+
import { join as join6 } from "node:path";
|
|
1491
|
+
|
|
1492
|
+
// packages/scanner/dist/facts-catalog.js
|
|
1493
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
1494
|
+
|
|
1495
|
+
// packages/scanner/dist/target-suggest.js
|
|
1496
|
+
var TARGETS_MAX_SUGGESTED = 5;
|
|
1497
|
+
var TARGETS_ANOTHER = TARGETS_ANOTHER_LABEL;
|
|
1498
|
+
var TARGETS_NO_EVIDENCE = "no evidence; listed so you can pick it";
|
|
1499
|
+
var STOP = new Set("url uri base host client clients service services svc api apis http https rest endpoint endpoints impl java kt ts js py cs go src main test get post put delete patch the and for com www internal example config configs properties yaml yml env app apps application new call calls feign template web request requests response gateway proxy remote adapter port".split(" "));
|
|
1500
|
+
function nameWords(s) {
|
|
1501
|
+
if (!s)
|
|
1502
|
+
return [];
|
|
1503
|
+
return [
|
|
1504
|
+
...new Set(s.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/).filter((w) => w.length >= 3 && !STOP.has(w) && !/^v?\d+$/.test(w)).map((w) => w.length > 4 && w.endsWith("s") && !w.endsWith("ss") ? w.slice(0, -1) : w))
|
|
1505
|
+
];
|
|
1506
|
+
}
|
|
1507
|
+
var segs = (p) => p.split(/[?#]/)[0].split("/").filter(Boolean).map((x) => /^[{:<$]/.test(x) ? "{}" : x);
|
|
1508
|
+
var sameSeg = (a, b) => a === "{}" || b === "{}" || a === b;
|
|
1509
|
+
var under = (path, prefix) => {
|
|
1510
|
+
const a = segs(prefix);
|
|
1511
|
+
const b = segs(path);
|
|
1512
|
+
return a.length > 0 && a.length <= b.length && a.every((x, i) => x === b[i]);
|
|
1513
|
+
};
|
|
1514
|
+
function serves(entry, call, bases) {
|
|
1515
|
+
const e = segs(entry);
|
|
1516
|
+
const tries = [
|
|
1517
|
+
segs(call),
|
|
1518
|
+
...bases.filter((b) => under(call, b)).map((b) => segs(call).slice(segs(b).length))
|
|
1519
|
+
];
|
|
1520
|
+
return tries.some((c) => c.length === e.length && c.every((x, i) => sameSeg(x, e[i])));
|
|
1521
|
+
}
|
|
1522
|
+
function clientOf(f) {
|
|
1523
|
+
const q = f.cite.quote ?? "";
|
|
1524
|
+
const named2 = /\b([A-Z][A-Za-z0-9]*(?:Client|Service|Api|Feign|Gateway|Proxy))\b/.exec(q)?.[1];
|
|
1525
|
+
if (named2)
|
|
1526
|
+
return named2;
|
|
1527
|
+
const file = f.cite.file.split("/").pop().replace(/\.[^.]+$/, "");
|
|
1528
|
+
return /(Client|Service|Api|Feign|Gateway|Proxy)$/.test(file) ? file : void 0;
|
|
1529
|
+
}
|
|
1530
|
+
function hostOf(f, facts) {
|
|
1531
|
+
const fromUrl = (u) => /^[a-z][a-z0-9+.-]*:\/\/([^/:?#]+)/i.exec(u)?.[1];
|
|
1532
|
+
if (f.host)
|
|
1533
|
+
return fromUrl(f.host) ?? f.host.split(/[/:]/)[0];
|
|
1534
|
+
if (f.base) {
|
|
1535
|
+
const cfg = facts.find((x) => x.kind === "config" && x.key === f.base && !!x.value);
|
|
1536
|
+
if (!cfg?.value)
|
|
1537
|
+
return void 0;
|
|
1538
|
+
return fromUrl(cfg.value) ?? (/^[a-z0-9-]+(\.[a-z0-9-]+)+(:\d+)?$/i.test(cfg.value) ? cfg.value.split(":")[0] : void 0);
|
|
1539
|
+
}
|
|
1540
|
+
return void 0;
|
|
1541
|
+
}
|
|
1542
|
+
var answerLabel = (q) => q.answer?.option !== void 0 ? q.options[q.answer.option - 1]?.label : q.answer?.text;
|
|
1543
|
+
var NOT_A_TARGET = /* @__PURE__ */ new Set(["None of these", TARGETS_SPLIT_LABEL, TARGETS_ANOTHER_LABEL]);
|
|
1544
|
+
function candidates(callerRepo, ctx) {
|
|
1545
|
+
const manifest = estateRepos(ctx.op).filter((r) => r.id !== callerRepo || !!ctx.only);
|
|
1546
|
+
const systems = ctx.op.profiles.flatMap((p) => p.profile?.systems ?? []);
|
|
1547
|
+
const reposOfSystem = /* @__PURE__ */ new Map();
|
|
1548
|
+
for (const r of estateRepos(ctx.op))
|
|
1549
|
+
if (r.system)
|
|
1550
|
+
reposOfSystem.set(r.system, (reposOfSystem.get(r.system) ?? 0) + 1);
|
|
1551
|
+
const read = new Set((ctx.estate ?? []).filter((x) => x.facts.length > 0).map((x) => x.repo));
|
|
1552
|
+
const repoOf = /* @__PURE__ */ new Map();
|
|
1553
|
+
for (const r of ctx.records)
|
|
1554
|
+
if (r.kind === "Component" && r.repo)
|
|
1555
|
+
repoOf.set(r.id, r.repo);
|
|
1556
|
+
if (!ctx.estate)
|
|
1557
|
+
for (const repo of repoOf.values())
|
|
1558
|
+
read.add(repo);
|
|
1559
|
+
const endpoints = ctx.records.filter((r) => r.kind === "Endpoint");
|
|
1560
|
+
const deploys = (ctx.estate ?? []).flatMap((x) => x.facts.filter((f) => f.kind === "deploy" && !!f.basePath));
|
|
1561
|
+
const out = [];
|
|
1562
|
+
for (const m of manifest) {
|
|
1563
|
+
const own2 = new Set([m.id, m.component, ...m.modules.map((x) => x.component)].filter((x) => !!x).map(factIdPart));
|
|
1564
|
+
const sys = m.system && reposOfSystem.get(m.system) === 1 ? systems.find((s) => s.id === m.system) : void 0;
|
|
1565
|
+
const names = /* @__PURE__ */ new Map();
|
|
1566
|
+
for (const n of [m.id, m.component, ...m.modules.map((x) => x.component)])
|
|
1567
|
+
for (const w of nameWords(n))
|
|
1568
|
+
if (!names.has(w))
|
|
1569
|
+
names.set(w, n);
|
|
1570
|
+
for (const a of sys ? [sys.id, ...sys.aliases] : [])
|
|
1571
|
+
for (const w of nameWords(a))
|
|
1572
|
+
if (!names.has(w))
|
|
1573
|
+
names.set(w, a);
|
|
1574
|
+
const bases = [];
|
|
1575
|
+
if (m.basePath)
|
|
1576
|
+
bases.push({ path: m.basePath, from: "the manifest's basePath" });
|
|
1577
|
+
for (const x of m.modules)
|
|
1578
|
+
if (x.basePath)
|
|
1579
|
+
bases.push({ path: x.basePath, from: `the basePath of its module ${x.component}` });
|
|
1580
|
+
for (const d of deploys) {
|
|
1581
|
+
const reaches = d.exposes ? factIdPart(d.exposes) : d.repo === m.id ? m.id : void 0;
|
|
1582
|
+
if (!reaches || !(own2.has(reaches) || d.repo === m.id && !d.exposes))
|
|
1583
|
+
continue;
|
|
1584
|
+
const what2 = d.platform === "apigee" ? "its proxy's BasePath" : `the ${d.resourceType ?? "route"} ${d.resourceName ?? d.id}`;
|
|
1585
|
+
bases.push({ path: d.basePath, from: `${what2} (${d.repo} ${d.cite.file}:${d.cite.line})` });
|
|
1586
|
+
}
|
|
1587
|
+
const comps = [
|
|
1588
|
+
...new Set(endpoints.filter((e) => repoOf.get(e.component) === m.id).map((e) => e.component))
|
|
1589
|
+
].sort();
|
|
1590
|
+
const isRead = read.has(m.id);
|
|
1591
|
+
const forLabel = comps.length ? comps : [m.id];
|
|
1592
|
+
for (const label2 of forLabel) {
|
|
1593
|
+
const mine = endpoints.filter((e) => e.component === label2);
|
|
1594
|
+
const entries = (ctx.estate ?? []).filter((x) => x.repo === m.id).flatMap((x) => x.facts.filter((f) => f.kind === "entry" && (!comps.length || factIdPart(f.component ?? m.component ?? m.id) === label2 || comps.length === 1))).map((f) => ({
|
|
1595
|
+
...f.method ? { method: f.method.toUpperCase() } : {},
|
|
1596
|
+
path: f.path,
|
|
1597
|
+
at: `${f.cite.file}:${f.cite.line}`
|
|
1598
|
+
}));
|
|
1599
|
+
for (const e of mine)
|
|
1600
|
+
if (!entries.some((x) => x.path === e.pathOrTopic))
|
|
1601
|
+
entries.push({
|
|
1602
|
+
...e.method ? { method: e.method.toUpperCase() } : {},
|
|
1603
|
+
path: e.pathOrTopic,
|
|
1604
|
+
at: e.id
|
|
1605
|
+
});
|
|
1606
|
+
out.push({
|
|
1607
|
+
s: { label: label2, kind: "repo", repo: m.id, read: isRead, score: 0, why: [] },
|
|
1608
|
+
names,
|
|
1609
|
+
bases,
|
|
1610
|
+
entries,
|
|
1611
|
+
...m.description?.text ? { description: m.description.text } : {},
|
|
1612
|
+
hosts: m.hosts.map((h) => h.toLowerCase())
|
|
1613
|
+
});
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
for (const c of ctx.only ?? [])
|
|
1617
|
+
if (!out.some((x) => x.s.label === c))
|
|
1618
|
+
out.push({
|
|
1619
|
+
s: {
|
|
1620
|
+
label: c,
|
|
1621
|
+
kind: "repo",
|
|
1622
|
+
repo: repoOf.get(c) ?? callerRepo,
|
|
1623
|
+
read: true,
|
|
1624
|
+
score: 0,
|
|
1625
|
+
why: []
|
|
1626
|
+
},
|
|
1627
|
+
names: /* @__PURE__ */ new Map(),
|
|
1628
|
+
bases: [],
|
|
1629
|
+
entries: [],
|
|
1630
|
+
hosts: []
|
|
1631
|
+
});
|
|
1632
|
+
const own = new Set(estateRepos(ctx.op).flatMap((r) => r.system ? [r.system] : []));
|
|
1633
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1634
|
+
for (const s of systems) {
|
|
1635
|
+
if (!(s.boundary === "vendor" || !own.has(s.id)) || seen.has(s.id))
|
|
1636
|
+
continue;
|
|
1637
|
+
seen.add(s.id);
|
|
1638
|
+
const names = /* @__PURE__ */ new Map();
|
|
1639
|
+
for (const n of [s.id, s.name, ...s.aliases])
|
|
1640
|
+
for (const w of nameWords(n))
|
|
1641
|
+
if (!names.has(w))
|
|
1642
|
+
names.set(w, n);
|
|
1643
|
+
out.push({
|
|
1644
|
+
s: {
|
|
1645
|
+
label: s.id,
|
|
1646
|
+
kind: "outside",
|
|
1647
|
+
read: false,
|
|
1648
|
+
name: s.name,
|
|
1649
|
+
boundary: s.boundary,
|
|
1650
|
+
score: 0,
|
|
1651
|
+
why: []
|
|
1652
|
+
},
|
|
1653
|
+
names,
|
|
1654
|
+
bases: [],
|
|
1655
|
+
entries: [],
|
|
1656
|
+
hosts: s.aliases.map((a) => a.toLowerCase())
|
|
1657
|
+
});
|
|
1658
|
+
}
|
|
1659
|
+
return out;
|
|
1660
|
+
}
|
|
1661
|
+
function targetSuggestions(f, ctx) {
|
|
1662
|
+
const all = candidates(f.repo, ctx);
|
|
1663
|
+
const df = /* @__PURE__ */ new Map();
|
|
1664
|
+
for (const c of all)
|
|
1665
|
+
for (const w of c.names.keys())
|
|
1666
|
+
df.set(w, (df.get(w) ?? 0) + 1);
|
|
1667
|
+
const telling = (w) => (df.get(w) ?? 0) <= Math.max(2, Math.floor(all.length / 3));
|
|
1668
|
+
const host = hostOf(f, ctx.facts)?.toLowerCase();
|
|
1669
|
+
const client = clientOf(f);
|
|
1670
|
+
const said = [
|
|
1671
|
+
{ from: f.base ? `the config key ${f.base}` : "", words: nameWords(f.base), weight: 4 },
|
|
1672
|
+
{ from: client ? `the client ${client}` : "", words: nameWords(client), weight: 4 },
|
|
1673
|
+
{
|
|
1674
|
+
from: host ? `the host label ${host.split(".")[0]} (${host})` : "",
|
|
1675
|
+
words: host ? nameWords(host.split(".")[0]) : [],
|
|
1676
|
+
weight: 4
|
|
1677
|
+
},
|
|
1678
|
+
{
|
|
1679
|
+
from: `the path ${f.path}`,
|
|
1680
|
+
words: nameWords(segs(f.path).filter((x) => x !== "{}").join(" ")),
|
|
1681
|
+
weight: 2
|
|
1682
|
+
}
|
|
1683
|
+
].filter((x) => x.from && x.words.length);
|
|
1684
|
+
const method = f.method?.toUpperCase();
|
|
1685
|
+
const earlier = (ctx.questions ?? []).filter((q) => q.askedBy?.step === TARGETS_STEP_ID && q.state === "answered" && q.repo === f.repo && !q.evidence.includes(`${f.cite.file}:${f.cite.line}`));
|
|
1686
|
+
const only = ctx.only ? new Set(ctx.only) : void 0;
|
|
1687
|
+
const out = [];
|
|
1688
|
+
for (const c of all) {
|
|
1689
|
+
if (only && c.s.kind === "repo" && !only.has(c.s.label))
|
|
1690
|
+
continue;
|
|
1691
|
+
const s = { ...c.s, why: [] };
|
|
1692
|
+
const add = (n, why) => {
|
|
1693
|
+
s.score += n;
|
|
1694
|
+
s.why.push(why);
|
|
1695
|
+
};
|
|
1696
|
+
if (only?.has(c.s.label))
|
|
1697
|
+
add(6, "the join found an entry of it that serves the call");
|
|
1698
|
+
const hit = c.entries.find((e) => (!method || !e.method || e.method === method || e.method === "ANY") && serves(e.path, f.path, c.bases.map((b) => b.path)));
|
|
1699
|
+
if (hit && !only) {
|
|
1700
|
+
const what2 = [hit.method, hit.path].filter(Boolean).join(" ");
|
|
1701
|
+
add(6, `serves ${what2} (${hit.at})`);
|
|
1702
|
+
s.serves = [what2];
|
|
1703
|
+
}
|
|
1704
|
+
const base2 = c.bases.find((b) => under(f.path, b.path));
|
|
1705
|
+
if (base2)
|
|
1706
|
+
add(5, `its basePath ${base2.path} matches: ${base2.from}`);
|
|
1707
|
+
if (host && c.hosts.includes(host))
|
|
1708
|
+
add(8, `it lists ${host} under hosts`);
|
|
1709
|
+
for (const x of said) {
|
|
1710
|
+
const w = x.words.find((y) => c.names.has(y) && telling(y));
|
|
1711
|
+
if (w)
|
|
1712
|
+
add(x.weight, `named like ${x.from}`);
|
|
1713
|
+
}
|
|
1714
|
+
if (c.description) {
|
|
1715
|
+
const d = new Set(nameWords(c.description));
|
|
1716
|
+
const w = said.flatMap((x) => x.words).find((y) => d.has(y) && telling(y));
|
|
1717
|
+
if (w)
|
|
1718
|
+
add(2, `its description mentions ${w}`);
|
|
1719
|
+
}
|
|
1720
|
+
const named2 = earlier.filter((q) => {
|
|
1721
|
+
const l = answerLabel(q);
|
|
1722
|
+
if (!l || NOT_A_TARGET.has(l))
|
|
1723
|
+
return false;
|
|
1724
|
+
const t = slugify(l);
|
|
1725
|
+
return t === slugify(c.s.label) || !!c.s.repo && t === slugify(c.s.repo);
|
|
1726
|
+
}).length;
|
|
1727
|
+
if (named2)
|
|
1728
|
+
add(3, `picked for ${named2} other ${named2 === 1 ? "call" : "calls"} of ${f.repo}`);
|
|
1729
|
+
if (!s.score)
|
|
1730
|
+
s.why.push(TARGETS_NO_EVIDENCE);
|
|
1731
|
+
s.why = [...new Set(s.why)];
|
|
1732
|
+
out.push(s);
|
|
1733
|
+
}
|
|
1734
|
+
return out.sort((a, b) => b.score - a.score || Number(b.read) - Number(a.read) || (a.kind === b.kind ? 0 : a.kind === "repo" ? -1 : 1) || cmpText(a.label, b.label));
|
|
1735
|
+
}
|
|
1736
|
+
function offeredSuggestions(ranked, o) {
|
|
1737
|
+
const max = o.max ?? TARGETS_MAX_SUGGESTED;
|
|
1738
|
+
const strong = ranked.filter((s) => s.score >= 2);
|
|
1739
|
+
const fill = ranked.filter((s) => s.score < 2 && (s.kind === "outside" || s.read && o.serving.has(s.label)));
|
|
1740
|
+
return [...strong, ...fill].slice(0, max);
|
|
1741
|
+
}
|
|
1742
|
+
function targetPicker(op, read = /* @__PURE__ */ new Set()) {
|
|
1743
|
+
const repos = estateRepos(op).map((r) => ({
|
|
1744
|
+
id: r.id,
|
|
1745
|
+
kind: "repo",
|
|
1746
|
+
name: r.component && r.component !== r.id ? `${r.id} (${r.component})` : r.id,
|
|
1747
|
+
read: read.has(r.id)
|
|
1748
|
+
})).sort((a, b) => cmpText(a.id, b.id));
|
|
1749
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1750
|
+
const systems = op.profiles.flatMap((p) => p.profile?.systems ?? []).filter((s) => !seen.has(s.id) && !!seen.add(s.id)).map((s) => ({ id: s.id, kind: "system", name: s.name, boundary: s.boundary })).sort((a, b) => cmpText(a.id, b.id));
|
|
1751
|
+
const owned = new Set(op.profiles.flatMap((p) => (p.profile?.systems ?? []).flatMap((s) => s.owner ? [s.owner] : [])));
|
|
1752
|
+
const teams = (op.roles?.teams ?? []).map((t) => {
|
|
1753
|
+
const name = t.name ?? t.id;
|
|
1754
|
+
const has = owned.has(t.id);
|
|
1755
|
+
return {
|
|
1756
|
+
id: t.id,
|
|
1757
|
+
kind: "team",
|
|
1758
|
+
name,
|
|
1759
|
+
...has ? {} : {
|
|
1760
|
+
adds: {
|
|
1761
|
+
id: slugify(`${t.id}-system`),
|
|
1762
|
+
name: `${name}'s system`,
|
|
1763
|
+
boundary: "internal",
|
|
1764
|
+
owner: t.id
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
};
|
|
1768
|
+
}).sort((a, b) => cmpText(a.id, b.id));
|
|
1769
|
+
return [...repos, ...systems, ...teams];
|
|
1770
|
+
}
|
|
1771
|
+
function pickerLines(picks) {
|
|
1772
|
+
const of = (k) => picks.filter((p) => p.kind === k);
|
|
1773
|
+
return [
|
|
1774
|
+
`Repos (${of("repo").length}): ${of("repo").map((p) => `${p.id}${p.read ? "" : " (not read yet)"}`).join(", ") || "none"}`,
|
|
1775
|
+
`Systems (${of("system").length}): ${of("system").map((p) => `${p.id} (${p.boundary})`).join(", ") || "none"}`,
|
|
1776
|
+
`Teams (${of("team").length}): ${of("team").map((p) => `${p.id}${p.adds ? " (no system yet)" : ""}`).join(", ") || "none"}`
|
|
1777
|
+
];
|
|
1778
|
+
}
|
|
1779
|
+
function topicQuestionDrafts(repo, records) {
|
|
1780
|
+
const out = /* @__PURE__ */ new Map();
|
|
1781
|
+
for (const r of records) {
|
|
1782
|
+
if (r.kind !== "Unresolved")
|
|
1783
|
+
continue;
|
|
1784
|
+
const x = r;
|
|
1785
|
+
if (x.unresolvedKind !== "event-publish" && x.unresolvedKind !== "event-subscribe")
|
|
1786
|
+
continue;
|
|
1787
|
+
const topic = String(x.raw);
|
|
1788
|
+
if (!topic || topic.includes("${") || /\s/.test(topic))
|
|
1789
|
+
continue;
|
|
1790
|
+
const ev = x.evidence.filter((e) => e.repo === repo && e.path && e.line && !/[\s:]/.test(e.path)).map((e) => `${e.path}:${e.line}`).sort();
|
|
1791
|
+
if (!ev.length)
|
|
1792
|
+
continue;
|
|
1793
|
+
const side = x.unresolvedKind === "event-publish" ? "publish" : "listen";
|
|
1794
|
+
const question = topicQuestionText(topic, x.owner ?? repo, side);
|
|
1795
|
+
const draft = {
|
|
1796
|
+
question,
|
|
1797
|
+
options: [
|
|
1798
|
+
{
|
|
1799
|
+
label: TOPICS_OUTSIDE_LABEL,
|
|
1800
|
+
means: `${topic} belongs to another team's system or a vendor's: name it, or the team, from the list`,
|
|
1801
|
+
changes: `${topic} is added under that system's topics in the profile (or the system is added, owner the team), as a file change for review; the next write ${side === "publish" ? "sends the publish to it" : "takes the listener's messages from it"}, marked as your word`
|
|
1802
|
+
},
|
|
1803
|
+
{
|
|
1804
|
+
label: "None of these",
|
|
1805
|
+
means: `Nothing outside the estate owns ${topic}, or a repo not read yet serves it`,
|
|
1806
|
+
changes: `It stays a loose end, and this isn't asked again`
|
|
1807
|
+
}
|
|
1808
|
+
],
|
|
1809
|
+
evidence: [...new Set(ev)],
|
|
1810
|
+
scope: "repo"
|
|
1811
|
+
};
|
|
1812
|
+
const id = questionId({ step: TOPICS_STEP_ID, repo, question, evidence: draft.evidence });
|
|
1813
|
+
if (!out.has(id))
|
|
1814
|
+
out.set(id, { topic, id, draft });
|
|
1815
|
+
}
|
|
1816
|
+
return [...out.values()].sort((a, b) => cmpText(a.topic, b.topic));
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1415
1819
|
// packages/scanner/dist/facts-catalog.js
|
|
1416
|
-
var TARGETS_STEP =
|
|
1820
|
+
var TARGETS_STEP = TARGETS_STEP_ID;
|
|
1417
1821
|
var TARGETS_NONE = "None of these";
|
|
1418
|
-
var byKindId = (a, b) => a.kind === b.kind ? a.id
|
|
1822
|
+
var byKindId = (a, b) => a.kind === b.kind ? cmpText(a.id, b.id) : cmpText(a.kind, b.kind);
|
|
1419
1823
|
var URL_RE = /^[a-z][a-z0-9+.-]*:\/\//i;
|
|
1420
1824
|
var joinUrl = (base2, path) => `${base2.replace(/\/+$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
|
|
1421
1825
|
function urlOf(f, facts) {
|
|
@@ -1433,6 +1837,42 @@ var EVENT_CALLS = /* @__PURE__ */ new Set(["event-topic", "mq-queue"]);
|
|
|
1433
1837
|
function needsTarget(f, facts) {
|
|
1434
1838
|
return f.kind === "call" && !f.to && !EVENT_CALLS.has(f.callType) && !(HTTP_CALLS.has(f.callType) && urlOf(f, facts));
|
|
1435
1839
|
}
|
|
1840
|
+
var UNRESOLVED_HOST = /@[A-Za-z_][A-Za-z0-9_.-]*@|\$\{|\{[^}]*\}/;
|
|
1841
|
+
function ruleMayName(f, facts) {
|
|
1842
|
+
if (f.kind !== "call" || EVENT_CALLS.has(f.callType))
|
|
1843
|
+
return false;
|
|
1844
|
+
if (!f.to && !(HTTP_CALLS.has(f.callType) && urlOf(f, facts)))
|
|
1845
|
+
return true;
|
|
1846
|
+
if (f.to)
|
|
1847
|
+
return f.by !== "person" && UNRESOLVED_HOST.test(f.to);
|
|
1848
|
+
if (!HTTP_CALLS.has(f.callType))
|
|
1849
|
+
return false;
|
|
1850
|
+
const url = urlOf(f, facts);
|
|
1851
|
+
const host = url ? /^[a-z][a-z0-9+.-]*:\/\/([^/?#]*)/i.exec(url)?.[1] ?? "" : "";
|
|
1852
|
+
if (!host || UNRESOLVED_HOST.test(host) || UNRESOLVED_HOST.test(f.host ?? ""))
|
|
1853
|
+
return true;
|
|
1854
|
+
if (f.host || !f.base)
|
|
1855
|
+
return false;
|
|
1856
|
+
const hosts = new Set(facts.filter((x) => x.kind === "config" && x.key === f.base && !!x.value).map((x) => URL_RE.test(x.value) ? urlHostPort(x.value)?.host : x.value.trim()).filter((h) => !!h));
|
|
1857
|
+
return hosts.size > 1;
|
|
1858
|
+
}
|
|
1859
|
+
var MAX_ID = 120;
|
|
1860
|
+
function remapIds(remap) {
|
|
1861
|
+
return (id) => {
|
|
1862
|
+
const exact = remap.get(id);
|
|
1863
|
+
if (exact)
|
|
1864
|
+
return exact;
|
|
1865
|
+
for (const [from, to] of remap)
|
|
1866
|
+
if (id.startsWith(`${from}.`)) {
|
|
1867
|
+
const out = to + id.slice(from.length);
|
|
1868
|
+
if (out.length <= MAX_ID)
|
|
1869
|
+
return out;
|
|
1870
|
+
const hash = createHash2("sha256").update(out, "utf8").digest("hex").slice(0, 16);
|
|
1871
|
+
return `${out.slice(0, MAX_ID - hash.length - 1).replace(/[-._]+$/, "")}.${hash}`;
|
|
1872
|
+
}
|
|
1873
|
+
return id;
|
|
1874
|
+
};
|
|
1875
|
+
}
|
|
1436
1876
|
function targetQuestionFor(f) {
|
|
1437
1877
|
if (f.kind !== "call")
|
|
1438
1878
|
return void 0;
|
|
@@ -1449,81 +1889,425 @@ var SERVES = {
|
|
|
1449
1889
|
webhook: ["webhook"],
|
|
1450
1890
|
sftp: ["file-drop"]
|
|
1451
1891
|
};
|
|
1452
|
-
function
|
|
1892
|
+
function targetAsks(facts, records, asks) {
|
|
1453
1893
|
const repoOf = /* @__PURE__ */ new Map();
|
|
1454
1894
|
for (const r of records)
|
|
1455
1895
|
if (r.kind === "Component" && r.repo)
|
|
1456
1896
|
repoOf.set(r.id, r.repo);
|
|
1457
|
-
const
|
|
1897
|
+
const list = [];
|
|
1898
|
+
for (const f of [...facts].sort((a, b) => cmpText(a.id, b.id))) {
|
|
1899
|
+
const ask = asks?.find((a) => a.reason === "ambiguous" && a.call.repo === f.repo && a.call.fact === f.id);
|
|
1900
|
+
if (f.kind !== "call" || !needsTarget(f, facts) && !ask)
|
|
1901
|
+
continue;
|
|
1902
|
+
const own = slugify(f.component ?? f.repo).replace(/\./g, "-");
|
|
1903
|
+
const types = new Set(SERVES[f.callType] ?? []);
|
|
1904
|
+
const servers = ask ? [...new Set(ask.candidates.map((c) => c.component))].sort() : [
|
|
1905
|
+
...new Set(records.filter((r) => r.kind === "Endpoint" && types.has(r.endpointType) && r.component !== own).filter((r) => repoOf.get(r.component) !== f.repo).map((r) => r.component))
|
|
1906
|
+
].sort();
|
|
1907
|
+
list.push({ f, q: targetQuestionFor(f), servers });
|
|
1908
|
+
}
|
|
1909
|
+
return { list, repoOf };
|
|
1910
|
+
}
|
|
1911
|
+
var outsideAs = (s) => s.boundary === "vendor" ? "an outside system" : "another team's system";
|
|
1912
|
+
var callWhat = (f) => [f.method?.toUpperCase(), f.path].filter(Boolean).join(" ");
|
|
1913
|
+
function suggestionWhy(s, prefix) {
|
|
1914
|
+
const served = s.serves ?? [];
|
|
1915
|
+
let why = s.why;
|
|
1916
|
+
if (prefix !== void 0 && served.length > 1) {
|
|
1917
|
+
const under2 = prefix === "/" ? "" : ` under ${prefix}`;
|
|
1918
|
+
why = [
|
|
1919
|
+
`serves ${served[0]} and ${served.length - 1} more${under2}`,
|
|
1920
|
+
...why.filter((w) => !w.startsWith("serves "))
|
|
1921
|
+
];
|
|
1922
|
+
}
|
|
1923
|
+
return (why.length ? why : [TARGETS_NO_EVIDENCE]).slice(0, 3).join("; ");
|
|
1924
|
+
}
|
|
1925
|
+
function suggestionOption(s, callerRepo, n, prefix) {
|
|
1926
|
+
const why = suggestionWhy(s, prefix);
|
|
1927
|
+
if (s.kind === "outside")
|
|
1928
|
+
return {
|
|
1929
|
+
label: s.label,
|
|
1930
|
+
means: `${s.name}, ${outsideAs({ boundary: s.boundary ?? "vendor" })}: ${why}`,
|
|
1931
|
+
changes: n ? `The ${n} calls go to ${s.name} when the facts are next written` : `The call goes to ${s.name} when the facts are next written`
|
|
1932
|
+
};
|
|
1933
|
+
const who = s.label === s.repo ? `${s.repo}${s.read ? "" : " (not read yet)"}` : `${s.label}, built in ${s.repo}${s.read ? "" : " (not read yet)"}`;
|
|
1934
|
+
const them = n ? `The ${n} calls` : "The call";
|
|
1935
|
+
return {
|
|
1936
|
+
label: s.label,
|
|
1937
|
+
means: `${who}: ${why}`,
|
|
1938
|
+
changes: s.read ? `${them} ${n ? "join" : "joins"} ${s.label} when the facts are next written (atticus verify ${callerRepo} --write)` : `${them} ${n ? "go" : "goes"} to ${s.repo} when the facts are next written (atticus verify ${callerRepo} --write), and ${n ? "join" : "joins"} its route once ${s.repo} is read and verified (/atticus-map ${s.repo}); if no route of it serves ${n ? "them" : "it"}, the question opens again`
|
|
1939
|
+
};
|
|
1940
|
+
}
|
|
1941
|
+
var anotherOption = (op) => {
|
|
1942
|
+
const repos = estateRepos(op).length;
|
|
1943
|
+
const systems = new Set(op.profiles.flatMap((p) => (p.profile?.systems ?? []).map((s) => s.id))).size;
|
|
1944
|
+
const teams = op.roles?.teams?.length ?? 0;
|
|
1945
|
+
return {
|
|
1946
|
+
label: TARGETS_ANOTHER,
|
|
1947
|
+
means: `A repo, system or team not listed here: the manifest's ${repos} repos (read or not), the profiles' ${systems} systems and the ${teams} teams in roles.yaml`,
|
|
1948
|
+
changes: "Nothing until you name one: atticus questions answer <id> --target <repo, system or team id> lists them and takes the one you name (the app's Questions page lists them too); naming a vendor or a team with no system adds that system to the profile for review"
|
|
1949
|
+
};
|
|
1950
|
+
};
|
|
1951
|
+
function offered(members, facts, records, op, asks, ctx) {
|
|
1952
|
+
const merged = /* @__PURE__ */ new Map();
|
|
1953
|
+
for (const m of members) {
|
|
1954
|
+
const ambiguous = asks?.some((a) => a.reason === "ambiguous" && a.call.repo === m.f.repo && a.call.fact === m.f.id);
|
|
1955
|
+
for (const s of targetSuggestions(m.f, {
|
|
1956
|
+
op,
|
|
1957
|
+
records,
|
|
1958
|
+
facts,
|
|
1959
|
+
...ctx.estate ? { estate: ctx.estate } : {},
|
|
1960
|
+
...ctx.questions ? { questions: ctx.questions } : {},
|
|
1961
|
+
...ambiguous ? { only: m.servers } : {}
|
|
1962
|
+
})) {
|
|
1963
|
+
const x = merged.get(s.label);
|
|
1964
|
+
if (!x)
|
|
1965
|
+
merged.set(s.label, { ...s, why: [...s.why], serves: [...s.serves ?? []] });
|
|
1966
|
+
else {
|
|
1967
|
+
x.score += s.score;
|
|
1968
|
+
for (const w of s.why)
|
|
1969
|
+
if (!x.why.includes(w))
|
|
1970
|
+
x.why.push(w);
|
|
1971
|
+
for (const w of s.serves ?? [])
|
|
1972
|
+
if (!x.serves.includes(w))
|
|
1973
|
+
x.serves.push(w);
|
|
1974
|
+
if (x.why.length > 1)
|
|
1975
|
+
x.why = x.why.filter((w) => w !== TARGETS_NO_EVIDENCE);
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
const ranked = [...merged.values()].sort((a, b) => b.score - a.score || Number(b.read) - Number(a.read) || (a.kind === b.kind ? 0 : a.kind === "repo" ? -1 : 1) || cmpText(a.label, b.label));
|
|
1980
|
+
const serving = new Set(members.flatMap((m) => m.servers));
|
|
1981
|
+
const types = new Set(members.flatMap((m) => SERVES[m.f.callType] ?? []));
|
|
1982
|
+
return offeredSuggestions(ranked, { types, serving });
|
|
1983
|
+
}
|
|
1984
|
+
function targetQuestionDrafts(facts, records, op, asks, ctx = {}) {
|
|
1985
|
+
const { list } = targetAsks(facts, records, asks);
|
|
1986
|
+
return list.map((m) => {
|
|
1987
|
+
const { f, q } = m;
|
|
1988
|
+
const draft = {
|
|
1989
|
+
question: q.question,
|
|
1990
|
+
options: [
|
|
1991
|
+
...offered([m], facts, records, op, asks, ctx).map((s) => suggestionOption(s, f.repo)),
|
|
1992
|
+
anotherOption(op),
|
|
1993
|
+
{
|
|
1994
|
+
label: TARGETS_NONE,
|
|
1995
|
+
means: "Nothing in the estate or the org pack's systems answers it",
|
|
1996
|
+
changes: "The call stays a loose end, and this isn't asked again"
|
|
1997
|
+
}
|
|
1998
|
+
],
|
|
1999
|
+
evidence: q.evidence,
|
|
2000
|
+
scope: "repo"
|
|
2001
|
+
};
|
|
2002
|
+
return {
|
|
2003
|
+
factId: f.id,
|
|
2004
|
+
id: questionId({
|
|
2005
|
+
step: TARGETS_STEP,
|
|
2006
|
+
repo: f.repo,
|
|
2007
|
+
question: q.question,
|
|
2008
|
+
evidence: q.evidence
|
|
2009
|
+
}),
|
|
2010
|
+
repo: f.repo,
|
|
2011
|
+
draft
|
|
2012
|
+
};
|
|
2013
|
+
});
|
|
2014
|
+
}
|
|
2015
|
+
var TARGETS_SPLIT = TARGETS_SPLIT_LABEL;
|
|
2016
|
+
var PREFIX_ROOT = /^(api|rest|services?|v\d+(\.\d+)*)$/i;
|
|
2017
|
+
function targetPathPrefix(path) {
|
|
2018
|
+
const lead = path.startsWith("/") ? "/" : "";
|
|
2019
|
+
const out = [];
|
|
2020
|
+
for (const s of path.split(/[?#]/)[0].split("/").filter(Boolean)) {
|
|
2021
|
+
if (/^[{:<]/.test(s))
|
|
2022
|
+
break;
|
|
2023
|
+
out.push(s);
|
|
2024
|
+
if (!PREFIX_ROOT.test(s))
|
|
2025
|
+
break;
|
|
2026
|
+
}
|
|
2027
|
+
return out.length ? `${lead}${out.join("/")}` : "/";
|
|
2028
|
+
}
|
|
2029
|
+
function groupVia(f) {
|
|
2030
|
+
if (f.to)
|
|
2031
|
+
return ` naming ${f.to}`;
|
|
2032
|
+
if (f.host)
|
|
2033
|
+
return ` on ${f.host}`;
|
|
2034
|
+
if (f.base)
|
|
2035
|
+
return ` through ${f.base}`;
|
|
2036
|
+
return "";
|
|
2037
|
+
}
|
|
2038
|
+
function targetGroupQuestion(repo, f, facts) {
|
|
2039
|
+
const proxy = facts ? proxyOf(f, facts) : void 0;
|
|
2040
|
+
const through = f.base ?? f.host ?? f.to;
|
|
2041
|
+
if (proxy && through) {
|
|
2042
|
+
const p = targetPathPrefix(f.path);
|
|
2043
|
+
return `Which repo serves the routes under ${p === "/" ? "" : p}/** that ${proxy} forwards to ${through}?`;
|
|
2044
|
+
}
|
|
2045
|
+
return `Which repo or system answers the calls ${repo} makes under ${targetPathPrefix(f.path)}${groupVia(f)}?`;
|
|
2046
|
+
}
|
|
2047
|
+
function proxyOf(f, facts) {
|
|
2048
|
+
if (!f.from)
|
|
2049
|
+
return void 0;
|
|
2050
|
+
const from = facts.find((x) => x.id === f.from);
|
|
2051
|
+
if (!from || from.kind !== "entry")
|
|
2052
|
+
return void 0;
|
|
2053
|
+
const proxy = facts.find((x) => x.kind === "deploy" && x.repo === f.repo && x.platform === "apigee" && !!x.basePath);
|
|
2054
|
+
return proxy ? proxy.resourceName ?? f.repo : void 0;
|
|
2055
|
+
}
|
|
2056
|
+
function targetGroupWordings(repo, f, facts) {
|
|
2057
|
+
return [.../* @__PURE__ */ new Set([targetGroupQuestion(repo, f, facts), targetGroupQuestion(repo, f)])];
|
|
2058
|
+
}
|
|
2059
|
+
var repo0 = (members) => members[0].f.repo;
|
|
2060
|
+
var byLine = (a, b) => cmpNatural(a, b);
|
|
2061
|
+
var answerLabel2 = (q) => q.answer?.option !== void 0 ? q.options[q.answer.option - 1]?.label : q.answer?.text;
|
|
2062
|
+
function splitTargetGroups(questions) {
|
|
2063
|
+
return new Set(questions.filter((q) => q.askedBy?.step === TARGETS_STEP && q.state === "answered" && answerLabel2(q) === TARGETS_SPLIT).map((q) => q.question.trim()));
|
|
2064
|
+
}
|
|
2065
|
+
function groupedTargetDrafts(facts, records, op, asks, o) {
|
|
2066
|
+
const { list } = targetAsks(facts, records, asks);
|
|
2067
|
+
const ctx = {
|
|
2068
|
+
...o.estate ? { estate: o.estate } : {},
|
|
2069
|
+
...o.questions ? { questions: o.questions } : {}
|
|
2070
|
+
};
|
|
2071
|
+
const own = new Map(targetQuestionDrafts(facts, records, op, asks, ctx).map((d) => [d.factId, d]));
|
|
2072
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2073
|
+
for (const a of list) {
|
|
2074
|
+
if (!o.ask.has(a.f.id))
|
|
2075
|
+
continue;
|
|
2076
|
+
const k = targetGroupQuestion(a.f.repo, a.f, facts);
|
|
2077
|
+
groups.set(k, [...groups.get(k) ?? [], a]);
|
|
2078
|
+
}
|
|
1458
2079
|
const out = [];
|
|
1459
|
-
for (const
|
|
1460
|
-
const
|
|
1461
|
-
if (
|
|
2080
|
+
for (const [question, members] of groups) {
|
|
2081
|
+
const split = targetGroupWordings(repo0(members), members[0].f, facts).some((w) => o.split?.has(w));
|
|
2082
|
+
if (members.length === 1 || split) {
|
|
2083
|
+
for (const m of members)
|
|
2084
|
+
out.push({ ...own.get(m.f.id), factIds: [m.f.id] });
|
|
1462
2085
|
continue;
|
|
1463
|
-
|
|
1464
|
-
const
|
|
1465
|
-
const
|
|
1466
|
-
const
|
|
1467
|
-
|
|
1468
|
-
].sort();
|
|
1469
|
-
const what = [f.method?.toUpperCase(), f.path].filter(Boolean).join(" ");
|
|
2086
|
+
}
|
|
2087
|
+
const repo = members[0].f.repo;
|
|
2088
|
+
const whats = [...new Set(members.map((m) => callWhat(m.f)))];
|
|
2089
|
+
const listed = whats.length > 5 ? `${whats.slice(0, 5).join(", ")} and ${whats.length - 5} more` : whats.join(", ");
|
|
2090
|
+
const n = members.length;
|
|
2091
|
+
const evidence = [...new Set(members.flatMap((m) => m.q.evidence))].sort(byLine);
|
|
1470
2092
|
const draft = {
|
|
1471
|
-
question
|
|
2093
|
+
question,
|
|
1472
2094
|
options: [
|
|
1473
|
-
...
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
means: `${s.name}, an outside system, answers ${what}`,
|
|
1481
|
-
changes: `The call goes to ${s.name} when the facts are next written`
|
|
1482
|
-
})),
|
|
2095
|
+
...offered(members, facts, records, op, asks, ctx).map((s) => suggestionOption(s, repo, n, targetPathPrefix(members[0].f.path))),
|
|
2096
|
+
anotherOption(op),
|
|
2097
|
+
{
|
|
2098
|
+
label: TARGETS_SPLIT,
|
|
2099
|
+
means: `The ${n} calls (${listed}) don't all go to one place`,
|
|
2100
|
+
changes: "Nothing is joined from this answer; each call is asked on its own when the facts are next written"
|
|
2101
|
+
},
|
|
1483
2102
|
{
|
|
1484
2103
|
label: TARGETS_NONE,
|
|
1485
|
-
means: "Nothing in the estate or the org pack's systems answers
|
|
1486
|
-
changes: "The
|
|
2104
|
+
means: "Nothing in the estate or the org pack's systems answers any of them",
|
|
2105
|
+
changes: "The calls stay loose ends, and this isn't asked again"
|
|
1487
2106
|
}
|
|
1488
2107
|
],
|
|
1489
|
-
evidence
|
|
2108
|
+
evidence,
|
|
1490
2109
|
scope: "repo"
|
|
1491
2110
|
};
|
|
1492
2111
|
out.push({
|
|
1493
|
-
factId: f.id,
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
question: q.question,
|
|
1498
|
-
evidence: q.evidence
|
|
1499
|
-
}),
|
|
1500
|
-
repo: f.repo,
|
|
2112
|
+
factId: members[0].f.id,
|
|
2113
|
+
factIds: members.map((m) => m.f.id),
|
|
2114
|
+
id: questionId({ step: TARGETS_STEP, repo, question, evidence }),
|
|
2115
|
+
repo,
|
|
1501
2116
|
draft
|
|
1502
2117
|
});
|
|
1503
2118
|
}
|
|
2119
|
+
return out.sort((a, b) => cmpText(a.factId, b.factId));
|
|
2120
|
+
}
|
|
2121
|
+
function targetRuleOffers(questions, factsOf) {
|
|
2122
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
2123
|
+
for (const q of questions) {
|
|
2124
|
+
if (q.state !== "open" || q.askedBy?.step !== TARGETS_STEP)
|
|
2125
|
+
continue;
|
|
2126
|
+
const facts = factsOf(q.repo) ?? [];
|
|
2127
|
+
const at3 = new Set(q.evidence);
|
|
2128
|
+
const calls = facts.filter((f) => f.kind === "call" && f.repo === q.repo && at3.has(`${f.cite.file}:${f.cite.line}`));
|
|
2129
|
+
if (!calls.length)
|
|
2130
|
+
continue;
|
|
2131
|
+
const prefixes = new Set(calls.map((c) => targetPathPrefix(c.path)));
|
|
2132
|
+
const hosts = new Set(calls.map((c) => c.host?.replace(/^[a-z][a-z0-9+.-]*:\/\//i, "").split(/[/:]/)[0]));
|
|
2133
|
+
const [prefix] = prefixes;
|
|
2134
|
+
const [host] = hosts;
|
|
2135
|
+
const match = prefixes.size === 1 && prefix !== "/" ? `${prefix}/**` : hosts.size === 1 && host ? host : void 0;
|
|
2136
|
+
if (!match)
|
|
2137
|
+
continue;
|
|
2138
|
+
const key = `${q.repo}
|
|
2139
|
+
${match}`;
|
|
2140
|
+
const x = byKey.get(key) ?? { match, ids: [], calls: 0 };
|
|
2141
|
+
x.ids.push(q.id);
|
|
2142
|
+
x.calls += calls.length;
|
|
2143
|
+
byKey.set(key, x);
|
|
2144
|
+
}
|
|
2145
|
+
const out = /* @__PURE__ */ new Map();
|
|
2146
|
+
for (const x of byKey.values()) {
|
|
2147
|
+
if (x.ids.length < 2)
|
|
2148
|
+
continue;
|
|
2149
|
+
const ids = [...x.ids].sort();
|
|
2150
|
+
for (const id of ids)
|
|
2151
|
+
out.set(id, { match: x.match, ids, calls: x.calls });
|
|
2152
|
+
}
|
|
2153
|
+
return out;
|
|
2154
|
+
}
|
|
2155
|
+
var segmentsOf = (p) => p.split(/[?#]/)[0].split("/").filter(Boolean).map((x) => /^[{:<]/.test(x) ? "{}" : x);
|
|
2156
|
+
function ruleCoversCall(match, f, facts, o = {}) {
|
|
2157
|
+
if (isPathMatch(match)) {
|
|
2158
|
+
const want = segmentsOf(match.replace(/\/\*\*$/, ""));
|
|
2159
|
+
const got = segmentsOf(f.path);
|
|
2160
|
+
return want.length <= got.length && want.every((x, i) => x === "{}" || !o.strict && got[i] === "{}" || x === got[i]);
|
|
2161
|
+
}
|
|
2162
|
+
const url = urlOf(f, facts);
|
|
2163
|
+
const hp = url ? urlHostPort(url) : void 0;
|
|
2164
|
+
return !!hp?.host && hostMatches(match, hp.host, hp.port);
|
|
2165
|
+
}
|
|
2166
|
+
function ruledTargets(repo, facts, questions, o = {}) {
|
|
2167
|
+
const rules = questions.filter((q) => q.askedBy?.step === TARGETS_STEP && q.state === "answered" && !!q.answer?.match && (q.answer.scope.level === "repo" ? q.answer.scope.id === repo : !!o.repoType && q.answer.scope.id === o.repoType)).sort((a, b) => (a.answer.scope.level === "repo" ? 0 : 1) - (b.answer.scope.level === "repo" ? 0 : 1) || b.answer.match.length - a.answer.match.length || cmpText(b.answer.at, a.answer.at) || cmpText(a.id, b.id));
|
|
2168
|
+
const out = /* @__PURE__ */ new Map();
|
|
2169
|
+
if (!rules.length)
|
|
2170
|
+
return out;
|
|
2171
|
+
for (const f of facts) {
|
|
2172
|
+
if (!ruleMayName(f, facts))
|
|
2173
|
+
continue;
|
|
2174
|
+
const q = rules.find((r) => ruleCoversCall(r.answer.match, f, facts, o.strict ? { strict: true } : {}));
|
|
2175
|
+
const label2 = q ? answerLabel2(q) : void 0;
|
|
2176
|
+
if (q && label2 && label2 !== TARGETS_SPLIT)
|
|
2177
|
+
out.set(f.id, { id: q.id, match: q.answer.match, label: label2 });
|
|
2178
|
+
}
|
|
1504
2179
|
return out;
|
|
1505
2180
|
}
|
|
1506
|
-
function targetsFromAnswers(repo, facts, questions) {
|
|
2181
|
+
function targetsFromAnswers(repo, facts, questions, o = {}) {
|
|
1507
2182
|
const byId = new Map(questions.map((q) => [q.id, q]));
|
|
2183
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2184
|
+
for (const q of [...questions].filter((x) => x.askedBy?.step === TARGETS_STEP && x.state === "answered" && !!x.answer && typeof x.question === "string").sort((a, b) => cmpText(a.answer.at, b.answer.at) || cmpText(a.id, b.id)))
|
|
2185
|
+
groups.set(q.question.trim(), q);
|
|
2186
|
+
const ruled = ruledTargets(repo, facts, questions, o);
|
|
1508
2187
|
const out = {};
|
|
1509
|
-
for (const f of [...facts].sort((a, b) => a.id
|
|
2188
|
+
for (const f of [...facts].sort((a, b) => cmpText(a.id, b.id))) {
|
|
1510
2189
|
if (f.kind !== "call")
|
|
1511
2190
|
continue;
|
|
1512
2191
|
const q = targetQuestionFor(f);
|
|
1513
2192
|
if (!q)
|
|
1514
2193
|
continue;
|
|
1515
|
-
|
|
1516
|
-
if (!asked || asked.state !== "answered" || !asked.answer)
|
|
2194
|
+
let asked = byId.get(questionId({ step: TARGETS_STEP, repo, ...q }));
|
|
2195
|
+
if (!asked || asked.state !== "answered" || !asked.answer) {
|
|
2196
|
+
const g = targetGroupWordings(repo, f, facts).map((w) => groups.get(w)).find((x) => !!x);
|
|
2197
|
+
asked = g && (needsTarget(f, facts) || g.evidence.includes(q.evidence[0])) ? g : void 0;
|
|
2198
|
+
}
|
|
2199
|
+
let label2 = asked && asked.state === "answered" && asked.answer ? answerLabel2(asked) : void 0;
|
|
2200
|
+
let byAi = label2 && asked?.answer && isAiBy(asked.answer.by) ? asked.answer.by : void 0;
|
|
2201
|
+
if (!label2 || label2 === TARGETS_SPLIT || label2 === TARGETS_ANOTHER) {
|
|
2202
|
+
label2 = ruled.get(f.id)?.label;
|
|
2203
|
+
byAi = void 0;
|
|
2204
|
+
}
|
|
2205
|
+
if (!label2 || label2 === TARGETS_SPLIT || label2 === TARGETS_ANOTHER)
|
|
1517
2206
|
continue;
|
|
1518
|
-
|
|
1519
|
-
|
|
2207
|
+
if (byAi && o.ai)
|
|
2208
|
+
o.ai[f.id] = { runtime: byAi.runtime, run: byAi.run };
|
|
2209
|
+
if (label2 === TARGETS_NONE) {
|
|
2210
|
+
out[f.id] = NO_TARGET;
|
|
1520
2211
|
continue;
|
|
2212
|
+
}
|
|
1521
2213
|
const to = slugify(label2);
|
|
1522
2214
|
if (to)
|
|
1523
2215
|
out[f.id] = to;
|
|
1524
2216
|
}
|
|
1525
2217
|
return out;
|
|
1526
2218
|
}
|
|
2219
|
+
function answeredQuestionOfCall(repo, facts, questions, factId) {
|
|
2220
|
+
const f = facts.find((x) => x.id === factId);
|
|
2221
|
+
if (!f || f.kind !== "call")
|
|
2222
|
+
return void 0;
|
|
2223
|
+
const own = targetQuestionFor(f);
|
|
2224
|
+
const ownId = questionId({ step: TARGETS_STEP, repo, ...own });
|
|
2225
|
+
const answered = questions.filter((q2) => q2.askedBy?.step === TARGETS_STEP && q2.state === "answered" && q2.repo === repo);
|
|
2226
|
+
const q = answered.find((x) => x.id === ownId) ?? answered.find((x) => targetGroupWordings(repo, f, facts).includes(x.question.trim()) && x.evidence.includes(own.evidence[0]));
|
|
2227
|
+
return q?.answer?.match ? void 0 : q;
|
|
2228
|
+
}
|
|
2229
|
+
function answerReconfirmed(q) {
|
|
2230
|
+
if (!q.answer || !q.reopened)
|
|
2231
|
+
return false;
|
|
2232
|
+
const now2 = answerLabel2(q);
|
|
2233
|
+
const was = answerLabel2({ ...q, answer: q.reopened.answer });
|
|
2234
|
+
return !!now2 && !!was && slugify(now2) === slugify(was);
|
|
2235
|
+
}
|
|
2236
|
+
var JOINED_BY = {
|
|
2237
|
+
named: "the call names it",
|
|
2238
|
+
repo: "the call names its repo",
|
|
2239
|
+
host: "the manifest's hosts and base paths decide it",
|
|
2240
|
+
topic: "it listens on the call's topic"
|
|
2241
|
+
};
|
|
2242
|
+
function callsOfTargetQuestion(repo, facts, q) {
|
|
2243
|
+
return facts.filter((f) => {
|
|
2244
|
+
if (f.kind !== "call")
|
|
2245
|
+
return false;
|
|
2246
|
+
const own = targetQuestionFor(f);
|
|
2247
|
+
if (questionId({ step: TARGETS_STEP, repo, ...own }) === q.id)
|
|
2248
|
+
return true;
|
|
2249
|
+
return targetGroupWordings(repo, f, facts).includes(q.question.trim()) && q.evidence.includes(own.evidence[0]);
|
|
2250
|
+
});
|
|
2251
|
+
}
|
|
2252
|
+
function staleTargetQuestions(input) {
|
|
2253
|
+
const drafted = new Set(input.drafted.map((d) => d.id));
|
|
2254
|
+
const out = [];
|
|
2255
|
+
for (const q of input.open) {
|
|
2256
|
+
if (q.state !== "open" || q.askedBy.step !== TARGETS_STEP || drafted.has(q.id))
|
|
2257
|
+
continue;
|
|
2258
|
+
const calls = callsOfTargetQuestion(input.repo, input.facts, q);
|
|
2259
|
+
if (!calls.length)
|
|
2260
|
+
continue;
|
|
2261
|
+
const ids = new Set(calls.map((c) => c.id));
|
|
2262
|
+
const by = input.drafted.filter((d) => d.factIds.some((x) => ids.has(x))).map((d) => d.id);
|
|
2263
|
+
if (by.length) {
|
|
2264
|
+
out.push({ id: q.id, why: `its calls are asked now in ${by.join(", ")}`, replacedBy: by });
|
|
2265
|
+
continue;
|
|
2266
|
+
}
|
|
2267
|
+
if (calls.some((c) => input.asked.has(c.id)))
|
|
2268
|
+
continue;
|
|
2269
|
+
const reasons = calls.map((c) => {
|
|
2270
|
+
const rule = input.ruled?.get(c.id);
|
|
2271
|
+
if (rule)
|
|
2272
|
+
return `${callWhat(c)} is answered by rule in ${rule.id} (${rule.match})`;
|
|
2273
|
+
const j = input.join?.joins.find((x) => x.call.repo === input.repo && x.call.fact === c.id && x.how !== "answer");
|
|
2274
|
+
if (j && !isGuessedJoin(j) && JOINED_BY[j.how])
|
|
2275
|
+
return `${callWhat(c)} joins ${j.entry.component} in ${j.entry.repo}: ${JOINED_BY[j.how]}`;
|
|
2276
|
+
if (!j && c.to)
|
|
2277
|
+
return `${callWhat(c)} now names ${c.to} in the code`;
|
|
2278
|
+
return void 0;
|
|
2279
|
+
});
|
|
2280
|
+
const rules = [
|
|
2281
|
+
...new Set(calls.flatMap((c) => input.ruled?.get(c.id) ? [input.ruled.get(c.id).id] : []))
|
|
2282
|
+
].sort();
|
|
2283
|
+
if (reasons.every((r) => r !== void 0))
|
|
2284
|
+
out.push({
|
|
2285
|
+
id: q.id,
|
|
2286
|
+
why: reasons.join("; "),
|
|
2287
|
+
...rules.length ? { replacedBy: rules } : {}
|
|
2288
|
+
});
|
|
2289
|
+
}
|
|
2290
|
+
return out.sort((a, b) => cmpText(a.id, b.id));
|
|
2291
|
+
}
|
|
2292
|
+
function staleTopicQuestions(input) {
|
|
2293
|
+
const drafted = new Set(input.drafted.map((d) => d.id));
|
|
2294
|
+
const out = [];
|
|
2295
|
+
for (const q of input.open) {
|
|
2296
|
+
if (q.state !== "open" || q.askedBy.step !== TOPICS_STEP_ID || q.repo !== input.repo)
|
|
2297
|
+
continue;
|
|
2298
|
+
if (drafted.has(q.id))
|
|
2299
|
+
continue;
|
|
2300
|
+
const topic = topicOfQuestion(q);
|
|
2301
|
+
if (!topic)
|
|
2302
|
+
continue;
|
|
2303
|
+
const by = input.drafted.filter((d) => d.topic === topic).map((d) => d.id);
|
|
2304
|
+
out.push(by.length ? { id: q.id, why: `${topic} is asked now in ${by.join(", ")}`, replacedBy: by } : {
|
|
2305
|
+
id: q.id,
|
|
2306
|
+
why: `${topic} is no longer a loose end: a repo of the estate serves its other side now, or the profile names who owns it`
|
|
2307
|
+
});
|
|
2308
|
+
}
|
|
2309
|
+
return out.sort((a, b) => cmpText(a.id, b.id));
|
|
2310
|
+
}
|
|
1527
2311
|
function componentRenames(repos, op, taken = []) {
|
|
1528
2312
|
const manifests = estateRepos(op);
|
|
1529
2313
|
const users = /* @__PURE__ */ new Map();
|
|
@@ -1538,7 +2322,7 @@ function componentRenames(repos, op, taken = []) {
|
|
|
1538
2322
|
if (users.has(t.id))
|
|
1539
2323
|
use(t.id, t.repo);
|
|
1540
2324
|
const out = {};
|
|
1541
|
-
for (const [id, set] of [...users].sort((a, b) => a[0]
|
|
2325
|
+
for (const [id, set] of [...users].sort((a, b) => cmpText(a[0], b[0]))) {
|
|
1542
2326
|
if (set.size < 2)
|
|
1543
2327
|
continue;
|
|
1544
2328
|
for (const repo of repos.map((r) => r.repo).filter((r) => set.has(r))) {
|
|
@@ -1566,7 +2350,7 @@ function factsRecords(input) {
|
|
|
1566
2350
|
const facts = withComponents(repo, input.facts, op, input.renames).map((f) => {
|
|
1567
2351
|
let g = f;
|
|
1568
2352
|
const to = input.targets?.[g.id];
|
|
1569
|
-
if (g.kind === "call" && !g.to && to)
|
|
2353
|
+
if (g.kind === "call" && !g.to && to && to !== NO_TARGET)
|
|
1570
2354
|
g = { ...g, to };
|
|
1571
2355
|
return g;
|
|
1572
2356
|
});
|
|
@@ -1613,20 +2397,33 @@ function factsRecords(input) {
|
|
|
1613
2397
|
return { facts, projected, factById, newRecords, newFlows, loose, looseFact };
|
|
1614
2398
|
}
|
|
1615
2399
|
function outsideSystems(op) {
|
|
1616
|
-
return op
|
|
2400
|
+
return outsideProfileSystems(op).flatMap((s) => [s.id, s.name, ...s.aliases]).sort();
|
|
2401
|
+
}
|
|
2402
|
+
function outsideProfileSystems(op) {
|
|
2403
|
+
const own = new Set(estateRepos(op).flatMap((r) => r.system ? [r.system] : []));
|
|
2404
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2405
|
+
return op.profiles.flatMap((p) => p.profile?.systems ?? []).filter((s) => (s.boundary === "vendor" || !own.has(s.id)) && !seen.has(s.id) && !!seen.add(s.id)).sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
1617
2406
|
}
|
|
2407
|
+
var hostAliases = (aliases) => aliases.filter((a) => /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/i.test(a)).map((a) => a.toLowerCase());
|
|
1618
2408
|
function joinReposOf(op, list, renames) {
|
|
1619
2409
|
const manifests = estateRepos(op);
|
|
1620
2410
|
return list.map(({ repo, facts }) => {
|
|
1621
2411
|
const m = manifests.find((r) => r.id === repo);
|
|
1622
|
-
const modules = (m?.modules ?? []).filter((x) => x.hosts.length > 0);
|
|
2412
|
+
const modules = (m?.modules ?? []).filter((x) => x.hosts.length > 0 || x.basePath && (m?.hosts.length ?? 0) > 0).map((x) => x.hosts.length ? x : { ...x, hosts: m.hosts });
|
|
1623
2413
|
const component = m?.component ? renames?.[repo]?.[factIdPart(m.component)] ?? m.component : void 0;
|
|
1624
2414
|
return {
|
|
1625
2415
|
repo,
|
|
1626
2416
|
facts: renames?.[repo] ? withComponents(repo, facts, op, renames) : facts,
|
|
1627
2417
|
...component ? { component } : {},
|
|
1628
2418
|
...m?.hosts.length ? { hosts: m.hosts } : {},
|
|
1629
|
-
...
|
|
2419
|
+
...m?.basePath ? { basePath: m.basePath } : {},
|
|
2420
|
+
...modules.length ? {
|
|
2421
|
+
modules: modules.map((x) => ({
|
|
2422
|
+
component: x.component,
|
|
2423
|
+
hosts: x.hosts,
|
|
2424
|
+
...x.basePath ? { basePath: x.basePath } : {}
|
|
2425
|
+
}))
|
|
2426
|
+
} : {},
|
|
1630
2427
|
// An API gateway's entries are routes the join follows through (ADR 0124 addendum A-gateway).
|
|
1631
2428
|
// A type that says which side it faces (`gateway-external`, `gateway-internal`) marks each
|
|
1632
2429
|
// hop through it (A-gateway2); a plain `gateway` leaves that to the proxy's virtual host.
|
|
@@ -1668,12 +2465,7 @@ function applyFactJoins(records, join10, repos, withCallers) {
|
|
|
1668
2465
|
} else if (!(r.kind === "FieldMapping" && ifaces.has(r.interface)))
|
|
1669
2466
|
kept2.push(r);
|
|
1670
2467
|
}
|
|
1671
|
-
const re = (
|
|
1672
|
-
for (const [from, to] of was)
|
|
1673
|
-
if (id === from || id.startsWith(`${from}.`))
|
|
1674
|
-
return to + id.slice(from.length);
|
|
1675
|
-
return id;
|
|
1676
|
-
};
|
|
2468
|
+
const re = remapIds(was);
|
|
1677
2469
|
const joined = joinRecords(join10, repos);
|
|
1678
2470
|
const callers = withCallers ? callerRecords(withCallers.callers, repos, withCallers.join, {
|
|
1679
2471
|
...withCallers.only ? { only: withCallers.only } : {},
|
|
@@ -1694,20 +2486,28 @@ function applyFactJoins(records, join10, repos, withCallers) {
|
|
|
1694
2486
|
}
|
|
1695
2487
|
var entryText = (j) => j.method ? `${j.method} ${j.path}` : j.path;
|
|
1696
2488
|
function joinAbout(all, repo) {
|
|
1697
|
-
const joins = all.joins.filter((j) => j.call.repo === repo || j.entry.repo === repo || !!j.gateway?.some((h) => h.entry.repo === repo))
|
|
2489
|
+
const joins = all.joins.filter((j) => j.call.repo === repo || j.entry.repo === repo || !!j.gateway?.some((h) => h.entry.repo === repo) || // J0: a join whose placeholder this repo's config sets (a deploy or config repo) is
|
|
2490
|
+
// written again with it, so a changed value moves the link.
|
|
2491
|
+
[...j.through ?? [], ...j.entryThrough ?? []].some((t) => t.repo === repo) || // J1: and a join whose host or base path this repo's Route gives (a deploy repo's).
|
|
2492
|
+
[...j.exposedBy ?? [], ...(j.gateway ?? []).flatMap((h) => h.exposedBy ?? [])].some((t) => t.repo === repo));
|
|
1698
2493
|
const ifaces = new Set(joins.map((j) => j.interface));
|
|
2494
|
+
const outside = (all.outside ?? []).filter((o) => o.call.repo === repo);
|
|
2495
|
+
const exposure = (all.exposure ?? []).filter((x) => x.exposure.repo === repo || "repo" in x && x.repo === repo);
|
|
1699
2496
|
return {
|
|
1700
2497
|
joins,
|
|
1701
2498
|
asks: all.asks.filter((a) => a.call.repo === repo),
|
|
1702
2499
|
misses: all.misses.filter((m) => m.call.repo === repo),
|
|
2500
|
+
...outside.length ? { outside } : {},
|
|
2501
|
+
...exposure.length ? { exposure } : {},
|
|
1703
2502
|
lineage: all.lineage.filter((l) => ifaces.has(l.interface)),
|
|
1704
2503
|
gaps: all.gaps.filter((g) => ifaces.has(g.interface))
|
|
1705
2504
|
};
|
|
1706
2505
|
}
|
|
1707
|
-
function catalogWithFacts(
|
|
1708
|
-
const { repo, op } =
|
|
2506
|
+
function catalogWithFacts(given) {
|
|
2507
|
+
const { repo, op } = given;
|
|
1709
2508
|
if (isPrototypeRepo(op, repo))
|
|
1710
|
-
return prototypeLeftOut(
|
|
2509
|
+
return prototypeLeftOut(given);
|
|
2510
|
+
const input = { ...given, existing: stripSharedMaps(stripLinkEvidence(given.existing)) };
|
|
1711
2511
|
const others = (input.estate ?? []).filter((x) => x.repo !== repo && !isPrototypeRepo(op, x.repo));
|
|
1712
2512
|
const renames = componentRenames([{ repo, facts: input.facts }, ...others], op, input.existing.flatMap((r) => r.kind === "Component" && r.repo && r.repo !== repo ? [{ id: r.id, repo: r.repo }] : []));
|
|
1713
2513
|
const { facts, projected, newRecords, newFlows, looseFact } = factsRecords({
|
|
@@ -1757,20 +2557,46 @@ function catalogWithFacts(input) {
|
|
|
1757
2557
|
const remap = /* @__PURE__ */ new Map();
|
|
1758
2558
|
const stepTo = /* @__PURE__ */ new Map();
|
|
1759
2559
|
let join10;
|
|
2560
|
+
let answerMisses;
|
|
2561
|
+
let estateJoin;
|
|
2562
|
+
let terms;
|
|
1760
2563
|
let records0 = [...kept2, ...newRecords];
|
|
1761
2564
|
if (input.estate) {
|
|
1762
2565
|
const repos = joinReposOf(op, [{ repo, facts }, ...others], renames);
|
|
1763
2566
|
const whole = joinFacts({
|
|
1764
2567
|
repos,
|
|
1765
2568
|
targets: { ...input.estateTargets, [repo]: input.targets ?? {} },
|
|
1766
|
-
|
|
2569
|
+
pairs: { ...input.estatePairs, [repo]: input.pairs ?? {} },
|
|
2570
|
+
aiTargets: { ...input.estateAiTargets, [repo]: input.aiTargets ?? {} },
|
|
2571
|
+
outside: outsideSystems(op),
|
|
2572
|
+
...joinConventions(op),
|
|
2573
|
+
// G4: the walk through gateways is a stack helper (ATTICUS_HELPERS_OFF=gateway-walk).
|
|
2574
|
+
walkGateways: helperOn("gateway-walk")
|
|
1767
2575
|
});
|
|
1768
2576
|
join10 = joinAbout(whole, repo);
|
|
2577
|
+
estateJoin = { whole, repos };
|
|
2578
|
+
const named2 = { ...input.estateTargets, [repo]: input.targets ?? {} };
|
|
2579
|
+
const mine2 = /* @__PURE__ */ new Set([
|
|
2580
|
+
factIdPart(repo),
|
|
2581
|
+
...repos.filter((r) => r.repo === repo).flatMap((r) => [r.component, ...(r.modules ?? []).map((m) => m.component)]).filter((x) => !!x).map(factIdPart),
|
|
2582
|
+
...facts.flatMap((f) => f.component ? [factIdPart(f.component)] : [])
|
|
2583
|
+
]);
|
|
2584
|
+
answerMisses = whole.misses.filter((m) => {
|
|
2585
|
+
const t = named2[m.call.repo]?.[m.call.fact];
|
|
2586
|
+
return !!t && t !== NO_TARGET && (m.call.repo === repo || mine2.has(factIdPart(m.target)));
|
|
2587
|
+
});
|
|
1769
2588
|
records0 = applyFactJoins(records0, join10, repos, {
|
|
1770
|
-
callers: joinCallers({
|
|
2589
|
+
callers: joinCallers({
|
|
2590
|
+
repos,
|
|
2591
|
+
join: whole,
|
|
2592
|
+
declared: declaredCallsOf(op.profiles),
|
|
2593
|
+
...joinConventions(op)
|
|
2594
|
+
}),
|
|
1771
2595
|
join: whole,
|
|
1772
2596
|
only: [repo]
|
|
1773
2597
|
});
|
|
2598
|
+
terms = joinTerms({ repos, join: whole, bundleFolders: joinConventions(op).bundleFolders });
|
|
2599
|
+
records0 = [...records0, ...termGrantRecords(terms, repos, { only: [repo] })];
|
|
1774
2600
|
for (const j of join10.joins) {
|
|
1775
2601
|
const id = j.call.repo === repo ? projected.ids[j.call.fact] : void 0;
|
|
1776
2602
|
if (!id || remap.has(id))
|
|
@@ -1779,7 +2605,10 @@ function catalogWithFacts(input) {
|
|
|
1779
2605
|
stepTo.set(id, { component: j.entry.component, entry: entryText(j) });
|
|
1780
2606
|
}
|
|
1781
2607
|
}
|
|
1782
|
-
const merged0 = mergeEstate(op, estateRepos(op), records0.sort(byKindId)
|
|
2608
|
+
const merged0 = mergeEstate(op, estateRepos(op), records0.sort(byKindId), void 0, /* @__PURE__ */ new Set([
|
|
2609
|
+
repo,
|
|
2610
|
+
...input.estate ? others.filter((r) => r.facts.length > 0).map((r) => r.repo) : []
|
|
2611
|
+
]));
|
|
1783
2612
|
const heard = new Set((join10?.joins ?? []).filter((j) => j.how === "topic").map((j) => `${j.entry.repo}|${j.entry.at}|${j.entry.component}`));
|
|
1784
2613
|
const merged = {
|
|
1785
2614
|
...merged0,
|
|
@@ -1806,15 +2635,7 @@ function catalogWithFacts(input) {
|
|
|
1806
2635
|
joinedTo.set(id, iface.to);
|
|
1807
2636
|
}
|
|
1808
2637
|
}
|
|
1809
|
-
const re = (
|
|
1810
|
-
const exact = remap.get(id);
|
|
1811
|
-
if (exact)
|
|
1812
|
-
return exact;
|
|
1813
|
-
for (const [from, to] of remap)
|
|
1814
|
-
if (id.startsWith(`${from}.`))
|
|
1815
|
-
return to + id.slice(from.length);
|
|
1816
|
-
return id;
|
|
1817
|
-
};
|
|
2638
|
+
const re = remapIds(remap);
|
|
1818
2639
|
const records = merged.records.map((r) => {
|
|
1819
2640
|
if (r.kind === "Schema")
|
|
1820
2641
|
return { ...r, id: re(r.id), ref: re(r.ref) };
|
|
@@ -1824,7 +2645,25 @@ function catalogWithFacts(input) {
|
|
|
1824
2645
|
return { ...r, interface: re(r.interface) };
|
|
1825
2646
|
return r;
|
|
1826
2647
|
});
|
|
1827
|
-
const
|
|
2648
|
+
const curated = applyCurated([...records].sort(byKindId), input.decisions);
|
|
2649
|
+
const linkEvidence = estateJoin ? linkEvidenceOf({
|
|
2650
|
+
join: estateJoin.whole,
|
|
2651
|
+
repos: estateJoin.repos,
|
|
2652
|
+
contracts: input.linkEvidence?.contracts ?? [],
|
|
2653
|
+
traffic: input.linkEvidence?.traffic ?? []
|
|
2654
|
+
}) : void 0;
|
|
2655
|
+
const evidenced = linkEvidence ? { ...curated, records: applyLinkEvidence(curated.records, linkEvidence.marks) } : curated;
|
|
2656
|
+
const systems = outsideProfileSystems(op);
|
|
2657
|
+
const sharedWhole = estateJoin && input.shared?.length ? joinSharedMaps({
|
|
2658
|
+
outside: estateJoin.whole.outside ?? [],
|
|
2659
|
+
repos: estateJoin.repos,
|
|
2660
|
+
maps: input.shared,
|
|
2661
|
+
systems
|
|
2662
|
+
}) : void 0;
|
|
2663
|
+
const applied = {
|
|
2664
|
+
...evidenced,
|
|
2665
|
+
records: sharedWhole ? applySharedMaps(evidenced.records, sharedWhole, systems) : stripSharedMaps(evidenced.records)
|
|
2666
|
+
};
|
|
1828
2667
|
const newIds = new Set(applied.records.filter((r) => r.evidence.some(mine)).map((r) => r.id));
|
|
1829
2668
|
const oldIds = new Set([...dropped].map((k) => k.slice(k.indexOf("|") + 1)));
|
|
1830
2669
|
const flows = newFlows.map((f) => {
|
|
@@ -1852,10 +2691,10 @@ function catalogWithFacts(input) {
|
|
|
1852
2691
|
raw: r.raw
|
|
1853
2692
|
});
|
|
1854
2693
|
}
|
|
1855
|
-
unresolved.sort((a, b) => a.factId
|
|
2694
|
+
unresolved.sort((a, b) => cmpText(a.factId, b.factId));
|
|
1856
2695
|
return {
|
|
1857
2696
|
records: applied.records.sort(byKindId),
|
|
1858
|
-
flows: flows.sort((a, b) => a.id
|
|
2697
|
+
flows: flows.sort((a, b) => cmpText(a.id, b.id)),
|
|
1859
2698
|
unresolved,
|
|
1860
2699
|
curated: {
|
|
1861
2700
|
applied: applied.applied,
|
|
@@ -1865,7 +2704,11 @@ function catalogWithFacts(input) {
|
|
|
1865
2704
|
projectionIssues: projected.issues.filter((i) => i.code !== "unresolved-target"),
|
|
1866
2705
|
invalid: merged.invalid,
|
|
1867
2706
|
notes: merged.notes,
|
|
1868
|
-
...join10 ? { join: join10 } : {}
|
|
2707
|
+
...join10 ? { join: join10 } : {},
|
|
2708
|
+
...linkEvidence ? { linkEvidence } : {},
|
|
2709
|
+
...sharedWhole ? { shared: sharedJoinAbout(sharedWhole, repo) } : {},
|
|
2710
|
+
...terms ? { terms } : {},
|
|
2711
|
+
...answerMisses?.length ? { answerMisses } : {}
|
|
1869
2712
|
};
|
|
1870
2713
|
}
|
|
1871
2714
|
function prototypeLeftOut(input) {
|
|
@@ -1888,16 +2731,99 @@ function prototypeLeftOut(input) {
|
|
|
1888
2731
|
]
|
|
1889
2732
|
};
|
|
1890
2733
|
}
|
|
2734
|
+
function estateFromFacts(input) {
|
|
2735
|
+
const repos = input.repos.filter((x) => !isPrototypeRepo(input.op, x.repo));
|
|
2736
|
+
let records = [...input.existing ?? []];
|
|
2737
|
+
const specs = [];
|
|
2738
|
+
const invalid = [];
|
|
2739
|
+
for (const { repo, facts } of repos) {
|
|
2740
|
+
const out = catalogWithFacts({
|
|
2741
|
+
repo,
|
|
2742
|
+
facts,
|
|
2743
|
+
existing: records,
|
|
2744
|
+
op: input.op,
|
|
2745
|
+
decisions: input.decisions ?? [],
|
|
2746
|
+
estate: repos.filter((x) => x.repo !== repo)
|
|
2747
|
+
});
|
|
2748
|
+
records = out.records;
|
|
2749
|
+
specs.push({ repo, flows: out.flows });
|
|
2750
|
+
invalid.push(...out.invalid);
|
|
2751
|
+
}
|
|
2752
|
+
const renames = componentRenames(repos, input.op);
|
|
2753
|
+
const join10 = joinFacts({
|
|
2754
|
+
repos: joinReposOf(input.op, repos.map(({ repo, facts }) => ({
|
|
2755
|
+
repo,
|
|
2756
|
+
facts: factsRecords({ repo, facts, op: input.op, renames }).facts
|
|
2757
|
+
}))),
|
|
2758
|
+
outside: outsideSystems(input.op),
|
|
2759
|
+
...joinConventions(input.op),
|
|
2760
|
+
walkGateways: helperOn("gateway-walk")
|
|
2761
|
+
});
|
|
2762
|
+
return { records, specs, join: join10, invalid };
|
|
2763
|
+
}
|
|
2764
|
+
function answerRuleOffers(input) {
|
|
2765
|
+
const ruled = input.questions.filter((q) => q.askedBy?.step === TARGETS_STEP && q.state === "answered" && !!q.answer?.match);
|
|
2766
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2767
|
+
for (const r of input.repos)
|
|
2768
|
+
for (const f of r.facts) {
|
|
2769
|
+
if (f.kind !== "call")
|
|
2770
|
+
continue;
|
|
2771
|
+
const q = answeredQuestionOfCall(r.repo, r.facts, input.questions, f.id);
|
|
2772
|
+
const label2 = q ? answerLabel2(q) : void 0;
|
|
2773
|
+
if (!q || !label2 || label2 === TARGETS_NONE || label2 === TARGETS_SPLIT || label2 === TARGETS_ANOTHER)
|
|
2774
|
+
continue;
|
|
2775
|
+
const url = urlOf(f, r.facts);
|
|
2776
|
+
const host = url ? urlHostPort(url)?.host : void 0;
|
|
2777
|
+
const via = host ? `on ${host.toLowerCase()}` : f.base ? `through ${f.base}` : void 0;
|
|
2778
|
+
if (!via)
|
|
2779
|
+
continue;
|
|
2780
|
+
groups.set(via, [
|
|
2781
|
+
...groups.get(via) ?? [],
|
|
2782
|
+
{ repo: r.repo, q, label: label2, f, facts: r.facts, ...r.type ? { type: r.type } : {} }
|
|
2783
|
+
]);
|
|
2784
|
+
}
|
|
2785
|
+
const out = [];
|
|
2786
|
+
for (const [via, xs] of [...groups].sort(([a], [b]) => cmpText(a, b))) {
|
|
2787
|
+
const repos = [...new Set(xs.map((x) => x.repo))].sort();
|
|
2788
|
+
const labels = new Set(xs.map((x) => slugify(x.label)));
|
|
2789
|
+
if (repos.length < 2 || labels.size !== 1)
|
|
2790
|
+
continue;
|
|
2791
|
+
if (xs.every((x) => ruled.some((q) => ruleCoversCall(q.answer.match, x.f, x.facts))))
|
|
2792
|
+
continue;
|
|
2793
|
+
const label2 = xs[0].label;
|
|
2794
|
+
const prefixes = new Set(xs.map((x) => targetPathPrefix(x.f.path)));
|
|
2795
|
+
const prefix = prefixes.size === 1 ? [...prefixes][0] : void 0;
|
|
2796
|
+
const match = via.startsWith("on ") ? via.slice(3) : prefix && prefix !== "/" ? `${prefix}/**` : void 0;
|
|
2797
|
+
const types = new Set(xs.map((x) => x.type));
|
|
2798
|
+
const type = types.size === 1 ? xs[0].type : void 0;
|
|
2799
|
+
const questions = [...new Set(xs.map((x) => x.q.id))].sort();
|
|
2800
|
+
const first = xs.find((x) => x.q.id === questions[0]).q;
|
|
2801
|
+
const n = first.options.findIndex((o) => o.label === label2);
|
|
2802
|
+
const pick = n >= 0 ? `--option ${n + 1}` : `--text "${label2}"`;
|
|
2803
|
+
if (!match || !type)
|
|
2804
|
+
continue;
|
|
2805
|
+
const command = `atticus questions answer ${first.id} ${pick} --match "${match}" --scope repo-type --type ${type}`;
|
|
2806
|
+
out.push({
|
|
2807
|
+
via,
|
|
2808
|
+
label: label2,
|
|
2809
|
+
repos,
|
|
2810
|
+
questions,
|
|
2811
|
+
match,
|
|
2812
|
+
type,
|
|
2813
|
+
command,
|
|
2814
|
+
line: `${repos.length} repos (${repos.join(", ")}) answer the calls ${via} the same way, ${label2} (${questions.join(", ")}): one answer can say it for every ${type} repo. As a rule: ${command}.`
|
|
2815
|
+
});
|
|
2816
|
+
}
|
|
2817
|
+
return out;
|
|
2818
|
+
}
|
|
1891
2819
|
|
|
1892
2820
|
// packages/scanner/dist/facts-scan.js
|
|
1893
|
-
import { readFile as readFile4 } from "node:fs/promises";
|
|
1894
|
-
import { join as join6 } from "node:path";
|
|
1895
2821
|
async function factsForScan(root, op, repo, commit, readerRecords, at3 = {}) {
|
|
1896
2822
|
const rel = `${factsRelOf(root)}/${repo}.jsonl`;
|
|
1897
|
-
const
|
|
1898
|
-
if (
|
|
2823
|
+
const text2 = await readFile4(join6(factsDirOf(root), `${repo}.jsonl`), "utf8").catch(() => void 0);
|
|
2824
|
+
if (text2 === void 0 || text2.trim() === "")
|
|
1899
2825
|
return {};
|
|
1900
|
-
const parsed = parseFacts(
|
|
2826
|
+
const parsed = parseFacts(text2);
|
|
1901
2827
|
if (!parsed.ok)
|
|
1902
2828
|
return {
|
|
1903
2829
|
note: `${repo}: ${rel} doesn't read as facts (${parsed.issues[0]?.message ?? "invalid"}); its readers' records are used. Run atticus verify ${repo}.`
|
|
@@ -1910,29 +2836,45 @@ async function factsForScan(root, op, repo, commit, readerRecords, at3 = {}) {
|
|
|
1910
2836
|
};
|
|
1911
2837
|
const older = commit ? facts.filter((f) => !sameCommit(f.commit, commit)) : [];
|
|
1912
2838
|
let carriedSaid = "";
|
|
2839
|
+
let unread = [];
|
|
1913
2840
|
if (older.length) {
|
|
1914
2841
|
const diff = at3.dir ? await changesSince(at3.dir, older.map((f) => f.commit), commit, at3.git) : void 0;
|
|
1915
2842
|
if (!diff || diff.missing.length)
|
|
1916
2843
|
return {
|
|
1917
2844
|
note: `${repo}: its facts are of commit ${(diff?.missing[0] ?? older[0].commit).slice(0, 7)}, the checkout is at ${commit.slice(0, 7)}, and the two can't be compared here; its readers' records are used until it is read again (/atticus-map ${repo}, then atticus verify ${repo} --write).`
|
|
1918
2845
|
};
|
|
2846
|
+
const off = stackHelpersOff();
|
|
1919
2847
|
const report = await verifyFacts({
|
|
1920
2848
|
facts,
|
|
1921
2849
|
source: dirSource(at3.dir),
|
|
2850
|
+
...off.has("derived-names") ? { derivedNames: false } : {},
|
|
2851
|
+
...off.has("readers") ? { readers: false } : {},
|
|
1922
2852
|
commit,
|
|
1923
|
-
carry: diff.changes
|
|
2853
|
+
carry: diff.changes,
|
|
2854
|
+
conventions: conventionsOf(op),
|
|
2855
|
+
// ADR 0180: a type taken out of the organisation's set after a read is flagged here too.
|
|
2856
|
+
componentTypes: new Set(effectiveComponentTypes(op.componentTypes).map((t) => t.id))
|
|
1924
2857
|
});
|
|
1925
2858
|
const from = [...new Set(older.map((f) => f.commit.slice(0, 7)))].join(", ");
|
|
1926
2859
|
if (!report.accepted.length)
|
|
1927
2860
|
return {
|
|
1928
2861
|
note: `${repo}: every one of its facts (of commit ${from}) cites a file changed since, or names a fact that does; its readers' records are used until it is read again (/atticus-map ${repo}, then atticus verify ${repo} --write).`
|
|
1929
2862
|
};
|
|
2863
|
+
unread = rereadPlan({
|
|
2864
|
+
facts,
|
|
2865
|
+
commit,
|
|
2866
|
+
carry: diff.changes,
|
|
2867
|
+
source: dirSource(at3.dir),
|
|
2868
|
+
conventions: conventionsOf(op)
|
|
2869
|
+
}).files.map((f) => f.path);
|
|
1930
2870
|
facts = report.accepted;
|
|
1931
2871
|
const left = report.rejected.length;
|
|
1932
2872
|
carriedSaid = `; ${report.carried.length} carried from commit ${from} to ${commit.slice(0, 7)} (the files they cite are unchanged, their lines checked again)${left ? `, ${left} left out until read again (they cite a file changed since, or name a fact that does: /atticus-map ${repo}, then atticus verify ${repo} --write)` : ""}`;
|
|
1933
2873
|
}
|
|
1934
|
-
const questions = (await listQuestions(root, {
|
|
1935
|
-
const targets = targetsFromAnswers(repo, facts, questions
|
|
2874
|
+
const questions = (await listQuestions(root, { step: TARGETS_STEP }).catch(() => ({ questions: [] }))).questions;
|
|
2875
|
+
const targets = targetsFromAnswers(repo, facts, questions, {
|
|
2876
|
+
repoType: estateRepos(op).find((r) => r.id === repo)?.type
|
|
2877
|
+
});
|
|
1936
2878
|
const others = (await estateFacts(root, op, repo)).repos;
|
|
1937
2879
|
const renames = componentRenames([{ repo, facts }, ...others], op);
|
|
1938
2880
|
const { newRecords, facts: used } = factsRecords({ repo, facts, op, targets, renames });
|
|
@@ -1958,40 +2900,57 @@ async function factsForScan(root, op, repo, commit, readerRecords, at3 = {}) {
|
|
|
1958
2900
|
continue;
|
|
1959
2901
|
extra.set(r.kind, (extra.get(r.kind) ?? 0) + 1);
|
|
1960
2902
|
}
|
|
1961
|
-
const said = [...extra.entries()].sort((a, b) => a[0]
|
|
2903
|
+
const said = [...extra.entries()].sort((a, b) => cmpText(a[0], b[0])).map(([k, n]) => `${n} ${k}`).join(", ");
|
|
1962
2904
|
return {
|
|
1963
2905
|
records: newRecords,
|
|
1964
2906
|
facts: used,
|
|
1965
2907
|
targets,
|
|
2908
|
+
basis: { from: "facts", unread },
|
|
1966
2909
|
note: `${repo}: read from its facts (${facts.length}, ${rel}${carriedSaid})${said ? `; its readers found ${said} the facts don't have (a cross-check: read those lines again, or a reader misreads them)` : "; its readers agree"}.`
|
|
1967
2910
|
};
|
|
1968
2911
|
}
|
|
2912
|
+
async function pairsOfRepo(root, repo, facts) {
|
|
2913
|
+
const text2 = await readFile4(join6(factsDirOf(root), `${repo}.corrections.jsonl`), "utf8").catch(() => void 0);
|
|
2914
|
+
if (!text2 || !/"(?:un)?link"/.test(text2))
|
|
2915
|
+
return {};
|
|
2916
|
+
return pairsFromCorrections(facts, parseCorrections(text2).corrections, { declined: true });
|
|
2917
|
+
}
|
|
1969
2918
|
async function estateFacts(root, op, except) {
|
|
1970
2919
|
const repos = [];
|
|
1971
2920
|
const targets = {};
|
|
2921
|
+
const aiTargets = {};
|
|
2922
|
+
const pairs = {};
|
|
1972
2923
|
const notes = [];
|
|
2924
|
+
const unverified = [];
|
|
2925
|
+
const questions = (await listQuestions(root, { step: TARGETS_STEP }).catch(() => ({ questions: [] }))).questions;
|
|
1973
2926
|
for (const r of productionRepos(op)) {
|
|
1974
2927
|
if (r.id === except)
|
|
1975
2928
|
continue;
|
|
1976
|
-
const
|
|
1977
|
-
if (
|
|
2929
|
+
const text2 = await readFile4(join6(factsDirOf(root), `${r.id}.jsonl`), "utf8").catch(() => void 0);
|
|
2930
|
+
if (text2 === void 0 || text2.trim() === "")
|
|
1978
2931
|
continue;
|
|
1979
|
-
const parsed = parseFacts(
|
|
2932
|
+
const parsed = parseFacts(text2);
|
|
1980
2933
|
if (!parsed.ok) {
|
|
1981
2934
|
notes.push(`${r.id}: its facts don't read, so the estate join leaves them out.`);
|
|
1982
2935
|
continue;
|
|
1983
2936
|
}
|
|
1984
2937
|
if (parsed.facts.some((f) => f.verified === "none")) {
|
|
2938
|
+
unverified.push(r.id);
|
|
1985
2939
|
notes.push(`${r.id}: some of its facts aren't verified yet, so the estate join leaves them out (atticus verify ${r.id}).`);
|
|
1986
2940
|
continue;
|
|
1987
2941
|
}
|
|
1988
2942
|
repos.push({ repo: r.id, facts: parsed.facts });
|
|
1989
|
-
const
|
|
1990
|
-
const named2 = targetsFromAnswers(r.id, parsed.facts, questions);
|
|
2943
|
+
const ai = {};
|
|
2944
|
+
const named2 = targetsFromAnswers(r.id, parsed.facts, questions, { repoType: r.type, ai });
|
|
1991
2945
|
if (Object.keys(named2).length)
|
|
1992
2946
|
targets[r.id] = named2;
|
|
2947
|
+
if (Object.keys(ai).length)
|
|
2948
|
+
aiTargets[r.id] = ai;
|
|
2949
|
+
const paired = await pairsOfRepo(root, r.id, parsed.facts);
|
|
2950
|
+
if (Object.keys(paired).length)
|
|
2951
|
+
pairs[r.id] = paired;
|
|
1993
2952
|
}
|
|
1994
|
-
return { repos, targets, notes };
|
|
2953
|
+
return { repos, targets, pairs, notes, unverified, aiTargets };
|
|
1995
2954
|
}
|
|
1996
2955
|
|
|
1997
2956
|
// packages/scanner/dist/scan-settings.js
|
|
@@ -2006,10 +2965,10 @@ async function citedLine(repo, at3) {
|
|
|
2006
2965
|
const i = at3.lastIndexOf(":");
|
|
2007
2966
|
const path = at3.slice(0, i);
|
|
2008
2967
|
const n = Number(at3.slice(i + 1));
|
|
2009
|
-
const
|
|
2010
|
-
if (
|
|
2968
|
+
const text2 = await repo.readText(path).catch(() => void 0);
|
|
2969
|
+
if (text2 === void 0)
|
|
2011
2970
|
return void 0;
|
|
2012
|
-
return
|
|
2971
|
+
return text2.replace(/\r\n?/g, "\n").split("\n")[n - 1];
|
|
2013
2972
|
}
|
|
2014
2973
|
var CODE_EXT = new Set("java kt scala groovy ts tsx js jsx mjs cjs py go rb cs fs vb php rs swift m mm c cc cpp h hpp dart ex exs erl clj lua pl".split(" "));
|
|
2015
2974
|
var NOT_OWN = /(^|\/)(node_modules|\.git|target|bin|obj|dist|build|\.atticus|vendor)\//;
|
|
@@ -2068,8 +3027,8 @@ async function checkScanSettings(s, repo) {
|
|
|
2068
3027
|
if (!(isConfigFile(file) && names)) {
|
|
2069
3028
|
const dropped = code.filter((f) => re.test(f));
|
|
2070
3029
|
const root = mainRoots.find((d) => {
|
|
2071
|
-
const
|
|
2072
|
-
return
|
|
3030
|
+
const under2 = code.filter((f) => f.startsWith(`${d}/`));
|
|
3031
|
+
return under2.length && under2.every((f) => re.test(f));
|
|
2073
3032
|
});
|
|
2074
3033
|
const why = " so it is still read; only a build or config file that leaves it out itself can say so";
|
|
2075
3034
|
if (dropped.length * 2 > code.length) {
|
|
@@ -2126,9 +3085,9 @@ async function namesNotIn(repo, paths, names) {
|
|
|
2126
3085
|
for await (const f of repo.listFiles(["**/*"])) {
|
|
2127
3086
|
if (!paths.some((x) => x.test(f)))
|
|
2128
3087
|
continue;
|
|
2129
|
-
const
|
|
3088
|
+
const text2 = await repo.readText(f).catch(() => "");
|
|
2130
3089
|
for (const n of [...left])
|
|
2131
|
-
if (
|
|
3090
|
+
if (text2.includes(n))
|
|
2132
3091
|
left.delete(n);
|
|
2133
3092
|
if (left.size === 0)
|
|
2134
3093
|
break;
|
|
@@ -2194,6 +3153,12 @@ function withExcludes(repo, exclude) {
|
|
|
2194
3153
|
yield f;
|
|
2195
3154
|
}
|
|
2196
3155
|
});
|
|
3156
|
+
const listing = /* @__PURE__ */ Symbol.for("@maycombhq/sdk.listing");
|
|
3157
|
+
const before = repo[listing];
|
|
3158
|
+
Object.defineProperty(wrapped, listing, {
|
|
3159
|
+
value: `${typeof before === "string" ? before : ""}|exclude:${JSON.stringify(exclude.map((x) => `${x.source}/${x.flags}`))}`,
|
|
3160
|
+
configurable: true
|
|
3161
|
+
});
|
|
2197
3162
|
return wrapped;
|
|
2198
3163
|
}
|
|
2199
3164
|
var join7 = (prefix, path) => `${prefix.replace(/\/+$/, "")}${path.startsWith("/") ? path : `/${path}`}`.replace(/\/+$/, "") || "/";
|
|
@@ -2355,54 +3320,6 @@ function withPluginScan(scan, extra) {
|
|
|
2355
3320
|
};
|
|
2356
3321
|
}
|
|
2357
3322
|
|
|
2358
|
-
// packages/scanner/dist/scan.js
|
|
2359
|
-
import { stat } from "node:fs/promises";
|
|
2360
|
-
import { dirname as dirname3, isAbsolute, join as join9, resolve as resolve2 } from "node:path";
|
|
2361
|
-
import { fileURLToPath } from "node:url";
|
|
2362
|
-
|
|
2363
|
-
// packages/scanner/dist/federate-loose.js
|
|
2364
|
-
function resolveFederatedLooseEnds(o) {
|
|
2365
|
-
const originOf = (r) => (r.flags ?? []).find((f) => f.startsWith("federated:"))?.slice("federated:".length);
|
|
2366
|
-
const loose = o.federated.filter((r) => r.kind === "Unresolved");
|
|
2367
|
-
if (!loose.length)
|
|
2368
|
-
return { federated: o.federated, resolved: /* @__PURE__ */ new Map() };
|
|
2369
|
-
const fedComponents = o.federated.filter((r) => r.kind === "Component");
|
|
2370
|
-
const result = o.merge([...o.raw, ...fedComponents, ...loose]);
|
|
2371
|
-
const have = new Set(o.local.map((r) => `${r.kind}/${r.id}`));
|
|
2372
|
-
const known = /* @__PURE__ */ new Set([
|
|
2373
|
-
...o.local.filter((r) => r.kind === "Component" || r.kind === "System").map((r) => r.id),
|
|
2374
|
-
...fedComponents.map((r) => r.id)
|
|
2375
|
-
]);
|
|
2376
|
-
const origin = new Map(fedComponents.map((c) => [c.id, originOf(c)]));
|
|
2377
|
-
const stillLoose = new Set(result.records.filter((r) => r.kind === "Unresolved").map((r) => r.id));
|
|
2378
|
-
const added = [];
|
|
2379
|
-
for (const r of result.records) {
|
|
2380
|
-
if (r.kind !== "Interface" || have.has(`Interface/${r.id}`))
|
|
2381
|
-
continue;
|
|
2382
|
-
const i = r;
|
|
2383
|
-
const from = origin.get(i.from) ?? origin.get(i.to);
|
|
2384
|
-
if (!from || !known.has(i.from) || !known.has(i.to))
|
|
2385
|
-
continue;
|
|
2386
|
-
added.push({ ...r, flags: [.../* @__PURE__ */ new Set([...r.flags ?? [], `federated:${from}`])] });
|
|
2387
|
-
}
|
|
2388
|
-
const key = (e) => JSON.stringify(e);
|
|
2389
|
-
const carried = new Set(added.flatMap((i) => i.evidence.map(key)));
|
|
2390
|
-
const resolved = /* @__PURE__ */ new Map();
|
|
2391
|
-
const kept2 = o.federated.filter((r) => {
|
|
2392
|
-
if (r.kind !== "Unresolved" || stillLoose.has(r.id))
|
|
2393
|
-
return true;
|
|
2394
|
-
if (!r.evidence.some((e) => e.source !== "export" && carried.has(key(e))))
|
|
2395
|
-
return true;
|
|
2396
|
-
const id = originOf(r) ?? "";
|
|
2397
|
-
resolved.set(id, (resolved.get(id) ?? 0) + 1);
|
|
2398
|
-
return false;
|
|
2399
|
-
});
|
|
2400
|
-
return {
|
|
2401
|
-
federated: [...kept2, ...added].sort((a, b) => a.kind === b.kind ? a.id.localeCompare(b.id) : a.kind.localeCompare(b.kind)),
|
|
2402
|
-
resolved
|
|
2403
|
-
};
|
|
2404
|
-
}
|
|
2405
|
-
|
|
2406
3323
|
// packages/scanner/dist/scan.js
|
|
2407
3324
|
var STRUCTURED = /\.(ya?ml|json|xml|tf|tfvars|properties|toml|conf|hcl)$/i;
|
|
2408
3325
|
var TOOLING = /(^|\/)(package(-lock)?\.json|pnpm-lock\.yaml|yarn\.lock|tsconfig[^/]*\.json|\.eslintrc[^/]*|\.prettierrc[^/]*|renovate\.json|\.vscode\/.*|\.idea\/.*|pom\.xml|catalog-info\.yaml|mkdocs\.yml|codecov\.yml)$/i;
|
|
@@ -2418,13 +3335,6 @@ async function orgPackDirFor(root) {
|
|
|
2418
3335
|
return { dir: join9(root, ".atticus", "org-pack"), adopted: true };
|
|
2419
3336
|
return { dir: join9(root, "org-pack"), adopted: false };
|
|
2420
3337
|
}
|
|
2421
|
-
function inPlace(root, url) {
|
|
2422
|
-
if (url === "." || url === "./")
|
|
2423
|
-
return root;
|
|
2424
|
-
if (url.startsWith("./") && !url.split("/").includes(".."))
|
|
2425
|
-
return join9(root, url.slice(2));
|
|
2426
|
-
return void 0;
|
|
2427
|
-
}
|
|
2428
3338
|
async function federate(local, sources, problems, root) {
|
|
2429
3339
|
const have = new Set(local.map((r) => `${r.kind}/${r.id}`));
|
|
2430
3340
|
const records = [];
|
|
@@ -2553,7 +3463,8 @@ async function scanEstate(o) {
|
|
|
2553
3463
|
}));
|
|
2554
3464
|
const brokenDialects = /* @__PURE__ */ new Set();
|
|
2555
3465
|
const dialects = await usableDialects(op, orgPackDir, problems, brokenDialects);
|
|
2556
|
-
const
|
|
3466
|
+
const off = stackHelpersOff();
|
|
3467
|
+
const frameworks = off.has("framework-models") ? [] : await usableFrameworks(op, problems);
|
|
2557
3468
|
const pluginSet = await usablePlugins(op);
|
|
2558
3469
|
problems.push(...pluginSet.problems);
|
|
2559
3470
|
const byName = new Map(dialects.map((d) => [d.name, d]));
|
|
@@ -2563,6 +3474,7 @@ async function scanEstate(o) {
|
|
|
2563
3474
|
const repos = productionRepos(op, o.repos);
|
|
2564
3475
|
const results = [];
|
|
2565
3476
|
const factNotes = prototypeScanNotes(op, o.repos);
|
|
3477
|
+
const basis = {};
|
|
2566
3478
|
const factRepos = [];
|
|
2567
3479
|
const structuredOf = /* @__PURE__ */ new Map();
|
|
2568
3480
|
const modulesOf = /* @__PURE__ */ new Map();
|
|
@@ -2570,7 +3482,7 @@ async function scanEstate(o) {
|
|
|
2570
3482
|
let done = 0;
|
|
2571
3483
|
for (const r of repos) {
|
|
2572
3484
|
throwIfCancelled();
|
|
2573
|
-
const dir = o.repoDir?.(r.id) ??
|
|
3485
|
+
const dir = o.repoDir?.(r.id) ?? inPlaceDir(o.root, r.url) ?? baseClonePath(o.root, r.id);
|
|
2574
3486
|
let state;
|
|
2575
3487
|
if (!o.repoState && !await stat(dir).catch(() => void 0)) {
|
|
2576
3488
|
problems.push({
|
|
@@ -2590,7 +3502,7 @@ async function scanEstate(o) {
|
|
|
2590
3502
|
});
|
|
2591
3503
|
continue;
|
|
2592
3504
|
}
|
|
2593
|
-
const extractors = [
|
|
3505
|
+
const extractors = off.has("readers") ? [] : [
|
|
2594
3506
|
...BUILT_IN_EXTRACTORS,
|
|
2595
3507
|
...o.extractors ?? [],
|
|
2596
3508
|
...dialectExtractors.filter(({ d }) => r.dialects.includes(d.name) || d.appliesTo.repoTypes.includes(r.type) || // "*": any repo where the marker matches (ADR 0062).
|
|
@@ -2670,6 +3582,7 @@ async function scanEstate(o) {
|
|
|
2670
3582
|
});
|
|
2671
3583
|
if (fromFacts.note)
|
|
2672
3584
|
factNotes.push(fromFacts.note);
|
|
3585
|
+
basis[r.id] = fromFacts.records && fromFacts.basis ? fromFacts.basis : { from: "readers" };
|
|
2673
3586
|
if (fromFacts.facts)
|
|
2674
3587
|
factRepos.push({ repo: r.id, facts: fromFacts.facts, targets: fromFacts.targets ?? {} });
|
|
2675
3588
|
results.push({ ...scan, records: fromFacts.records ?? readerRecords });
|
|
@@ -2682,10 +3595,17 @@ async function scanEstate(o) {
|
|
|
2682
3595
|
const join10 = joinFacts({
|
|
2683
3596
|
repos: joinRepos,
|
|
2684
3597
|
targets: Object.fromEntries(factRepos.map((x) => [x.repo, x.targets])),
|
|
2685
|
-
outside: outsideSystems(op)
|
|
3598
|
+
outside: outsideSystems(op),
|
|
3599
|
+
...joinConventions(op),
|
|
3600
|
+
walkGateways: helperOn("gateway-walk")
|
|
2686
3601
|
});
|
|
2687
3602
|
codeRecords = applyFactJoins(codeRecords, join10, joinRepos, {
|
|
2688
|
-
callers: joinCallers({
|
|
3603
|
+
callers: joinCallers({
|
|
3604
|
+
repos: joinRepos,
|
|
3605
|
+
join: join10,
|
|
3606
|
+
declared: declaredCallsOf(op.profiles),
|
|
3607
|
+
...joinConventions(op)
|
|
3608
|
+
}),
|
|
2689
3609
|
join: join10
|
|
2690
3610
|
});
|
|
2691
3611
|
}
|
|
@@ -2739,7 +3659,7 @@ async function scanEstate(o) {
|
|
|
2739
3659
|
orgPackDir
|
|
2740
3660
|
});
|
|
2741
3661
|
const notes = [...factNotes, ...merged.notes, ...glossary.problems.map((p) => `glossary: ${p}`)];
|
|
2742
|
-
const withFeatures = [...glossary.records, ...fedRecords].sort((a, b) => a.kind === b.kind ? a.id
|
|
3662
|
+
const withFeatures = [...glossary.records, ...fedRecords].sort((a, b) => a.kind === b.kind ? cmpText(a.id, b.id) : cmpText(a.kind, b.kind));
|
|
2743
3663
|
const applied = applyCurated(withFeatures, curated.records);
|
|
2744
3664
|
for (const p of applied.problems)
|
|
2745
3665
|
problems.push({ source: "curated", message: JSON.stringify(p) });
|
|
@@ -2800,7 +3720,8 @@ async function scanEstate(o) {
|
|
|
2800
3720
|
...inputs ? { inputs } : {},
|
|
2801
3721
|
curated: { applied: applied.applied, suppressed: applied.suppressed },
|
|
2802
3722
|
notes,
|
|
2803
|
-
...federated ? { federated } : {}
|
|
3723
|
+
...federated ? { federated } : {},
|
|
3724
|
+
basis
|
|
2804
3725
|
};
|
|
2805
3726
|
}
|
|
2806
3727
|
|
|
@@ -2836,8 +3757,8 @@ var RepoFiles = class {
|
|
|
2836
3757
|
}
|
|
2837
3758
|
async linesOf(path) {
|
|
2838
3759
|
if (!this.lines.has(path)) {
|
|
2839
|
-
const
|
|
2840
|
-
this.lines.set(path,
|
|
3760
|
+
const text2 = await this.repo.readText(path).catch(() => void 0);
|
|
3761
|
+
this.lines.set(path, text2 === void 0 ? null : text2.replace(/\r\n?/g, "\n").split("\n"));
|
|
2841
3762
|
}
|
|
2842
3763
|
return this.lines.get(path);
|
|
2843
3764
|
}
|
|
@@ -2913,13 +3834,13 @@ async function scoreType(t, files) {
|
|
|
2913
3834
|
};
|
|
2914
3835
|
}
|
|
2915
3836
|
function typeFromWords(description, types) {
|
|
2916
|
-
const
|
|
2917
|
-
if (!
|
|
3837
|
+
const text2 = (description ?? "").toLowerCase();
|
|
3838
|
+
if (!text2)
|
|
2918
3839
|
return void 0;
|
|
2919
3840
|
const hits = [];
|
|
2920
3841
|
for (const t of types)
|
|
2921
3842
|
for (const words2 of [t.name.toLowerCase(), t.id.replace(/[-_.]+/g, " ")])
|
|
2922
|
-
if (words2.length > 2 && new RegExp(`(^|[^a-z0-9])${escapeRe(words2)}([^a-z0-9]|$)`).test(
|
|
3843
|
+
if (words2.length > 2 && new RegExp(`(^|[^a-z0-9])${escapeRe(words2)}([^a-z0-9]|$)`).test(text2))
|
|
2923
3844
|
hits.push({ type: t.id, words: words2 });
|
|
2924
3845
|
const ids = new Set(hits.map((h) => h.type));
|
|
2925
3846
|
if (ids.size !== 1)
|
|
@@ -2932,7 +3853,7 @@ async function matchRepoType(repo, types, o = {}) {
|
|
|
2932
3853
|
for (const t of types)
|
|
2933
3854
|
matches.push(await scoreType(t, files));
|
|
2934
3855
|
const pct = (m) => Math.round(m.score * 100);
|
|
2935
|
-
matches.sort((a, b) => Number(a.ruledOut) - Number(b.ruledOut) || pct(b) - pct(a) || a.type
|
|
3856
|
+
matches.sort((a, b) => Number(a.ruledOut) - Number(b.ruledOut) || pct(b) - pct(a) || cmpText(a.type, b.type));
|
|
2936
3857
|
const said = typeFromWords(o.description, types);
|
|
2937
3858
|
const eligible = matches.filter((m) => !m.ruledOut && pct(m) >= PARTIAL_MATCH * 100);
|
|
2938
3859
|
const top2 = eligible[0];
|
|
@@ -2975,8 +3896,8 @@ async function readRule(rule, path, files) {
|
|
|
2975
3896
|
const key = rule.key;
|
|
2976
3897
|
if (/\.ya?ml$/i.test(path)) {
|
|
2977
3898
|
const counter = new import_yaml3.LineCounter();
|
|
2978
|
-
const
|
|
2979
|
-
for (const doc of (0, import_yaml3.parseAllDocuments)(
|
|
3899
|
+
const text2 = lines.join("\n");
|
|
3900
|
+
for (const doc of (0, import_yaml3.parseAllDocuments)(text2, { lineCounter: counter })) {
|
|
2980
3901
|
if (!("getIn" in doc))
|
|
2981
3902
|
continue;
|
|
2982
3903
|
for (const node of [doc.getIn(key.split("."), true), doc.get(key, true)]) {
|
|
@@ -3015,8 +3936,8 @@ async function resolveSettingsTemplate(t, repo, repoId) {
|
|
|
3015
3936
|
out.exclude.push({ path: e.path, why: e.why, at: `${first}:1` });
|
|
3016
3937
|
}
|
|
3017
3938
|
for (const rule of t.settings.prefixes) {
|
|
3018
|
-
const
|
|
3019
|
-
for (const f of
|
|
3939
|
+
const candidates2 = (await files.matching(rule.files)).sort((a, b) => a.length - b.length || cmpText(a, b));
|
|
3940
|
+
for (const f of candidates2) {
|
|
3020
3941
|
const hit = await readRule(rule, f, files);
|
|
3021
3942
|
if (!hit)
|
|
3022
3943
|
continue;
|
|
@@ -3049,15 +3970,15 @@ var BUILD_FILES = [
|
|
|
3049
3970
|
["composer.json", "composer.json"]
|
|
3050
3971
|
].map(([name, glob]) => ({ name, glob, re: globToRegExp(`**/${glob}`) }));
|
|
3051
3972
|
var PACKAGE_NAME = /^[A-Za-z0-9_.@/-]+$/;
|
|
3052
|
-
function declaredDependencies(path,
|
|
3973
|
+
function declaredDependencies(path, text2) {
|
|
3053
3974
|
const out = [];
|
|
3054
3975
|
const all = (re, g = 1) => {
|
|
3055
|
-
for (const m of
|
|
3976
|
+
for (const m of text2.matchAll(re))
|
|
3056
3977
|
if (m[g])
|
|
3057
3978
|
out.push(m[g].trim());
|
|
3058
3979
|
};
|
|
3059
3980
|
if (path.endsWith("pom.xml")) {
|
|
3060
|
-
for (const block2 of
|
|
3981
|
+
for (const block2 of text2.matchAll(/<(parent|dependency|plugin)>([\s\S]*?)<\/\1>/g)) {
|
|
3061
3982
|
const a = /<artifactId>\s*([^<\s]+)\s*<\/artifactId>/.exec(block2[2]);
|
|
3062
3983
|
if (a)
|
|
3063
3984
|
out.push(a[1]);
|
|
@@ -3066,7 +3987,7 @@ function declaredDependencies(path, text) {
|
|
|
3066
3987
|
all(/['"]([\w.-]+):([\w.-]+)(?::[^'"]*)?['"]/g, 2);
|
|
3067
3988
|
} else if (path.endsWith("package.json")) {
|
|
3068
3989
|
try {
|
|
3069
|
-
const j = JSON.parse(
|
|
3990
|
+
const j = JSON.parse(text2);
|
|
3070
3991
|
const deps = Object.keys(j.dependencies ?? {});
|
|
3071
3992
|
out.push(...deps.length ? deps : Object.keys(j.devDependencies ?? {}));
|
|
3072
3993
|
} catch {
|
|
@@ -3090,7 +4011,7 @@ var PLAIN_ANNOTATIONS = /* @__PURE__ */ new Set([
|
|
|
3090
4011
|
"JvmStatic"
|
|
3091
4012
|
]);
|
|
3092
4013
|
function top(counts, n) {
|
|
3093
|
-
return [...counts].sort((a, b) => b[1] - a[1] || a[0]
|
|
4014
|
+
return [...counts].sort((a, b) => b[1] - a[1] || cmpText(a[0], b[0])).slice(0, n).map(([k]) => k);
|
|
3094
4015
|
}
|
|
3095
4016
|
var TEST_FILE = /(^|\/)(test|tests|__tests__|src\/test)\/|\.(spec|test)\.[jt]sx?$|Tests?\.(java|kt|cs)$/;
|
|
3096
4017
|
async function draftMarkers(files) {
|
|
@@ -3176,9 +4097,9 @@ async function draftRepoType(repo, id) {
|
|
|
3176
4097
|
fingerprint.push({ kind: "build-file", globs: buildGlobs, weight: 2, required: true });
|
|
3177
4098
|
const deps = /* @__PURE__ */ new Map();
|
|
3178
4099
|
for (const f of builds) {
|
|
3179
|
-
const
|
|
4100
|
+
const text2 = (await files.linesOf(f) ?? []).join("\n");
|
|
3180
4101
|
const glob = buildGlobs.find((g) => globToRegExp(g).test(f));
|
|
3181
|
-
for (const d of declaredDependencies(f,
|
|
4102
|
+
for (const d of declaredDependencies(f, text2)) {
|
|
3182
4103
|
if (!PACKAGE_NAME.test(d) || d === repo.name)
|
|
3183
4104
|
continue;
|
|
3184
4105
|
if (!deps.has(d))
|
|
@@ -3318,7 +4239,7 @@ function specTriggers(records, repoId, template) {
|
|
|
3318
4239
|
}
|
|
3319
4240
|
}
|
|
3320
4241
|
}
|
|
3321
|
-
return out.sort((a, b) => a.id
|
|
4242
|
+
return out.sort((a, b) => cmpText(a.id, b.id));
|
|
3322
4243
|
}
|
|
3323
4244
|
var methodPath = (s) => {
|
|
3324
4245
|
const m = /^([A-Za-z]+)\s+(\S+)$/.exec(s.trim());
|
|
@@ -3740,7 +4661,7 @@ function parseEntry(s) {
|
|
|
3740
4661
|
const m = /^([A-Za-z]+)\s+(\S+)$/.exec(s.trim());
|
|
3741
4662
|
return m ? { method: m[1].toUpperCase(), path: m[2] } : { path: s.trim() };
|
|
3742
4663
|
}
|
|
3743
|
-
var
|
|
4664
|
+
var segs2 = (p) => normalizePath(p).toLowerCase().split("/").filter(Boolean);
|
|
3744
4665
|
var sameMethod = (a, b) => !a || !b || a.toUpperCase() === b.toUpperCase();
|
|
3745
4666
|
async function joinEstate(o) {
|
|
3746
4667
|
const byId = new Map(o.records.map((r) => [r.id, r]));
|
|
@@ -3752,8 +4673,8 @@ async function joinEstate(o) {
|
|
|
3752
4673
|
const out = [i.normalizedPath];
|
|
3753
4674
|
const route = i.route ? byId.get(i.route) : void 0;
|
|
3754
4675
|
if (route?.kind === "GatewayRoute") {
|
|
3755
|
-
const base2 =
|
|
3756
|
-
const p =
|
|
4676
|
+
const base2 = segs2(route.basepath);
|
|
4677
|
+
const p = segs2(i.normalizedPath);
|
|
3757
4678
|
if (base2.length && base2.every((s, k) => p[k] === s))
|
|
3758
4679
|
out.push(`/${p.slice(base2.length).join("/")}`);
|
|
3759
4680
|
}
|
|
@@ -3789,7 +4710,7 @@ async function joinEstate(o) {
|
|
|
3789
4710
|
const c = byId.get(id);
|
|
3790
4711
|
return c?.kind === "Component" ? c.repo ?? c.evidence.find((e) => e.repo)?.repo : void 0;
|
|
3791
4712
|
};
|
|
3792
|
-
const servingExactly = (caller, want, scope) => endpoints.filter((e) => e.component !== caller && (!scope || scope.has(e.component)) && sameMethod(want.method, e.method) && pathScore(want.path, e.normalizedPath ?? e.pathOrTopic) === 1e3).sort((a, b) => a.id
|
|
4713
|
+
const servingExactly = (caller, want, scope) => endpoints.filter((e) => e.component !== caller && (!scope || scope.has(e.component)) && sameMethod(want.method, e.method) && pathScore(want.path, e.normalizedPath ?? e.pathOrTopic) === 1e3).sort((a, b) => cmpText(a.id, b.id));
|
|
3793
4714
|
const joins = [];
|
|
3794
4715
|
const unmatched = [];
|
|
3795
4716
|
const inferred = [];
|
|
@@ -3868,27 +4789,27 @@ async function joinEstate(o) {
|
|
|
3868
4789
|
within = "estate";
|
|
3869
4790
|
hits = servingExactly(flow.component, want);
|
|
3870
4791
|
}
|
|
3871
|
-
const
|
|
3872
|
-
if (
|
|
4792
|
+
const candidates2 = [...new Set(hits.map((e) => e.component))].sort();
|
|
4793
|
+
if (candidates2.length === 0) {
|
|
3873
4794
|
miss({
|
|
3874
4795
|
reason: "no-target",
|
|
3875
4796
|
message: `${s.to.component} isn't a component in the catalog, and neither an Interface from ${flow.component} nor an entry point of the estate serves ${s.to.entry}`
|
|
3876
4797
|
});
|
|
3877
4798
|
continue;
|
|
3878
4799
|
}
|
|
3879
|
-
if (
|
|
3880
|
-
const repos = [...new Set(
|
|
4800
|
+
if (candidates2.length > 1) {
|
|
4801
|
+
const repos = [...new Set(candidates2.map(repoOfComponent))];
|
|
3881
4802
|
const of = repos.length === 1 && repos[0] ? repos[0] : "the estate";
|
|
3882
4803
|
miss({
|
|
3883
4804
|
reason: "no-target",
|
|
3884
|
-
message: `${s.to.component} isn't a component in the catalog, and ${
|
|
4805
|
+
message: `${s.to.component} isn't a component in the catalog, and ${candidates2.length} components of ${of} serve ${s.to.entry} (${candidates2.join(", ")}): a person says which`
|
|
3885
4806
|
});
|
|
3886
4807
|
asks.push({
|
|
3887
4808
|
from,
|
|
3888
4809
|
caller: flow.component,
|
|
3889
4810
|
named: s.to.component,
|
|
3890
4811
|
what: s.to.entry,
|
|
3891
|
-
candidates
|
|
4812
|
+
candidates: candidates2
|
|
3892
4813
|
});
|
|
3893
4814
|
continue;
|
|
3894
4815
|
}
|
|
@@ -3961,7 +4882,7 @@ async function joinEstate(o) {
|
|
|
3961
4882
|
inferred.push({ from: { repo, flow: flow.id, step }, named: s.to.component, ...guess });
|
|
3962
4883
|
}
|
|
3963
4884
|
}
|
|
3964
|
-
const order = (a, b) => a.from.flow
|
|
4885
|
+
const order = (a, b) => cmpText(a.from.flow, b.from.flow) || a.from.step - b.from.step;
|
|
3965
4886
|
return {
|
|
3966
4887
|
joins: joins.sort(order),
|
|
3967
4888
|
unmatched: unmatched.sort(order),
|
|
@@ -4116,6 +5037,17 @@ function fieldLineage(f, field) {
|
|
|
4116
5037
|
}
|
|
4117
5038
|
|
|
4118
5039
|
// packages/scanner/dist/hosts-asked.js
|
|
5040
|
+
var segs3 = (p) => p.split(/[?#]/)[0].split("/").filter(Boolean).map((x) => /^[{:<$]/.test(x) ? "{}" : x);
|
|
5041
|
+
var underPath = (path, base2) => {
|
|
5042
|
+
const b = segs3(base2);
|
|
5043
|
+
const c = segs3(path);
|
|
5044
|
+
return b.length > 0 && b.length <= c.length && b.every((x, i) => x === c[i]);
|
|
5045
|
+
};
|
|
5046
|
+
var servesPath = (entry, path) => {
|
|
5047
|
+
const e = segs3(entry);
|
|
5048
|
+
const c = segs3(path);
|
|
5049
|
+
return e.length === c.length && e.every((x, i) => x === "{}" || c[i] === "{}" || x === c[i]);
|
|
5050
|
+
};
|
|
4119
5051
|
var HTTP_TYPES = /* @__PURE__ */ new Set(["rest", "graphql", "soap", "websocket", "sse", "webhook"]);
|
|
4120
5052
|
var CONFIG_FILES = [
|
|
4121
5053
|
"**/application*.properties",
|
|
@@ -4178,8 +5110,12 @@ async function unknownHosts(o) {
|
|
|
4178
5110
|
const entry = byHost.get(h.label) ?? {
|
|
4179
5111
|
...h.port !== void 0 ? { port: h.port } : {},
|
|
4180
5112
|
explicit: h.label !== h.host,
|
|
4181
|
-
calls: []
|
|
5113
|
+
calls: [],
|
|
5114
|
+
paths: /* @__PURE__ */ new Set()
|
|
4182
5115
|
};
|
|
5116
|
+
const path = /^[a-z][a-z0-9+.-]*:\/\/[^/?#]*(\/[^?#]*)?/i.exec(url)?.[1];
|
|
5117
|
+
if (path)
|
|
5118
|
+
entry.paths.add(path);
|
|
4183
5119
|
const at3 = `${e.path}:${e.line}`;
|
|
4184
5120
|
if (!entry.calls.some((c) => c.repo === repo && c.at === at3))
|
|
4185
5121
|
entry.calls.push({ repo, at: at3 });
|
|
@@ -4217,7 +5153,7 @@ async function unknownHosts(o) {
|
|
|
4217
5153
|
return out;
|
|
4218
5154
|
};
|
|
4219
5155
|
const asks = [];
|
|
4220
|
-
for (const [host, entry] of [...byHost.entries()].sort(([a], [b]) => a
|
|
5156
|
+
for (const [host, entry] of [...byHost.entries()].sort(([a], [b]) => cmpText(a, b))) {
|
|
4221
5157
|
if (servers.length === 0)
|
|
4222
5158
|
continue;
|
|
4223
5159
|
const hits = [];
|
|
@@ -4231,7 +5167,26 @@ async function unknownHosts(o) {
|
|
|
4231
5167
|
}
|
|
4232
5168
|
}
|
|
4233
5169
|
const suggest = hits.length === 1 ? hits[0] : void 0;
|
|
4234
|
-
const calls = entry.calls.sort((a, b) => a.repo
|
|
5170
|
+
const calls = entry.calls.sort((a, b) => cmpText(a.repo, b.repo) || cmpNatural(a.at, b.at));
|
|
5171
|
+
const byPath = [];
|
|
5172
|
+
for (const g of o.records) {
|
|
5173
|
+
if (g.kind !== "GatewayRoute")
|
|
5174
|
+
continue;
|
|
5175
|
+
const repo = g.evidence.find((e) => e.repo)?.repo;
|
|
5176
|
+
if (!repo || byPath.some((b) => b.repo === repo))
|
|
5177
|
+
continue;
|
|
5178
|
+
const under2 = [...entry.paths].filter((p) => underPath(p, g.basepath));
|
|
5179
|
+
if (!under2.length)
|
|
5180
|
+
continue;
|
|
5181
|
+
const comps = new Set([...compRepo].filter(([, r]) => r === repo).map(([c]) => c));
|
|
5182
|
+
const hit = o.records.find((r) => r.kind === "Endpoint" && comps.has(r.component) && under2.some((p) => servesPath(r.pathOrTopic, p)));
|
|
5183
|
+
byPath.push({
|
|
5184
|
+
repo,
|
|
5185
|
+
serves: hit ? [hit.method?.toUpperCase(), hit.pathOrTopic].filter(Boolean).join(" ") : `the routes under ${g.basepath}`
|
|
5186
|
+
});
|
|
5187
|
+
}
|
|
5188
|
+
byPath.sort((a, b) => cmpText(a.repo, b.repo));
|
|
5189
|
+
const first = suggest ? [suggest.repo] : byPath.map((b) => b.repo).filter((x) => servers.includes(x));
|
|
4235
5190
|
const about = Object.fromEntries(servers.flatMap((s) => {
|
|
4236
5191
|
const d = o.repos.find((r) => r.id === s)?.description;
|
|
4237
5192
|
return d ? [[s, { text: d.text, trust: d.trust }]] : [];
|
|
@@ -4239,9 +5194,10 @@ async function unknownHosts(o) {
|
|
|
4239
5194
|
asks.push({
|
|
4240
5195
|
host,
|
|
4241
5196
|
calls,
|
|
4242
|
-
servers:
|
|
5197
|
+
servers: [...first, ...servers.filter((s) => !first.includes(s))],
|
|
4243
5198
|
...suggest ? { suggest } : {},
|
|
4244
|
-
...Object.keys(about).length ? { about } : {}
|
|
5199
|
+
...Object.keys(about).length ? { about } : {},
|
|
5200
|
+
...byPath.length ? { byPath } : {}
|
|
4245
5201
|
});
|
|
4246
5202
|
}
|
|
4247
5203
|
return asks;
|
|
@@ -4252,23 +5208,38 @@ function aboutOf(a, repo) {
|
|
|
4252
5208
|
return "";
|
|
4253
5209
|
return d.trust === "suggested" ? `. What it is, as your assistant drafted it (unconfirmed): ${d.text}` : `. What it is: ${d.text}`;
|
|
4254
5210
|
}
|
|
5211
|
+
function gatewayHostFix(host, byPath) {
|
|
5212
|
+
const repos = byPath.map((b) => b.repo);
|
|
5213
|
+
return `${repos.length === 1 ? `${repos[0]}'s routes serve` : `The routes of ${repos.join(", ")} serve`} the paths the calls to ${host} make, and no repo lists ${host}: it looks like a shared gateway's host. Once, not per call: list ${host} under hosts: on each proxy repo in repos.manifest.yaml (${repos.join(", ")}, and any other proxy on it); the path then picks the proxy, and this question retires itself`;
|
|
5214
|
+
}
|
|
4255
5215
|
function hostQuestionDrafts(asks) {
|
|
4256
5216
|
return asks.map((a) => {
|
|
4257
5217
|
const repo = a.calls[0].repo;
|
|
5218
|
+
const byPath = new Map((a.byPath ?? []).map((b) => [b.repo, b.serves]));
|
|
4258
5219
|
return {
|
|
4259
5220
|
repo,
|
|
5221
|
+
...a.byPath?.length ? { byPath: a.byPath } : {},
|
|
4260
5222
|
draft: {
|
|
4261
5223
|
question: hostQuestionText(a.host),
|
|
4262
5224
|
options: [
|
|
4263
5225
|
...a.servers.map((s) => ({
|
|
4264
5226
|
label: s,
|
|
4265
|
-
means: (a.suggest?.repo === s ? `${s} serves HTTP, and its ${a.suggest.at} sets the port ${a.host.split(":").pop()} (${a.suggest.line})` : `${s} serves HTTP`) + aboutOf(a, s),
|
|
4266
|
-
changes: `${a.host} goes under hosts for ${s} in repos.manifest.yaml, and the calls to it join ${s} on the next scan`
|
|
5227
|
+
means: (a.suggest?.repo === s ? `${s} serves HTTP, and its ${a.suggest.at} sets the port ${a.host.split(":").pop()} (${a.suggest.line})` : byPath.has(s) ? `${s} serves ${byPath.get(s)}, a path the calls to ${a.host} make` : `${s} serves HTTP`) + aboutOf(a, s),
|
|
5228
|
+
changes: byPath.has(s) ? `${a.host} goes under hosts for ${s} in repos.manifest.yaml, and the calls to it join ${s} on the next scan; on a shared gateway host, list it on each proxy repo instead (${[...byPath.keys()].join(", ")}, and any other proxy on it), and the path picks the proxy` : `${a.host} goes under hosts for ${s} in repos.manifest.yaml, and the calls to it join ${s} on the next scan`
|
|
5229
|
+
})),
|
|
5230
|
+
// O1: a system outside the estate, declared in the profile with the host as its alias.
|
|
5231
|
+
...[
|
|
5232
|
+
[HOSTS_OUTSIDE_INTERNAL, "internal", "another team's system"],
|
|
5233
|
+
[HOSTS_OUTSIDE_VENDOR, "vendor", "a vendor's system"]
|
|
5234
|
+
].map(([label2, boundary, what2]) => ({
|
|
5235
|
+
label: label2,
|
|
5236
|
+
means: `${a.host} is ${what2}, outside this estate, that the org pack doesn't name yet`,
|
|
5237
|
+
changes: `A system ${outsideSystemOf(a.host).id} (boundary ${boundary}, alias ${a.host}) goes into the profile, and the calls to ${a.host} go to it on the next scan`
|
|
4267
5238
|
})),
|
|
4268
5239
|
{
|
|
4269
5240
|
label: HOSTS_NONE,
|
|
4270
|
-
means: `
|
|
4271
|
-
changes: "Nothing is added to the manifest; the calls stay loose ends, and this isn't asked again"
|
|
5241
|
+
means: `Neither a repo of this estate nor a system to add to the org pack answers on ${a.host}`,
|
|
5242
|
+
changes: "Nothing is added to the manifest or the profile; the calls stay loose ends, and this isn't asked again"
|
|
4272
5243
|
}
|
|
4273
5244
|
],
|
|
4274
5245
|
evidence: a.calls.filter((c) => c.repo === repo).slice(0, 3).map((c) => c.at),
|
|
@@ -4278,6 +5249,375 @@ function hostQuestionDrafts(asks) {
|
|
|
4278
5249
|
});
|
|
4279
5250
|
}
|
|
4280
5251
|
|
|
5252
|
+
// packages/scanner/dist/measure-links.js
|
|
5253
|
+
var import_yaml4 = __toESM(require_dist(), 1);
|
|
5254
|
+
var METHODS = /^(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS|TRACE|ANY)\s+(\S.*)$/i;
|
|
5255
|
+
var NEAR = 3;
|
|
5256
|
+
var AT = /^.+:\d+$/;
|
|
5257
|
+
var AT_HELP = "at is file:line, e.g. src/main/java/Client.java:16";
|
|
5258
|
+
var isObj2 = (x) => typeof x === "object" && x !== null && !Array.isArray(x);
|
|
5259
|
+
var text = (x) => typeof x === "string" && x.trim() ? x.trim() : typeof x === "number" ? String(x) : void 0;
|
|
5260
|
+
function parseLinkReference(source) {
|
|
5261
|
+
let raw;
|
|
5262
|
+
try {
|
|
5263
|
+
raw = (0, import_yaml4.parse)(source);
|
|
5264
|
+
} catch (e) {
|
|
5265
|
+
return { ok: false, issues: [`the file isn't YAML: ${e.message}`] };
|
|
5266
|
+
}
|
|
5267
|
+
const issues = [];
|
|
5268
|
+
const say = (where, message) => issues.push(`${where}: ${message}`);
|
|
5269
|
+
const only = (where, o, keys) => {
|
|
5270
|
+
for (const k of Object.keys(o))
|
|
5271
|
+
if (!keys.includes(k))
|
|
5272
|
+
say(where, `unknown key ${k}`);
|
|
5273
|
+
};
|
|
5274
|
+
if (!isObj2(raw) || !Array.isArray(raw.repos))
|
|
5275
|
+
return { ok: false, issues: ["repos: list the repos, each with its entries and calls"] };
|
|
5276
|
+
only("(top)", raw, ["estate", "repos"]);
|
|
5277
|
+
const repos = [];
|
|
5278
|
+
raw.repos.forEach((r, i) => {
|
|
5279
|
+
const w = `repos.${i}`;
|
|
5280
|
+
if (!isObj2(r) || !text(r.repo))
|
|
5281
|
+
return say(w, "each repo needs repo: <its id>");
|
|
5282
|
+
only(w, r, ["repo", "entries", "calls"]);
|
|
5283
|
+
const entries = [];
|
|
5284
|
+
const calls = [];
|
|
5285
|
+
const list = (x, name) => {
|
|
5286
|
+
if (x === void 0 || x === null)
|
|
5287
|
+
return [];
|
|
5288
|
+
if (Array.isArray(x))
|
|
5289
|
+
return x;
|
|
5290
|
+
say(`${w}.${name}`, `${name} is a list`);
|
|
5291
|
+
return [];
|
|
5292
|
+
};
|
|
5293
|
+
list(r.entries, "entries").forEach((e, j) => {
|
|
5294
|
+
const we = `${w}.entries.${j}`;
|
|
5295
|
+
if (text(e))
|
|
5296
|
+
return entries.push({ entry: text(e) });
|
|
5297
|
+
if (!isObj2(e))
|
|
5298
|
+
return say(we, "an entry is METHOD /path, or { entry, at, fact }");
|
|
5299
|
+
only(we, e, ["entry", "at", "fact"]);
|
|
5300
|
+
const at3 = text(e.at);
|
|
5301
|
+
if (at3 && !AT.test(at3))
|
|
5302
|
+
return say(`${we}.at`, AT_HELP);
|
|
5303
|
+
if (!text(e.entry) && !at3 && !text(e.fact))
|
|
5304
|
+
return say(we, "give the entry (METHOD /path), its line (at: file:line) or its fact id");
|
|
5305
|
+
entries.push(stripped({ entry: text(e.entry), at: at3, fact: text(e.fact) }));
|
|
5306
|
+
});
|
|
5307
|
+
list(r.calls, "calls").forEach((c, j) => {
|
|
5308
|
+
const wc = `${w}.calls.${j}`;
|
|
5309
|
+
if (!isObj2(c))
|
|
5310
|
+
return say(wc, "a call is { call, at, reaches } or { call, at, outside }");
|
|
5311
|
+
only(wc, c, ["call", "at", "fact", "reaches", "outside", "note"]);
|
|
5312
|
+
const reaches = text(c.reaches);
|
|
5313
|
+
const outside = text(c.outside);
|
|
5314
|
+
const at3 = text(c.at);
|
|
5315
|
+
let bad = false;
|
|
5316
|
+
if (reaches && outside) {
|
|
5317
|
+
say(wc, "give one of reaches (a repo, or none) and outside (a system), not both");
|
|
5318
|
+
bad = true;
|
|
5319
|
+
} else if (!reaches && !outside) {
|
|
5320
|
+
say(wc, "give one of reaches (a repo, or none) and outside (a system)");
|
|
5321
|
+
bad = true;
|
|
5322
|
+
}
|
|
5323
|
+
if (!text(c.call) && !at3 && !text(c.fact)) {
|
|
5324
|
+
say(wc, "give the call (METHOD /path), its line (at: file:line) or its fact id");
|
|
5325
|
+
bad = true;
|
|
5326
|
+
}
|
|
5327
|
+
if (at3 && !AT.test(at3)) {
|
|
5328
|
+
say(`${wc}.at`, AT_HELP);
|
|
5329
|
+
bad = true;
|
|
5330
|
+
}
|
|
5331
|
+
if (bad)
|
|
5332
|
+
return;
|
|
5333
|
+
const want = outside ? { outside } : reaches.toLowerCase() === "none" ? { none: true } : { repo: reaches };
|
|
5334
|
+
calls.push({
|
|
5335
|
+
...stripped({ call: text(c.call), at: at3, fact: text(c.fact) }),
|
|
5336
|
+
want
|
|
5337
|
+
});
|
|
5338
|
+
});
|
|
5339
|
+
repos.push({ repo: text(r.repo), entries, calls });
|
|
5340
|
+
});
|
|
5341
|
+
if (issues.length)
|
|
5342
|
+
return { ok: false, issues };
|
|
5343
|
+
const estate = text(raw.estate);
|
|
5344
|
+
return { ok: true, reference: { ...estate ? { estate } : {}, repos } };
|
|
5345
|
+
}
|
|
5346
|
+
var stripped = (o) => Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0));
|
|
5347
|
+
function segs4(path) {
|
|
5348
|
+
return path.split(/[?#]/)[0].split("/").filter(Boolean).map((s) => /^(\{[^}]*\}|:\w+|\*)$/.test(s) ? "{}" : s.toLowerCase());
|
|
5349
|
+
}
|
|
5350
|
+
function pathMatch(a, b) {
|
|
5351
|
+
const x = segs4(a);
|
|
5352
|
+
const y = segs4(b);
|
|
5353
|
+
if (x.length === y.length && x.every((s, i) => s === y[i]))
|
|
5354
|
+
return 2;
|
|
5355
|
+
const [short, long] = x.length < y.length ? [x, y] : [y, x];
|
|
5356
|
+
if (!short.length)
|
|
5357
|
+
return 0;
|
|
5358
|
+
const off = long.length - short.length;
|
|
5359
|
+
return short.every((s, i) => s === long[off + i]) ? 1 : 0;
|
|
5360
|
+
}
|
|
5361
|
+
function parseWhat(what2) {
|
|
5362
|
+
if (!what2)
|
|
5363
|
+
return {};
|
|
5364
|
+
const m = METHODS.exec(what2.trim());
|
|
5365
|
+
return m ? { method: m[1].toUpperCase(), path: m[2].trim() } : { path: what2.trim() };
|
|
5366
|
+
}
|
|
5367
|
+
function parseAt(at3) {
|
|
5368
|
+
if (!at3)
|
|
5369
|
+
return void 0;
|
|
5370
|
+
const i = at3.lastIndexOf(":");
|
|
5371
|
+
return { file: at3.slice(0, i).replace(/^\.\//, ""), line: Number(at3.slice(i + 1)) };
|
|
5372
|
+
}
|
|
5373
|
+
function finder(facts) {
|
|
5374
|
+
const used = /* @__PURE__ */ new Set();
|
|
5375
|
+
return (ref) => {
|
|
5376
|
+
const { method, path } = parseWhat(ref.what);
|
|
5377
|
+
const at3 = parseAt(ref.at);
|
|
5378
|
+
let best;
|
|
5379
|
+
for (const f of facts) {
|
|
5380
|
+
if (used.has(f.id))
|
|
5381
|
+
continue;
|
|
5382
|
+
if (ref.fact) {
|
|
5383
|
+
if (f.id === ref.fact)
|
|
5384
|
+
best = { f, score: 1e6 };
|
|
5385
|
+
continue;
|
|
5386
|
+
}
|
|
5387
|
+
let score = 0;
|
|
5388
|
+
if (at3) {
|
|
5389
|
+
const d = Math.abs(f.cite.line - at3.line);
|
|
5390
|
+
if (f.cite.file !== at3.file || d > NEAR)
|
|
5391
|
+
continue;
|
|
5392
|
+
score += 10 * (NEAR + 1 - d);
|
|
5393
|
+
}
|
|
5394
|
+
if (method && f.method && f.method.toUpperCase() !== method && method !== "ANY")
|
|
5395
|
+
continue;
|
|
5396
|
+
if (path) {
|
|
5397
|
+
const p = pathMatch(path, f.path);
|
|
5398
|
+
if (!p)
|
|
5399
|
+
continue;
|
|
5400
|
+
score += 100 * p;
|
|
5401
|
+
}
|
|
5402
|
+
if (!best || score > best.score)
|
|
5403
|
+
best = { f, score };
|
|
5404
|
+
}
|
|
5405
|
+
if (best)
|
|
5406
|
+
used.add(best.f.id);
|
|
5407
|
+
return best?.f;
|
|
5408
|
+
};
|
|
5409
|
+
}
|
|
5410
|
+
var round = (n, d) => d ? Math.round(n / d * 1e3) / 1e3 : null;
|
|
5411
|
+
var what = (f) => [f.method?.toUpperCase(), f.path].filter(Boolean).join(" ");
|
|
5412
|
+
function measureLinks(input) {
|
|
5413
|
+
const { op } = input;
|
|
5414
|
+
const repos = input.repos.filter((x) => !isPrototypeRepo(op, x.repo));
|
|
5415
|
+
const renames = componentRenames(repos, op);
|
|
5416
|
+
const join10 = joinFacts({
|
|
5417
|
+
repos: joinReposOf(op, repos, renames),
|
|
5418
|
+
...input.targets ? { targets: input.targets } : {},
|
|
5419
|
+
outside: outsideSystems(op),
|
|
5420
|
+
...joinConventions(op),
|
|
5421
|
+
walkGateways: input.walkGateways ?? helperOn("gateway-walk")
|
|
5422
|
+
});
|
|
5423
|
+
const key = (repo, fact) => `${repo}\0${fact}`;
|
|
5424
|
+
const joins = /* @__PURE__ */ new Map();
|
|
5425
|
+
for (const j of join10.joins) {
|
|
5426
|
+
const k = key(j.call.repo, j.call.fact);
|
|
5427
|
+
joins.set(k, [...joins.get(k) ?? [], j]);
|
|
5428
|
+
}
|
|
5429
|
+
const asks = new Map(join10.asks.map((a) => [key(a.call.repo, a.call.fact), a]));
|
|
5430
|
+
const misses = new Map(join10.misses.map((m) => [key(m.call.repo, m.call.fact), m]));
|
|
5431
|
+
const outsideOf = new Map((join10.outside ?? []).map((o) => [key(o.call.repo, o.call.fact), o]));
|
|
5432
|
+
const own = new Set(estateRepos(op).flatMap((r) => r.system ? [r.system] : []));
|
|
5433
|
+
const systemOf = /* @__PURE__ */ new Map();
|
|
5434
|
+
for (const s of op.profiles.flatMap((p) => p.profile?.systems ?? []))
|
|
5435
|
+
if (s.boundary === "vendor" || !own.has(s.id))
|
|
5436
|
+
for (const n of [s.id, s.name, ...s.aliases])
|
|
5437
|
+
systemOf.set(factIdPart(n), s.id);
|
|
5438
|
+
const outsideName = (n) => n ? systemOf.get(factIdPart(n)) : void 0;
|
|
5439
|
+
const manifests = estateRepos(op);
|
|
5440
|
+
const repoOf = (name) => {
|
|
5441
|
+
const slug = factIdPart(name);
|
|
5442
|
+
const m = manifests.find((r) => r.id === name || factIdPart(r.id) === slug) ?? manifests.find((r) => r.component && factIdPart(r.component) === slug);
|
|
5443
|
+
return m?.id ?? name;
|
|
5444
|
+
};
|
|
5445
|
+
const factsOf = new Map(repos.map((r) => [r.repo, r.facts]));
|
|
5446
|
+
const notes = [];
|
|
5447
|
+
const reading = {
|
|
5448
|
+
entries: { want: 0, found: 0 },
|
|
5449
|
+
calls: { want: 0, found: 0 },
|
|
5450
|
+
missed: []
|
|
5451
|
+
};
|
|
5452
|
+
const rows = [];
|
|
5453
|
+
for (const r of input.reference.repos) {
|
|
5454
|
+
const facts = factsOf.get(r.repo);
|
|
5455
|
+
if (!facts)
|
|
5456
|
+
notes.push(`${r.repo}: no verified facts in the estate, so nothing of it is found (atticus map ${r.repo}, then atticus verify ${r.repo}).`);
|
|
5457
|
+
const all = facts ?? [];
|
|
5458
|
+
const findEntry = finder(all.filter((f) => f.kind === "entry"));
|
|
5459
|
+
for (const e of r.entries) {
|
|
5460
|
+
reading.entries.want++;
|
|
5461
|
+
if (findEntry({ what: e.entry, at: e.at, fact: e.fact }))
|
|
5462
|
+
reading.entries.found++;
|
|
5463
|
+
else
|
|
5464
|
+
reading.missed.push(`${r.repo} entry ${e.entry ?? e.fact ?? e.at}`);
|
|
5465
|
+
}
|
|
5466
|
+
const findCall = finder(all.filter((f) => f.kind === "call"));
|
|
5467
|
+
for (const c of r.calls) {
|
|
5468
|
+
reading.calls.want++;
|
|
5469
|
+
const f = findCall({ what: c.call, at: c.at, fact: c.fact });
|
|
5470
|
+
const label2 = c.call ?? (f ? what(f) : c.fact ?? c.at);
|
|
5471
|
+
const base2 = { repo: r.repo, call: label2, ...c.at ? { at: c.at } : {} };
|
|
5472
|
+
if (!f) {
|
|
5473
|
+
reading.missed.push(`${r.repo} call ${label2}`);
|
|
5474
|
+
rows.push({ ...base2, want: c.want, got: { kind: "not-read" }, right: false });
|
|
5475
|
+
continue;
|
|
5476
|
+
}
|
|
5477
|
+
reading.calls.found++;
|
|
5478
|
+
const got = gotOf(r.repo, f, all);
|
|
5479
|
+
rows.push({ ...base2, fact: f.id, want: c.want, got, right: isRight(c.want, got) });
|
|
5480
|
+
}
|
|
5481
|
+
}
|
|
5482
|
+
function gotOf(repo, f, facts) {
|
|
5483
|
+
const k = key(repo, f.id);
|
|
5484
|
+
const j = joins.get(k)?.[0];
|
|
5485
|
+
if (j) {
|
|
5486
|
+
const via = (j.gateway ?? []).map((h) => h.entry.repo);
|
|
5487
|
+
const inferred2 = joinGuessesOf(j)[0];
|
|
5488
|
+
return {
|
|
5489
|
+
kind: "joined",
|
|
5490
|
+
repo: j.entry.repo,
|
|
5491
|
+
component: j.entry.component,
|
|
5492
|
+
entry: [j.method, j.path].filter(Boolean).join(" "),
|
|
5493
|
+
...via.length ? { via } : {},
|
|
5494
|
+
...inferred2 ? { inferred: inferred2 } : {}
|
|
5495
|
+
};
|
|
5496
|
+
}
|
|
5497
|
+
const ask = asks.get(k);
|
|
5498
|
+
if (ask)
|
|
5499
|
+
return { kind: "asked", reason: ask.reason };
|
|
5500
|
+
const miss = misses.get(k);
|
|
5501
|
+
if (miss)
|
|
5502
|
+
return { kind: "missed", target: miss.target };
|
|
5503
|
+
const answer = input.targets?.[repo]?.[f.id];
|
|
5504
|
+
if (answer === NO_TARGET)
|
|
5505
|
+
return { kind: "unlinked" };
|
|
5506
|
+
const host = callHostOf(f, facts);
|
|
5507
|
+
const system = outsideName(answer) ?? outsideName(outsideOf.get(k)?.target) ?? outsideName(f.to) ?? outsideName(host);
|
|
5508
|
+
if (system)
|
|
5509
|
+
return { kind: "outside", system };
|
|
5510
|
+
if (f.to && host)
|
|
5511
|
+
return { kind: "outside", system: f.to };
|
|
5512
|
+
return { kind: "unlinked" };
|
|
5513
|
+
}
|
|
5514
|
+
function isRight(want, got) {
|
|
5515
|
+
if ("repo" in want) {
|
|
5516
|
+
if (got.kind !== "joined")
|
|
5517
|
+
return false;
|
|
5518
|
+
const w = repoOf(want.repo);
|
|
5519
|
+
return got.repo === w || got.component === factIdPart(want.repo);
|
|
5520
|
+
}
|
|
5521
|
+
if ("outside" in want) {
|
|
5522
|
+
if (got.kind !== "outside")
|
|
5523
|
+
return false;
|
|
5524
|
+
const w = outsideName(want.outside) ?? factIdPart(want.outside);
|
|
5525
|
+
return got.system === w || factIdPart(got.system) === factIdPart(want.outside);
|
|
5526
|
+
}
|
|
5527
|
+
return got.kind === "unlinked" || got.kind === "missed";
|
|
5528
|
+
}
|
|
5529
|
+
const toRepo = rows.filter((x) => "repo" in x.want);
|
|
5530
|
+
const joined = rows.filter((x) => x.got.kind === "joined");
|
|
5531
|
+
const right = joined.filter((x) => x.right).length;
|
|
5532
|
+
const inferred = joined.filter((x) => x.got.kind === "joined" && x.got.inferred);
|
|
5533
|
+
const outsideWant = rows.filter((x) => "outside" in x.want);
|
|
5534
|
+
const noneWant = rows.filter((x) => "none" in x.want);
|
|
5535
|
+
return {
|
|
5536
|
+
...input.reference.estate ? { estate: input.reference.estate } : {},
|
|
5537
|
+
reading,
|
|
5538
|
+
links: {
|
|
5539
|
+
want: toRepo.length,
|
|
5540
|
+
joined: joined.length,
|
|
5541
|
+
right,
|
|
5542
|
+
precision: round(right, joined.length),
|
|
5543
|
+
recall: round(toRepo.filter((x) => x.right).length, toRepo.length),
|
|
5544
|
+
asked: rows.filter((x) => x.got.kind === "asked").length,
|
|
5545
|
+
missed: rows.filter((x) => x.got.kind === "missed").length,
|
|
5546
|
+
unlinked: rows.filter((x) => x.got.kind === "unlinked").length,
|
|
5547
|
+
notRead: rows.filter((x) => x.got.kind === "not-read").length,
|
|
5548
|
+
inferred: { joined: inferred.length, right: inferred.filter((x) => x.right).length },
|
|
5549
|
+
outside: {
|
|
5550
|
+
want: outsideWant.length,
|
|
5551
|
+
got: rows.filter((x) => x.got.kind === "outside").length,
|
|
5552
|
+
right: outsideWant.filter((x) => x.right).length
|
|
5553
|
+
},
|
|
5554
|
+
none: { want: noneWant.length, right: noneWant.filter((x) => x.right).length }
|
|
5555
|
+
},
|
|
5556
|
+
calls: rows,
|
|
5557
|
+
notes
|
|
5558
|
+
};
|
|
5559
|
+
}
|
|
5560
|
+
|
|
5561
|
+
// packages/scanner/dist/audit-links.js
|
|
5562
|
+
function estateJoinOf(op, list, targets, pairs, aiTargets) {
|
|
5563
|
+
const repos = list.filter((x) => !isPrototypeRepo(op, x.repo));
|
|
5564
|
+
const renames = componentRenames(repos, op);
|
|
5565
|
+
return joinFacts({
|
|
5566
|
+
repos: joinReposOf(op, repos, renames),
|
|
5567
|
+
...targets ? { targets } : {},
|
|
5568
|
+
...aiTargets && Object.keys(aiTargets).length ? { aiTargets } : {},
|
|
5569
|
+
...pairs && Object.keys(pairs).length ? { pairs } : {},
|
|
5570
|
+
outside: outsideSystems(op),
|
|
5571
|
+
// The org pack's facing words and gateway bundle folders (ADR 0147), as the catalog joins.
|
|
5572
|
+
...joinConventions(op),
|
|
5573
|
+
walkGateways: helperOn("gateway-walk")
|
|
5574
|
+
});
|
|
5575
|
+
}
|
|
5576
|
+
function auditLinkCandidates(join10, list, repo) {
|
|
5577
|
+
const facts = /* @__PURE__ */ new Map();
|
|
5578
|
+
for (const r of list)
|
|
5579
|
+
for (const f of r.facts)
|
|
5580
|
+
facts.set(`${r.repo}\0${f.id}`, f);
|
|
5581
|
+
const mine = join10.joins.filter((j) => j.call.repo === repo);
|
|
5582
|
+
const perCall = /* @__PURE__ */ new Map();
|
|
5583
|
+
for (const j of mine)
|
|
5584
|
+
perCall.set(j.call.fact, (perCall.get(j.call.fact) ?? 0) + 1);
|
|
5585
|
+
const at3 = (e) => `${e.repo} ${e.at}`;
|
|
5586
|
+
const out = [];
|
|
5587
|
+
for (const j of mine) {
|
|
5588
|
+
const call = facts.get(`${j.call.repo}\0${j.call.fact}`);
|
|
5589
|
+
const said = [call?.method?.toUpperCase(), call?.path].filter(Boolean).join(" ");
|
|
5590
|
+
const via = (j.gateway ?? []).map((h) => h.entry.repo);
|
|
5591
|
+
const flags = joinFlagsOf(j);
|
|
5592
|
+
const cites = [
|
|
5593
|
+
at3(j.call),
|
|
5594
|
+
...j.tie ? [at3(j.tie)] : [],
|
|
5595
|
+
...(j.through ?? []).map(at3),
|
|
5596
|
+
...(j.exposedBy ?? []).map(at3),
|
|
5597
|
+
...(j.gateway ?? []).flatMap((h) => [
|
|
5598
|
+
at3(h.entry),
|
|
5599
|
+
at3(h.call),
|
|
5600
|
+
...h.tie ? [at3(h.tie)] : [],
|
|
5601
|
+
...(h.through ?? []).map(at3),
|
|
5602
|
+
...(h.exposedBy ?? []).map(at3)
|
|
5603
|
+
]),
|
|
5604
|
+
...(j.entryThrough ?? []).map(at3),
|
|
5605
|
+
at3(j.entry)
|
|
5606
|
+
];
|
|
5607
|
+
out.push({
|
|
5608
|
+
id: `link:${j.call.fact}${(perCall.get(j.call.fact) ?? 0) > 1 ? `~${j.entry.fact}` : ""}`,
|
|
5609
|
+
kind: "link",
|
|
5610
|
+
// A publish joined to its listener carries no flag: its topic says how (ADR 0135).
|
|
5611
|
+
of: flags[0] ?? (j.how === "topic" ? "topic" : `joined-by-${j.how}`),
|
|
5612
|
+
claim: `${repo} ${said || j.call.fact} -> ${j.entry.repo} ${[j.method, j.path].filter(Boolean).join(" ")}${via.length ? ` via ${via.join(", ")}` : ""}`,
|
|
5613
|
+
cites: [...new Set(cites)],
|
|
5614
|
+
fact: j.call.fact,
|
|
5615
|
+
to: j.entry.repo
|
|
5616
|
+
});
|
|
5617
|
+
}
|
|
5618
|
+
return out.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
5619
|
+
}
|
|
5620
|
+
|
|
4281
5621
|
export {
|
|
4282
5622
|
buildGlossary,
|
|
4283
5623
|
GLOSSARY_CHANGE,
|
|
@@ -4300,31 +5640,100 @@ export {
|
|
|
4300
5640
|
diffCatalogs,
|
|
4301
5641
|
diffIsEmpty,
|
|
4302
5642
|
estateRepos,
|
|
5643
|
+
productionRepos,
|
|
5644
|
+
prototypeScanNotes,
|
|
4303
5645
|
isPrototypeRepo,
|
|
5646
|
+
estateMergeInput,
|
|
5647
|
+
mergeEstate,
|
|
5648
|
+
curatedDirOf,
|
|
5649
|
+
TARGETS_MAX_SUGGESTED,
|
|
5650
|
+
TARGETS_ANOTHER,
|
|
5651
|
+
TARGETS_NO_EVIDENCE,
|
|
5652
|
+
nameWords,
|
|
5653
|
+
targetSuggestions,
|
|
5654
|
+
offeredSuggestions,
|
|
5655
|
+
targetPicker,
|
|
5656
|
+
pickerLines,
|
|
5657
|
+
topicQuestionDrafts,
|
|
4304
5658
|
TARGETS_STEP,
|
|
5659
|
+
TARGETS_NONE,
|
|
5660
|
+
remapIds,
|
|
5661
|
+
targetQuestionFor,
|
|
5662
|
+
suggestionWhy,
|
|
4305
5663
|
targetQuestionDrafts,
|
|
5664
|
+
TARGETS_SPLIT,
|
|
5665
|
+
targetPathPrefix,
|
|
5666
|
+
targetGroupQuestion,
|
|
5667
|
+
targetGroupWordings,
|
|
5668
|
+
splitTargetGroups,
|
|
5669
|
+
groupedTargetDrafts,
|
|
5670
|
+
targetRuleOffers,
|
|
5671
|
+
ruleCoversCall,
|
|
5672
|
+
ruledTargets,
|
|
4306
5673
|
targetsFromAnswers,
|
|
5674
|
+
answeredQuestionOfCall,
|
|
5675
|
+
answerReconfirmed,
|
|
5676
|
+
staleTargetQuestions,
|
|
5677
|
+
staleTopicQuestions,
|
|
5678
|
+
componentRenames,
|
|
5679
|
+
factsRecords,
|
|
4307
5680
|
outsideSystems,
|
|
5681
|
+
outsideProfileSystems,
|
|
5682
|
+
hostAliases,
|
|
4308
5683
|
joinReposOf,
|
|
5684
|
+
applyFactJoins,
|
|
4309
5685
|
catalogWithFacts,
|
|
4310
5686
|
prototypeLeftOut,
|
|
5687
|
+
estateFromFacts,
|
|
5688
|
+
answerRuleOffers,
|
|
5689
|
+
factsForScan,
|
|
5690
|
+
pairsOfRepo,
|
|
4311
5691
|
estateFacts,
|
|
5692
|
+
NO_SETTINGS,
|
|
4312
5693
|
citedLine,
|
|
5694
|
+
isCodeFile,
|
|
5695
|
+
isConfigFile,
|
|
5696
|
+
globName,
|
|
5697
|
+
prefixOf,
|
|
4313
5698
|
checkScanSettings,
|
|
5699
|
+
joinBase,
|
|
5700
|
+
applyClients,
|
|
5701
|
+
withExcludes,
|
|
5702
|
+
applyPrefixes,
|
|
5703
|
+
pluginExtractor,
|
|
4314
5704
|
runPluginTests,
|
|
5705
|
+
pluginApplies,
|
|
5706
|
+
usablePlugins,
|
|
5707
|
+
withPluginScan,
|
|
5708
|
+
orgPackDirFor,
|
|
4315
5709
|
scanEstate,
|
|
5710
|
+
CLEAR_MATCH,
|
|
5711
|
+
PARTIAL_MATCH,
|
|
5712
|
+
TIE_MARGIN,
|
|
4316
5713
|
matchRepoType,
|
|
4317
5714
|
resolveSettingsTemplate,
|
|
4318
5715
|
draftRepoType,
|
|
4319
5716
|
repoComponents,
|
|
5717
|
+
pathScore,
|
|
5718
|
+
resourceKey,
|
|
5719
|
+
familyMismatches,
|
|
4320
5720
|
specTriggers,
|
|
5721
|
+
lineNamesUiWord,
|
|
5722
|
+
uiFindings,
|
|
4321
5723
|
checkSpec,
|
|
4322
5724
|
staleFlows,
|
|
4323
5725
|
citedFileHashes,
|
|
4324
5726
|
estateStepKey,
|
|
4325
5727
|
joinEstate,
|
|
4326
5728
|
estateFlow,
|
|
5729
|
+
flattenEstateFlow,
|
|
4327
5730
|
fieldLineage,
|
|
5731
|
+
lineSetsPort,
|
|
4328
5732
|
unknownHosts,
|
|
4329
|
-
|
|
5733
|
+
gatewayHostFix,
|
|
5734
|
+
hostQuestionDrafts,
|
|
5735
|
+
parseLinkReference,
|
|
5736
|
+
measureLinks,
|
|
5737
|
+
estateJoinOf,
|
|
5738
|
+
auditLinkCandidates
|
|
4330
5739
|
};
|