@maycombhq/atticus 2.0.0-beta.2 → 2.0.0-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +8 -4
- package/dist/chunk-265LWMOA.js +91 -0
- package/dist/chunk-26WBY34Y.js +37 -0
- package/dist/{chunk-UBBC2XSG.js → chunk-26Z7OGQW.js} +518 -93
- package/dist/chunk-2D7PWSOK.js +172 -0
- package/dist/{chunk-JCZIKA4C.js → chunk-2HH3UCZM.js} +4 -4
- package/dist/{chunk-GDLULHTC.js → chunk-2HTEMYTB.js} +12 -26
- package/dist/chunk-2UCRFXFZ.js +130 -0
- package/dist/{chunk-MVZVGYXH.js → chunk-2V2ZPQQU.js} +81 -13
- package/dist/{chunk-BOHJZTEQ.js → chunk-36RY7ZXM.js} +130 -71
- package/dist/{chunk-I3E7CX4H.js → chunk-37WE3LS2.js} +3 -3
- package/dist/{chunk-IPKHE5D6.js → chunk-3AQ2R5NL.js} +1520 -871
- package/dist/chunk-3G6S7LAA.js +43 -0
- package/dist/chunk-3J5I7ZOU.js +58 -0
- package/dist/{chunk-65NYPGRC.js → chunk-3LZ6P5EP.js} +1 -1
- package/dist/chunk-3SSTNB4E.js +191 -0
- package/dist/chunk-3TD6S4TC.js +49 -0
- package/dist/{chunk-3C7EYRFZ.js → chunk-3UTYPHN3.js} +1 -1
- package/dist/{chunk-G2B4ZQGH.js → chunk-3WON4G3M.js} +2 -2
- package/dist/{chunk-S3QNRWYR.js → chunk-42EPP7OB.js} +12 -12
- package/dist/chunk-455SYIIE.js +113 -0
- package/dist/{chunk-YISXLPHN.js → chunk-4B6BT75O.js} +166 -23
- package/dist/{chunk-C6EKFH7J.js → chunk-4CLK2RJO.js} +7 -7
- package/dist/chunk-4CYTJ5TZ.js +38 -0
- package/dist/{chunk-EYWIDJFU.js → chunk-4D62IZII.js} +5 -5
- package/dist/chunk-4FOAYLI4.js +328 -0
- package/dist/{chunk-7JUKDRSZ.js → chunk-4KNOJC6J.js} +294 -215
- package/dist/{chunk-YMZ3TNYB.js → chunk-4PYC2UGC.js} +2 -2
- package/dist/{chunk-27NKUS3X.js → chunk-4SLBCEPD.js} +2104 -726
- package/dist/chunk-4TKVU2I2.js +122 -0
- package/dist/{chunk-RUVFXE2L.js → chunk-4WXRPQDE.js} +448 -47
- package/dist/{chunk-7ZUIMTOU.js → chunk-5CMPVC3N.js} +24 -24
- package/dist/chunk-5G7ILBUG.js +66 -0
- package/dist/{chunk-HFQCFSJR.js → chunk-5PV7S2ZP.js} +3 -3
- package/dist/{chunk-FITWGOJ2.js → chunk-5QUOPNLN.js} +4 -4
- package/dist/{chunk-Z6LF7HQN.js → chunk-5TDRBOAV.js} +5 -5
- package/dist/{chunk-T6AIP7H5.js → chunk-5WERBKLE.js} +199 -63
- package/dist/{chunk-XPCM366L.js → chunk-62DLJSLV.js} +8 -8
- package/dist/{chunk-L24C55VN.js → chunk-64AADMTW.js} +99 -25
- package/dist/{chunk-K65BJIMT.js → chunk-67BP7HXL.js} +17 -17
- package/dist/chunk-6DQYZU2W.js +379 -0
- package/dist/{chunk-EDS2L2LD.js → chunk-6EXDI5GI.js} +52 -26
- package/dist/chunk-6FNAKCET.js +38 -0
- package/dist/{chunk-6IU67AEK.js → chunk-75ASLHBT.js} +21 -15
- package/dist/{chunk-A3GY6SOA.js → chunk-75ZOZFGH.js} +18 -18
- package/dist/{chunk-MGYZM27B.js → chunk-77O633L5.js} +49 -23
- package/dist/{chunk-GQ4N4WKF.js → chunk-7AB6OWTV.js} +3 -3
- package/dist/{chunk-M766ZJ6W.js → chunk-7CZHUZBL.js} +25 -25
- package/dist/{chunk-A7UPUQFY.js → chunk-7FHNFCHK.js} +643 -124
- package/dist/chunk-7FUIVIBG.js +415 -0
- package/dist/{chunk-6QSERWXH.js → chunk-7QPCGTPS.js} +21 -21
- package/dist/{chunk-LR3R6Z2A.js → chunk-A2G72UI4.js} +7 -7
- package/dist/{chunk-I4JRCD6M.js → chunk-AE4P57KK.js} +23 -17
- package/dist/{chunk-AVJDSJ2F.js → chunk-AHW3RY3X.js} +7 -7
- package/dist/chunk-AM6FP6JW.js +43 -0
- package/dist/{chunk-CSZ4C5N6.js → chunk-AOCWFA7T.js} +93 -10
- package/dist/{chunk-EPTBZ3GP.js → chunk-API5TOUB.js} +1 -1
- package/dist/{chunk-652QWTLZ.js → chunk-AZDITBCT.js} +3 -3
- package/dist/{chunk-DPUYDDQG.js → chunk-BA2HUMDN.js} +25 -3
- package/dist/chunk-BA3U7ALS.js +99 -0
- package/dist/{chunk-HVUPKCGJ.js → chunk-BC6KKZ67.js} +5 -5
- package/dist/{chunk-CKSITVS4.js → chunk-BEPCKDCP.js} +2554 -722
- package/dist/{chunk-3ALD2W2A.js → chunk-BPPU3PW7.js} +4 -4
- package/dist/{chunk-QRAOANID.js → chunk-BVWWPBKD.js} +21 -21
- package/dist/{chunk-ZDPAS56H.js → chunk-BXBCMGIQ.js} +7 -1
- package/dist/chunk-BXZ4PKIT.js +233 -0
- package/dist/chunk-C7CEK3KW.js +247 -0
- package/dist/chunk-CFH3QH26.js +29 -0
- package/dist/chunk-CG6CULP6.js +170 -0
- package/dist/chunk-CLD4RVTA.js +45 -0
- package/dist/{chunk-DKJA76CJ.js → chunk-CNPY4NTZ.js} +13 -13
- package/dist/chunk-CNU5Q2D5.js +575 -0
- package/dist/{chunk-T2UYZSCQ.js → chunk-COYN3UAN.js} +126 -23
- package/dist/{chunk-23W7VWKR.js → chunk-CPK7QSBG.js} +1360 -113
- package/dist/{chunk-4AK7IBVP.js → chunk-D2WSW4GU.js} +1 -1
- package/dist/{chunk-ABWHYWNI.js → chunk-D57MFJPP.js} +3 -3
- package/dist/chunk-D5KV5PSK.js +45 -0
- package/dist/chunk-D5MASS4I.js +48 -0
- package/dist/{chunk-7EOBHUMC.js → chunk-DCWN3HFV.js} +2 -2
- package/dist/{chunk-QSXK2RGH.js → chunk-DDJPM5YX.js} +2 -2
- package/dist/chunk-DN4A2Q2O.js +57 -0
- package/dist/{chunk-IHMLFRVV.js → chunk-DUPZPDCJ.js} +58 -16
- package/dist/{chunk-PRPPGYZG.js → chunk-E2VXBSIR.js} +4 -4
- package/dist/chunk-E57AOFYN.js +32 -0
- package/dist/{chunk-UGLN7QHV.js → chunk-ECLXZWAG.js} +12 -12
- package/dist/{chunk-5WG5YP4T.js → chunk-EDZ45WIR.js} +14 -14
- package/dist/chunk-EFCQSMXB.js +37 -0
- package/dist/{chunk-2ANOROX3.js → chunk-EZIN5E5B.js} +8 -8
- package/dist/{chunk-BWZ77RQT.js → chunk-F6H2RJ3N.js} +1 -1
- package/dist/{chunk-OLQJTJJU.js → chunk-FAJDKNSD.js} +7 -3
- package/dist/chunk-FGAGWYZB.js +47 -0
- package/dist/{chunk-4WBVUHXN.js → chunk-FLVZFTLQ.js} +18 -18
- package/dist/chunk-FQ6WH6PB.js +140 -0
- package/dist/{chunk-T6XQL73F.js → chunk-FRHD22WM.js} +61 -23
- package/dist/{chunk-XAKWMCXI.js → chunk-FVMPFCDS.js} +4 -4
- package/dist/{chunk-B2WLAWNJ.js → chunk-FY3QIRTL.js} +3 -3
- package/dist/{chunk-CPA6HPVR.js → chunk-FYBF5MB2.js} +15 -11
- package/dist/{chunk-V2YTAIQO.js → chunk-G5YJBNDZ.js} +21 -6
- package/dist/chunk-G6IQYM25.js +339 -0
- package/dist/chunk-G6X4YL4D.js +161 -0
- package/dist/{chunk-SJRGMG6A.js → chunk-GC2VK3JT.js} +25 -25
- package/dist/{chunk-SP2I63OO.js → chunk-GCS3FZOB.js} +77 -3
- package/dist/{chunk-6TACQN3N.js → chunk-GDZB57IU.js} +5 -5
- package/dist/chunk-GIFMBCTW.js +221 -0
- package/dist/{chunk-YY6ZQQBP.js → chunk-GK4PYVEC.js} +18 -8
- package/dist/{chunk-TDD55CJH.js → chunk-GNWAOVFQ.js} +6 -5
- package/dist/chunk-GS76RXQE.js +226 -0
- package/dist/{chunk-YYQN46FY.js → chunk-GSGIZ2OO.js} +23 -23
- package/dist/chunk-GUYL7RMK.js +60 -0
- package/dist/chunk-GX74YWR7.js +321 -0
- package/dist/chunk-HE3HRDNZ.js +122 -0
- package/dist/{chunk-AIOQPLA5.js → chunk-HGHRSF3E.js} +30 -12
- package/dist/{chunk-2RMYG36Q.js → chunk-HK3VOHIE.js} +198 -336
- package/dist/{chunk-QZSX3F7C.js → chunk-HS4BGB4G.js} +24 -22
- package/dist/{chunk-XY3P7JW7.js → chunk-HSZMPH5T.js} +834 -149
- package/dist/{chunk-L6SGQIR3.js → chunk-HWD4JPU7.js} +4 -2
- package/dist/{chunk-PCVDV3UZ.js → chunk-HXXP2NTE.js} +6 -6
- package/dist/{chunk-AA6YULYB.js → chunk-I2A3Z5AK.js} +7 -7
- package/dist/{chunk-N7IXEHK3.js → chunk-ICHDSWEY.js} +7 -7
- package/dist/chunk-IFFRGC3N.js +31 -0
- package/dist/{chunk-4BN53TG3.js → chunk-IFHDV6KQ.js} +31 -23
- package/dist/chunk-J2O4LFVI.js +80 -0
- package/dist/chunk-J5AAZ6OJ.js +161 -0
- package/dist/{chunk-ZMFPW66A.js → chunk-JAABT3DA.js} +3 -3
- package/dist/chunk-JITJP64K.js +34 -0
- package/dist/chunk-JKIGL3XQ.js +162 -0
- package/dist/chunk-JRVNXLZP.js +615 -0
- package/dist/chunk-JRXHON4E.js +41 -0
- package/dist/{chunk-DHPACDG7.js → chunk-JS6WMC3J.js} +8 -8
- package/dist/chunk-JU4QNJSR.js +62 -0
- package/dist/{chunk-PAMPPUR6.js → chunk-JYMIB5XY.js} +1697 -172
- package/dist/{chunk-RRTKKSLQ.js → chunk-JYTIHTIK.js} +8 -6
- package/dist/{chunk-3HI6Z55Y.js → chunk-K332XW7K.js} +324 -39
- package/dist/{chunk-GLUX4EEP.js → chunk-K6JB6XTX.js} +9 -9
- package/dist/chunk-KDAHXQIE.js +294 -0
- package/dist/chunk-KYZMRSPS.js +93 -0
- package/dist/chunk-L4CESDPB.js +22 -0
- package/dist/{chunk-NXFPS2LR.js → chunk-LEBR5NHW.js} +14 -14
- package/dist/chunk-LIJJCBQI.js +223 -0
- package/dist/{chunk-DE76JROX.js → chunk-LIRVU3HW.js} +16 -16
- package/dist/{chunk-JMSCMUCA.js → chunk-LN77DXBM.js} +145 -21
- package/dist/{chunk-JIITQXFB.js → chunk-LNWEWYCM.js} +2969 -788
- package/dist/{chunk-TANFV3MZ.js → chunk-LO3BLMVG.js} +3 -3
- package/dist/chunk-LPV6T362.js +250 -0
- package/dist/{chunk-2VHF7OXM.js → chunk-LQH4P45K.js} +1 -1
- package/dist/chunk-LU4MQVAL.js +41 -0
- package/dist/chunk-LWDNKE4X.js +273 -0
- package/dist/chunk-M4H2UG4R.js +218 -0
- package/dist/{chunk-6BVONWJ7.js → chunk-M6D2U3YM.js} +3 -3
- package/dist/chunk-MD7KUEJ3.js +514 -0
- package/dist/{chunk-66YVAGGM.js → chunk-MMZUN7ZO.js} +20 -20
- package/dist/chunk-MZGRDONP.js +916 -0
- package/dist/chunk-N3RUGTGV.js +39 -0
- package/dist/{chunk-JGZCG5VM.js → chunk-NCB6656Z.js} +7 -7
- package/dist/{chunk-4NDAAFQT.js → chunk-NRQ65LTQ.js} +7 -5
- package/dist/{chunk-UNM6ZOKP.js → chunk-NTAVOWYD.js} +145 -18
- package/dist/{chunk-3USYZGRF.js → chunk-NYOABJNK.js} +36 -27
- package/dist/{chunk-WVPJSRRJ.js → chunk-O4IKAIZ2.js} +24 -20
- package/dist/{chunk-D3PO4DOZ.js → chunk-ODMBPDDV.js} +51 -14
- package/dist/chunk-OKUB6RJA.js +533 -0
- package/dist/chunk-OMPENNWY.js +39 -0
- package/dist/chunk-OPQZISG3.js +25 -0
- package/dist/{chunk-5GGOLAKC.js → chunk-OV57AHCG.js} +26 -26
- package/dist/chunk-OZJ3JLYI.js +58 -0
- package/dist/{chunk-KEM4SDDP.js → chunk-P2VBXX7N.js} +11 -10
- package/dist/{chunk-S2A7YC3Q.js → chunk-PAINMSID.js} +10150 -1720
- package/dist/chunk-PKJAV25S.js +43 -0
- package/dist/{chunk-EBEW3XXQ.js → chunk-PNQLCRSS.js} +4 -4
- package/dist/{chunk-WY6PP6AA.js → chunk-PQH56YF6.js} +37 -5
- package/dist/chunk-PRTBPXYA.js +50 -0
- package/dist/{chunk-YEDDOYSC.js → chunk-PRW3HXM3.js} +2 -2
- package/dist/chunk-PRZ7KMWL.js +42 -0
- package/dist/{chunk-6M5NK3K3.js → chunk-PTQ3ZALZ.js} +42 -4
- package/dist/{chunk-5GDRCDQB.js → chunk-PUEWDYGP.js} +2 -2
- package/dist/chunk-Q26LDIUV.js +236 -0
- package/dist/{chunk-CR3MTH32.js → chunk-Q46QDDMF.js} +77 -7
- package/dist/chunk-QBNWF5ZU.js +19 -0
- package/dist/chunk-QE527XRY.js +342 -0
- package/dist/{chunk-CNUKCS2T.js → chunk-QHJJMZLX.js} +1518 -54
- package/dist/chunk-QJCJOMW3.js +425 -0
- package/dist/{chunk-VYDBJ73M.js → chunk-QKMRFNMA.js} +15 -15
- package/dist/chunk-QOOEYDNJ.js +317 -0
- package/dist/chunk-QSIDVFB7.js +45 -0
- package/dist/{chunk-R77KLRB3.js → chunk-RA33EYS4.js} +19 -18
- package/dist/{chunk-DFBCSHVT.js → chunk-RALY3E5U.js} +4 -4
- package/dist/{chunk-ZCJDOK2S.js → chunk-S2RY3BOD.js} +35 -21
- package/dist/chunk-S5LSDXV4.js +40 -0
- package/dist/chunk-SB5GRUFM.js +54 -0
- package/dist/chunk-SH27FYPF.js +150 -0
- package/dist/chunk-T74WQQGS.js +40 -0
- package/dist/{chunk-O3WI5KWW.js → chunk-TGVVUKTG.js} +2 -2
- package/dist/chunk-THN26IEF.js +48 -0
- package/dist/chunk-TJH2EUAZ.js +47 -0
- package/dist/{chunk-ZNCEKR4J.js → chunk-TQ4TG57V.js} +12 -8
- package/dist/{chunk-7FBGJFEE.js → chunk-TQTRWENN.js} +12 -12
- package/dist/{chunk-TYAID7B7.js → chunk-TSM6T6GQ.js} +23 -23
- package/dist/{chunk-DPZHZQKE.js → chunk-TW3KOSQK.js} +43 -10
- package/dist/{chunk-MC3H7736.js → chunk-TWQRPM76.js} +37 -4
- package/dist/chunk-TYD6I4CZ.js +109 -0
- package/dist/chunk-U3NBK4EU.js +420 -0
- package/dist/{chunk-6JCPI55B.js → chunk-UDXWIPCB.js} +47 -7
- package/dist/{chunk-MMOSZPWJ.js → chunk-UEVZZZU2.js} +4 -4
- package/dist/{chunk-JBTK5OP2.js → chunk-UF4UHCIG.js} +2 -2
- package/dist/{chunk-6JUBZBMW.js → chunk-UFE36WJH.js} +4 -4
- package/dist/{chunk-ONLS5QA5.js → chunk-URIKUID7.js} +12 -12
- package/dist/chunk-UYLBRRFK.js +1106 -0
- package/dist/chunk-UZACEG2C.js +515 -0
- package/dist/{chunk-7SW2QWLU.js → chunk-VAMHOHUU.js} +12 -12
- package/dist/{chunk-RZPYKDWS.js → chunk-VUVGX5L2.js} +1 -1
- package/dist/{chunk-Q4D5X7QB.js → chunk-VYOCPTUB.js} +44 -24
- package/dist/{chunk-CAQZUNOH.js → chunk-WAZEXEJH.js} +567 -3
- package/dist/chunk-WCEF7H6L.js +221 -0
- package/dist/{chunk-2DIQQJCB.js → chunk-WEOMJHPV.js} +12 -12
- package/dist/{chunk-FHH4I4ZV.js → chunk-WFU77T3A.js} +12 -6
- package/dist/chunk-WIWTJMMR.js +292 -0
- package/dist/{chunk-2BFCZ3RK.js → chunk-WNTIOBOG.js} +18 -18
- package/dist/{chunk-7WD63HU2.js → chunk-X2CUXTWU.js} +6 -6
- package/dist/chunk-X7EMBM5X.js +34 -0
- package/dist/chunk-XEQCQO66.js +33 -0
- package/dist/{chunk-F7HYOUFF.js → chunk-XG4WJHLQ.js} +20 -4
- package/dist/{chunk-EFHQ3TWP.js → chunk-XOL3GFW2.js} +171 -31
- package/dist/{chunk-NE2W3OO7.js → chunk-XQ3YZ4QW.js} +22 -22
- package/dist/{chunk-CEXSU4S6.js → chunk-Y6K3CJBU.js} +723 -85
- package/dist/{chunk-4RS5KZQD.js → chunk-YAH5IQAL.js} +5 -5
- package/dist/{chunk-64EQYWCA.js → chunk-YBOVNMHC.js} +51 -14
- package/dist/chunk-YF5DZCPN.js +1212 -0
- package/dist/chunk-YIGBJNH4.js +37 -0
- package/dist/{chunk-BKVWTZ6A.js → chunk-YJ3TBOTK.js} +25 -25
- package/dist/{chunk-Q5AMFEWD.js → chunk-YKTE2RU3.js} +2 -2
- package/dist/{chunk-7EKXJZ2Y.js → chunk-YO2PKTXT.js} +296 -37
- package/dist/{chunk-6J5OKA4Y.js → chunk-YTTNTQOX.js} +15 -15
- package/dist/{chunk-OT2O76OS.js → chunk-YWAKDNMH.js} +183 -33
- package/dist/{chunk-R6XF6UR3.js → chunk-YZDZOEV4.js} +4 -4
- package/dist/chunk-Z3QFH7YG.js +17 -0
- package/dist/{chunk-W5BVQF44.js → chunk-Z6VLC5X4.js} +17 -17
- package/dist/{chunk-HLJAXMM4.js → chunk-ZFQMMU5Y.js} +2 -2
- package/dist/{chunk-W3BQAZ3Q.js → chunk-ZKYRDV7A.js} +12 -12
- package/dist/{chunk-ZMAINWBW.js → chunk-ZLS6PU2O.js} +3 -3
- package/dist/{chunk-ZVM6AKKM.js → chunk-ZOSD4FXV.js} +3 -3
- package/dist/chunk-ZPDDEVQX.js +47 -0
- package/dist/{chunk-TXLH6WHQ.js → chunk-ZQLRCA2V.js} +11 -11
- package/dist/{chunk-HITVBIJS.js → chunk-ZX7235KR.js} +128 -54
- package/dist/{chunk-GT4I4ZWM.js → chunk-ZZFHLRQW.js} +18 -16
- package/dist/files.json +636 -364
- package/dist/packages/naysayer/constitution/constitution.yaml +19 -0
- package/dist/packages/sdk/npm/index.js +5614 -2343
- package/dist/packages/ui/dist/assets/{elk-api-tma0MCuh.js → elk-api-CowTnhEJ.js} +2 -2
- package/dist/packages/ui/dist/assets/index-Dw20IRhQ.css +1 -0
- package/dist/packages/ui/dist/assets/index-z2wv0d5d.js +47 -0
- package/dist/packages/ui/dist/index.html +2 -2
- package/package.json +4 -4
- 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-RVH5SUW5.js +0 -69
- package/dist/chunk-VN3DP4LQ.js +0 -43
- 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-64AADMTW.js";
|
|
11
11
|
import {
|
|
12
12
|
dirSource,
|
|
13
|
+
rereadPlan,
|
|
13
14
|
sameCommit,
|
|
14
15
|
verifyFacts
|
|
15
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-JYMIB5XY.js";
|
|
16
17
|
import {
|
|
17
18
|
scanInputs
|
|
18
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-CPK7QSBG.js";
|
|
19
20
|
import {
|
|
20
21
|
listIngestors
|
|
21
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-YO2PKTXT.js";
|
|
22
23
|
import {
|
|
23
24
|
BUILT_IN_EXTRACTORS,
|
|
24
25
|
MODEL_READERS,
|
|
@@ -33,12 +34,17 @@ import {
|
|
|
33
34
|
runDialectTests,
|
|
34
35
|
runFrameworkTests,
|
|
35
36
|
unmodelledFrameworks
|
|
36
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-TWQRPM76.js";
|
|
37
38
|
import {
|
|
38
39
|
baseClonePath,
|
|
39
40
|
changesSince,
|
|
40
|
-
checkoutState
|
|
41
|
-
|
|
41
|
+
checkoutState,
|
|
42
|
+
inPlaceDir
|
|
43
|
+
} from "./chunk-XOL3GFW2.js";
|
|
44
|
+
import {
|
|
45
|
+
buildContextPack,
|
|
46
|
+
toRunSummary
|
|
47
|
+
} from "./chunk-QHJJMZLX.js";
|
|
42
48
|
import {
|
|
43
49
|
DEFAULT_IGNORES,
|
|
44
50
|
configHash,
|
|
@@ -50,49 +56,71 @@ import {
|
|
|
50
56
|
sandboxedExtractor,
|
|
51
57
|
scanRepo,
|
|
52
58
|
withParseCache
|
|
53
|
-
} from "./chunk-
|
|
54
|
-
import {
|
|
55
|
-
buildContextPack,
|
|
56
|
-
toRunSummary
|
|
57
|
-
} from "./chunk-CNUKCS2T.js";
|
|
59
|
+
} from "./chunk-7AB6OWTV.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
|
+
conventionsOf,
|
|
73
87
|
declaredCallsOf,
|
|
74
88
|
deserializeRecord,
|
|
89
|
+
effectiveComponentTypes,
|
|
75
90
|
external_exports,
|
|
76
91
|
factIdPart,
|
|
77
92
|
factsDirOf,
|
|
78
93
|
factsRelOf,
|
|
79
94
|
fieldKey,
|
|
80
95
|
flowFiles,
|
|
96
|
+
helperOn,
|
|
81
97
|
hostMatches,
|
|
82
98
|
hostQuestionText,
|
|
83
99
|
identifierWords,
|
|
100
|
+
isAiBy,
|
|
101
|
+
isGuessedJoin,
|
|
102
|
+
isPathMatch,
|
|
84
103
|
isPrototypeType,
|
|
85
104
|
joinCallers,
|
|
105
|
+
joinConventions,
|
|
86
106
|
joinFacts,
|
|
107
|
+
joinFlagsOf,
|
|
108
|
+
joinGuessesOf,
|
|
87
109
|
joinRecords,
|
|
110
|
+
joinSharedMaps,
|
|
111
|
+
joinTerms,
|
|
88
112
|
lineNamesField,
|
|
89
113
|
lineNamesRoute,
|
|
90
114
|
lineNamesWord,
|
|
115
|
+
linkEvidenceOf,
|
|
91
116
|
listQuestions,
|
|
92
117
|
loadOrgPack,
|
|
93
118
|
mergeCatalog,
|
|
94
119
|
normalizePath,
|
|
95
120
|
orgPackDirOf,
|
|
121
|
+
outsideSystemOf,
|
|
122
|
+
pairsFromCorrections,
|
|
123
|
+
parseCorrections,
|
|
96
124
|
parseFacts,
|
|
97
125
|
parseRecord,
|
|
98
126
|
projectFacts,
|
|
@@ -101,267 +129,32 @@ import {
|
|
|
101
129
|
recordPath,
|
|
102
130
|
require_dist,
|
|
103
131
|
serializeRecord,
|
|
132
|
+
sharedJoinAbout,
|
|
133
|
+
singularNoun,
|
|
104
134
|
slugify,
|
|
105
135
|
specFileHash,
|
|
106
136
|
specFindingFingerprint,
|
|
137
|
+
stackHelpersOff,
|
|
138
|
+
stripLinkEvidence,
|
|
139
|
+
stripSharedMaps,
|
|
140
|
+
termGrantRecords,
|
|
107
141
|
throwIfCancelled,
|
|
108
142
|
toCanonicalYaml,
|
|
143
|
+
topicQuestionText,
|
|
109
144
|
urlHostPort
|
|
110
|
-
} from "./chunk-
|
|
145
|
+
} from "./chunk-PAINMSID.js";
|
|
111
146
|
import {
|
|
112
147
|
SCHEMA_VERSION,
|
|
113
148
|
writeFileAtomic
|
|
114
|
-
} from "./chunk-
|
|
149
|
+
} from "./chunk-ODMBPDDV.js";
|
|
115
150
|
import {
|
|
116
151
|
__toESM
|
|
117
152
|
} from "./chunk-TV45EUGM.js";
|
|
118
153
|
|
|
119
|
-
// packages/
|
|
120
|
-
|
|
121
|
-
import {
|
|
122
|
-
import {
|
|
123
|
-
var GLOSSARY_DEFINER = "glossary-definer";
|
|
124
|
-
var GLOSSARY_CHANGE = "glossary";
|
|
125
|
-
var GLOSSARY_OUTPUT = "catalog/proposals/glossary-definitions.yaml";
|
|
126
|
-
var DefinitionProposalFile = external_exports.strictObject({
|
|
127
|
-
schemaVersion: external_exports.literal(1),
|
|
128
|
-
term: RecordId,
|
|
129
|
-
definition: external_exports.string().min(1),
|
|
130
|
-
evidence: external_exports.array(external_exports.string().min(1)).min(1),
|
|
131
|
-
agent: RecordId,
|
|
132
|
-
runId: external_exports.string().optional(),
|
|
133
|
-
at: IsoDateTime
|
|
134
|
-
});
|
|
135
|
-
var LEADING = ["schemaVersion", "term", "definition", "evidence"];
|
|
136
|
-
async function writeDefinitionProposals(dir, proposals) {
|
|
137
|
-
const written = [];
|
|
138
|
-
if (!proposals.length)
|
|
139
|
-
return written;
|
|
140
|
-
await mkdir(join(dir, "glossaryterm"), { recursive: true });
|
|
141
|
-
for (const p of proposals) {
|
|
142
|
-
const file = `glossaryterm/${p.term}.yaml`;
|
|
143
|
-
const body = DefinitionProposalFile.parse({ schemaVersion: 1, ...p });
|
|
144
|
-
await writeFile(join(dir, file), toCanonicalYaml(body, LEADING), "utf8");
|
|
145
|
-
written.push(file);
|
|
146
|
-
}
|
|
147
|
-
return written;
|
|
148
|
-
}
|
|
149
|
-
async function readDefinitionProposals(dir) {
|
|
150
|
-
const proposals = [];
|
|
151
|
-
const problems = [];
|
|
152
|
-
let files;
|
|
153
|
-
try {
|
|
154
|
-
files = (await readdir(join(dir, "glossaryterm"))).filter((f) => /\.ya?ml$/.test(f)).sort();
|
|
155
|
-
} catch {
|
|
156
|
-
return { proposals, problems };
|
|
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 };
|
|
180
|
-
}
|
|
181
|
-
function glossaryDefinerAgent(orgPack) {
|
|
182
|
-
const own = orgPack?.agents.find((a) => a.id === GLOSSARY_DEFINER);
|
|
183
|
-
if (own)
|
|
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
|
-
});
|
|
196
|
-
}
|
|
197
|
-
var INSTRUCTIONS = `# Glossary definer
|
|
198
|
-
|
|
199
|
-
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:
|
|
200
|
-
|
|
201
|
-
- definition: one sentence a product owner would accept, saying what the word means to the business, only from the evidence;
|
|
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();
|
|
228
|
-
}
|
|
229
|
-
function parseDefinerOutput(output, entries, meta) {
|
|
230
|
-
let data;
|
|
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;
|
|
279
|
-
}
|
|
280
|
-
function undefinedCodeTerms(entries) {
|
|
281
|
-
return entries.filter((e) => e.sources.length === 1 && e.sources[0] === "code" && (e.code?.uses.length ?? 0) > 0);
|
|
282
|
-
}
|
|
283
|
-
async function proposeDefinitions(entries, runtime, o = {}) {
|
|
284
|
-
const todo = [...entries].sort((a, b) => a.id.localeCompare(b.id));
|
|
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
|
-
};
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
// packages/glossary/dist/decisions.js
|
|
362
|
-
import { createHash } from "node:crypto";
|
|
363
|
-
import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
|
|
364
|
-
import { dirname, join as join2 } from "node:path";
|
|
154
|
+
// packages/scanner/dist/scan.js
|
|
155
|
+
import { stat } from "node:fs/promises";
|
|
156
|
+
import { dirname as dirname3, isAbsolute, join as join9, resolve as resolve2 } from "node:path";
|
|
157
|
+
import { fileURLToPath } from "node:url";
|
|
365
158
|
|
|
366
159
|
// packages/glossary/dist/words.js
|
|
367
160
|
var STOP_TOKENS = /* @__PURE__ */ new Set([
|
|
@@ -407,21 +200,7 @@ var QUALIFIERS = /* @__PURE__ */ new Set([
|
|
|
407
200
|
"previous"
|
|
408
201
|
]);
|
|
409
202
|
var splitWords = (s) => identifierWords(s);
|
|
410
|
-
|
|
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
|
-
}
|
|
203
|
+
var singular = (w) => singularNoun(w);
|
|
425
204
|
var isNoise = (w) => STOP_TOKENS.has(w) || /^v\d+$/.test(w) || /^\d+$/.test(w);
|
|
426
205
|
function businessWords(s) {
|
|
427
206
|
return splitWords(s).filter((w) => !isNoise(w)).map(singular);
|
|
@@ -457,101 +236,125 @@ function definitionKey(d) {
|
|
|
457
236
|
return d.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
|
|
458
237
|
}
|
|
459
238
|
|
|
460
|
-
// packages/glossary/dist/
|
|
461
|
-
var
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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);
|
|
239
|
+
// packages/glossary/dist/wiki.js
|
|
240
|
+
var GLOSSARY = /\b(glossary|glossaries|definitions|terminology|terms|vocabulary)\b/i;
|
|
241
|
+
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;
|
|
242
|
+
var strip = (s) => s.replace(/^\*\*(.+)\*\*$/, "$1").replace(/^__(.+)__$/, "$1").replace(/[`]/g, "").trim();
|
|
243
|
+
function isTermLike(raw) {
|
|
244
|
+
const t = strip(raw);
|
|
245
|
+
if (!t || t.length > 60 || !/^[A-Za-z]/.test(t))
|
|
246
|
+
return false;
|
|
247
|
+
if (/:\/\/|[|?!;]/.test(t) || /[.,]$/.test(t))
|
|
248
|
+
return false;
|
|
249
|
+
if (META.test(t))
|
|
250
|
+
return false;
|
|
251
|
+
const words2 = t.split(/\s+/);
|
|
252
|
+
return words2.length >= 1 && words2.length <= 5;
|
|
524
253
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
return
|
|
254
|
+
var cleanDefinition = (s) => s.replace(/\s+/g, " ").trim();
|
|
255
|
+
var BOLD = /^(?:\*\*([^*]+?)\*\*|__([^_]+?)__)\s*(?::|\s[—–-]\s)\s*(.+)$/;
|
|
256
|
+
var PLAIN = /^([A-Za-z][^:]{0,59}?)\s*(?::|\s[—–-]\s)\s*(.+)$/;
|
|
257
|
+
function lineDefinition(text2, scoped) {
|
|
258
|
+
const b = BOLD.exec(text2);
|
|
259
|
+
if (b) {
|
|
260
|
+
const term = (b[1] ?? b[2]).trim();
|
|
261
|
+
const definition = cleanDefinition(b[3]);
|
|
262
|
+
if (isTermLike(term) && definition.length >= 3)
|
|
263
|
+
return { term, definition, markup: "bold" };
|
|
264
|
+
return void 0;
|
|
536
265
|
}
|
|
537
|
-
|
|
538
|
-
|
|
266
|
+
if (!scoped)
|
|
267
|
+
return void 0;
|
|
268
|
+
const p = PLAIN.exec(text2);
|
|
269
|
+
if (p && isTermLike(p[1])) {
|
|
270
|
+
const definition = cleanDefinition(p[2]);
|
|
271
|
+
if (definition.length >= 3)
|
|
272
|
+
return { term: strip(p[1]), definition, markup: "line" };
|
|
273
|
+
}
|
|
274
|
+
return void 0;
|
|
539
275
|
}
|
|
540
|
-
|
|
276
|
+
var TERM_HEADER = /^(term|terms|name|word|phrase|concept)$/i;
|
|
277
|
+
var DEF_HEADER = /^(definition|definitions|meaning|description|what it means)$/i;
|
|
278
|
+
function scopes(page) {
|
|
279
|
+
const fmType = [page.frontMatter.type, page.frontMatter.kind].filter((v) => typeof v === "string").join(" ");
|
|
280
|
+
const pageWide = GLOSSARY.test(page.title) || GLOSSARY.test(fmType);
|
|
541
281
|
const out = [];
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
282
|
+
let openLevel;
|
|
283
|
+
for (const s of page.sections) {
|
|
284
|
+
if (openLevel !== void 0 && s.level > 0 && s.level <= openLevel)
|
|
285
|
+
openLevel = void 0;
|
|
286
|
+
if (openLevel === void 0 && s.level > 0 && GLOSSARY.test(s.heading))
|
|
287
|
+
openLevel = s.level;
|
|
288
|
+
out.push(pageWide || openLevel !== void 0);
|
|
548
289
|
}
|
|
549
290
|
return out;
|
|
550
291
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
292
|
+
function pageDefinitions(page) {
|
|
293
|
+
const out = [];
|
|
294
|
+
const add = (term, definition, line, markup) => {
|
|
295
|
+
const t = displayTerm(strip(term));
|
|
296
|
+
const key = termKey(t);
|
|
297
|
+
if (!key)
|
|
298
|
+
return;
|
|
299
|
+
out.push({
|
|
300
|
+
term: t,
|
|
301
|
+
key,
|
|
302
|
+
definition,
|
|
303
|
+
path: page.path,
|
|
304
|
+
line,
|
|
305
|
+
markup,
|
|
306
|
+
pageTitle: page.title,
|
|
307
|
+
...page.owner ? { owner: page.owner } : {}
|
|
308
|
+
});
|
|
309
|
+
};
|
|
310
|
+
const inScope = scopes(page);
|
|
311
|
+
page.sections.forEach((s, si) => {
|
|
312
|
+
const scoped = inScope[si];
|
|
313
|
+
if (scoped && s.level >= 2 && !GLOSSARY.test(s.heading) && isTermLike(s.heading)) {
|
|
314
|
+
const first = s.blocks[0];
|
|
315
|
+
if (first?.type === "paragraph" && !lineDefinition(first.text.split("\n")[0], true))
|
|
316
|
+
add(s.heading, cleanDefinition(first.text), first.line, "heading");
|
|
317
|
+
}
|
|
318
|
+
for (const b of s.blocks) {
|
|
319
|
+
if (b.type === "paragraph") {
|
|
320
|
+
const lines = b.text.split("\n");
|
|
321
|
+
for (let k = 0; k < lines.length; k++) {
|
|
322
|
+
const l = lines[k];
|
|
323
|
+
const next = lines[k + 1];
|
|
324
|
+
if (next !== void 0 && /^:\s+\S/.test(next) && isTermLike(l)) {
|
|
325
|
+
add(l, cleanDefinition(next.replace(/^:\s+/, "")), b.line + k, "definition-list");
|
|
326
|
+
k++;
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
const d = lineDefinition(l, scoped);
|
|
330
|
+
if (d)
|
|
331
|
+
add(d.term, d.definition, b.line + k, d.markup);
|
|
332
|
+
}
|
|
333
|
+
} else if (b.type === "list") {
|
|
334
|
+
for (const it of b.items) {
|
|
335
|
+
const d = lineDefinition(it.text.split("\n")[0], scoped);
|
|
336
|
+
if (d)
|
|
337
|
+
add(d.term, cleanDefinition([d.definition, ...it.text.split("\n").slice(1)].join(" ")), it.line, d.markup === "line" ? "list" : d.markup);
|
|
338
|
+
}
|
|
339
|
+
} else if (b.type === "table" && scoped) {
|
|
340
|
+
const ti = b.header.findIndex((h) => TERM_HEADER.test(strip(h)));
|
|
341
|
+
const di = b.header.findIndex((h) => DEF_HEADER.test(strip(h)));
|
|
342
|
+
if (ti < 0 || di < 0)
|
|
343
|
+
continue;
|
|
344
|
+
for (const r of b.rows) {
|
|
345
|
+
const term = r.cells[ti] ?? "";
|
|
346
|
+
const def = cleanDefinition(r.cells[di] ?? "");
|
|
347
|
+
if (isTermLike(term) && def.length >= 3)
|
|
348
|
+
add(term, def, r.line, "table");
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
return out.sort((a, b) => a.line - b.line);
|
|
354
|
+
}
|
|
355
|
+
function wikiDefinitions(pages) {
|
|
356
|
+
return pages.flatMap(pageDefinitions).sort((a, b) => a.path === b.path ? a.line - b.line : a.path < b.path ? -1 : 1);
|
|
357
|
+
}
|
|
555
358
|
|
|
556
359
|
// packages/glossary/dist/code.js
|
|
557
360
|
var firstEvidence = (r) => r.evidence[0];
|
|
@@ -700,148 +503,28 @@ function fieldNames(records) {
|
|
|
700
503
|
return named(records).filter((n) => n.isField).map((n) => ({ record: n.record, name: n.name }));
|
|
701
504
|
}
|
|
702
505
|
|
|
703
|
-
// packages/glossary/dist/
|
|
704
|
-
var
|
|
705
|
-
var
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
506
|
+
// packages/glossary/dist/build.js
|
|
507
|
+
var isDecision = (r) => r.kind === "CuratedDecision" && r.status === "current";
|
|
508
|
+
var isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
509
|
+
function typeSort(type) {
|
|
510
|
+
const t = type.toLowerCase();
|
|
511
|
+
if (/^(numeric|decimal|number|money|int|integer|bigint|smallint|tinyint|float|double|real|long|short)\b/.test(t))
|
|
512
|
+
return "number";
|
|
513
|
+
if (/^(date|time|timestamp|datetime|timestamptz|instant|localdate)/.test(t))
|
|
514
|
+
return "time";
|
|
515
|
+
if (/^(bool|boolean|bit)\b/.test(t))
|
|
516
|
+
return "boolean";
|
|
517
|
+
if (/^(uuid|guid)\b/.test(t))
|
|
518
|
+
return "identifier";
|
|
519
|
+
if (/^(varchar|char|character|nvarchar|nchar|text|string|clob|citext)/.test(t))
|
|
520
|
+
return "text";
|
|
521
|
+
return t.replace(/\(.*$/, "");
|
|
717
522
|
}
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
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
|
-
// packages/glossary/dist/build.js
|
|
824
|
-
var isDecision = (r) => r.kind === "CuratedDecision" && r.status === "current";
|
|
825
|
-
var isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
826
|
-
function typeSort(type) {
|
|
827
|
-
const t = type.toLowerCase();
|
|
828
|
-
if (/^(numeric|decimal|number|money|int|integer|bigint|smallint|tinyint|float|double|real|long|short)\b/.test(t))
|
|
829
|
-
return "number";
|
|
830
|
-
if (/^(date|time|timestamp|datetime|timestamptz|instant|localdate)/.test(t))
|
|
831
|
-
return "time";
|
|
832
|
-
if (/^(bool|boolean|bit)\b/.test(t))
|
|
833
|
-
return "boolean";
|
|
834
|
-
if (/^(uuid|guid)\b/.test(t))
|
|
835
|
-
return "identifier";
|
|
836
|
-
if (/^(varchar|char|character|nvarchar|nchar|text|string|clob|citext)/.test(t))
|
|
837
|
-
return "text";
|
|
838
|
-
return t.replace(/\(.*$/, "");
|
|
839
|
-
}
|
|
840
|
-
function definitionSort(d) {
|
|
841
|
-
if (/\b(amount|sum|total|count|number of|quantity)\b/i.test(d))
|
|
842
|
-
return "number";
|
|
843
|
-
if (/\b(date|timestamp|time at which|day on which)\b/i.test(d))
|
|
844
|
-
return "time";
|
|
523
|
+
function definitionSort(d) {
|
|
524
|
+
if (/\b(amount|sum|total|count|number of|quantity)\b/i.test(d))
|
|
525
|
+
return "number";
|
|
526
|
+
if (/\b(date|timestamp|time at which|day on which)\b/i.test(d))
|
|
527
|
+
return "time";
|
|
845
528
|
return void 0;
|
|
846
529
|
}
|
|
847
530
|
var ALIASES = (d) => (Array.isArray(d.value) ? d.value : [d.value]).map(String);
|
|
@@ -1021,38 +704,377 @@ function buildGlossary(input) {
|
|
|
1021
704
|
for (const col of linkedColumns)
|
|
1022
705
|
columnLinks.push({ column: col, term: id, how: "decision" });
|
|
1023
706
|
}
|
|
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
|
-
};
|
|
707
|
+
const decided = new Set(columnLinks.filter((l) => l.how === "decision").map((l) => l.column));
|
|
708
|
+
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));
|
|
709
|
+
const covered = /* @__PURE__ */ new Set([...terms.flatMap((t) => t.fields), ...links.map((l) => l.column)]);
|
|
710
|
+
const names = entries.map((e) => ({ id: e.id, term: e.term, aliases: e.aliases }));
|
|
711
|
+
const hints = fieldNames(records).filter((f) => !covered.has(f.record)).map((f) => namingHint(f.record, f.name, names)).filter((h) => !!h);
|
|
712
|
+
return {
|
|
713
|
+
terms,
|
|
714
|
+
entries,
|
|
715
|
+
columnLinks: links,
|
|
716
|
+
conflicts: conflicts.sort((a, b) => a.term.localeCompare(b.term) || a.kind.localeCompare(b.kind)),
|
|
717
|
+
hints,
|
|
718
|
+
problems
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
function applyGlossary(records, build) {
|
|
722
|
+
const link = /* @__PURE__ */ new Map();
|
|
723
|
+
for (const l of build.columnLinks)
|
|
724
|
+
if (!link.has(l.column))
|
|
725
|
+
link.set(l.column, l.term);
|
|
726
|
+
const out = [];
|
|
727
|
+
for (const r of records) {
|
|
728
|
+
if (r.kind === "GlossaryTerm")
|
|
729
|
+
continue;
|
|
730
|
+
if (r.kind === "Column" && !r.glossaryTerm && link.has(r.id))
|
|
731
|
+
out.push({ ...r, glossaryTerm: link.get(r.id) });
|
|
732
|
+
else
|
|
733
|
+
out.push(r);
|
|
734
|
+
}
|
|
735
|
+
return [...out, ...build.terms];
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// packages/glossary/dist/proposals.js
|
|
739
|
+
var import_yaml = __toESM(require_dist(), 1);
|
|
740
|
+
import { mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
741
|
+
import { join } from "node:path";
|
|
742
|
+
var GLOSSARY_DEFINER = "glossary-definer";
|
|
743
|
+
var GLOSSARY_CHANGE = "glossary";
|
|
744
|
+
var GLOSSARY_OUTPUT = "catalog/proposals/glossary-definitions.yaml";
|
|
745
|
+
var DefinitionProposalFile = external_exports.strictObject({
|
|
746
|
+
schemaVersion: external_exports.literal(1),
|
|
747
|
+
term: RecordId,
|
|
748
|
+
definition: external_exports.string().min(1),
|
|
749
|
+
evidence: external_exports.array(external_exports.string().min(1)).min(1),
|
|
750
|
+
agent: RecordId,
|
|
751
|
+
runId: external_exports.string().optional(),
|
|
752
|
+
at: IsoDateTime
|
|
753
|
+
});
|
|
754
|
+
var LEADING = ["schemaVersion", "term", "definition", "evidence"];
|
|
755
|
+
async function writeDefinitionProposals(dir, proposals) {
|
|
756
|
+
const written = [];
|
|
757
|
+
if (!proposals.length)
|
|
758
|
+
return written;
|
|
759
|
+
await mkdir(join(dir, "glossaryterm"), { recursive: true });
|
|
760
|
+
for (const p of proposals) {
|
|
761
|
+
const file = `glossaryterm/${p.term}.yaml`;
|
|
762
|
+
const body = DefinitionProposalFile.parse({ schemaVersion: 1, ...p });
|
|
763
|
+
await writeFile(join(dir, file), toCanonicalYaml(body, LEADING), "utf8");
|
|
764
|
+
written.push(file);
|
|
765
|
+
}
|
|
766
|
+
return written;
|
|
767
|
+
}
|
|
768
|
+
async function readDefinitionProposals(dir) {
|
|
769
|
+
const proposals = [];
|
|
770
|
+
const problems = [];
|
|
771
|
+
let files;
|
|
772
|
+
try {
|
|
773
|
+
files = (await readdir(join(dir, "glossaryterm"))).filter((f) => /\.ya?ml$/.test(f)).sort();
|
|
774
|
+
} catch {
|
|
775
|
+
return { proposals, problems };
|
|
776
|
+
}
|
|
777
|
+
for (const f of files) {
|
|
778
|
+
const file = `glossaryterm/${f}`;
|
|
779
|
+
let data;
|
|
780
|
+
try {
|
|
781
|
+
data = (0, import_yaml.parse)(await readFile(join(dir, file), "utf8"));
|
|
782
|
+
} catch (e) {
|
|
783
|
+
problems.push({ file, message: `invalid YAML: ${e.message.split("\n")[0]}` });
|
|
784
|
+
continue;
|
|
785
|
+
}
|
|
786
|
+
const p = DefinitionProposalFile.safeParse(data);
|
|
787
|
+
if (!p.success) {
|
|
788
|
+
problems.push({
|
|
789
|
+
file,
|
|
790
|
+
message: p.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")
|
|
791
|
+
});
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
794
|
+
const { schemaVersion: _v, ...rest } = p.data;
|
|
795
|
+
void _v;
|
|
796
|
+
proposals.push(rest);
|
|
797
|
+
}
|
|
798
|
+
return { proposals, problems };
|
|
799
|
+
}
|
|
800
|
+
function glossaryDefinerAgent(orgPack) {
|
|
801
|
+
const own = orgPack?.agents.find((a) => a.id === GLOSSARY_DEFINER);
|
|
802
|
+
if (own)
|
|
803
|
+
return own;
|
|
804
|
+
return AgentDefinition.parse({
|
|
805
|
+
schemaVersion: 1,
|
|
806
|
+
id: GLOSSARY_DEFINER,
|
|
807
|
+
role: "business-analyst",
|
|
808
|
+
description: "Proposes a one-sentence business definition for each glossary term found only in code, from where the code uses it.",
|
|
809
|
+
inputs: ["glossary", "catalog-slice"],
|
|
810
|
+
// C5 has no output kind for catalog proposals (see ADR 0031); a definition seeds specs.
|
|
811
|
+
outputArtifact: "specs",
|
|
812
|
+
tokenBudget: orgPack?.runtime.budgets.defaultTokens ?? 15e4,
|
|
813
|
+
allowedRuntimes: ["hand-off", "model", "headless-claude-code", "mock", "replay"]
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
var INSTRUCTIONS = `# Glossary definer
|
|
817
|
+
|
|
818
|
+
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:
|
|
819
|
+
|
|
820
|
+
- definition: one sentence a product owner would accept, saying what the word means to the business, only from the evidence;
|
|
821
|
+
- evidence: the record ids from that term's block your definition relies on.
|
|
822
|
+
|
|
823
|
+
Don't invent systems, rules or numbers. A proposal that cites no record id from its term is discarded. Write YAML and nothing else:
|
|
824
|
+
|
|
825
|
+
terms:
|
|
826
|
+
- id: <term id>
|
|
827
|
+
definition: <one sentence>
|
|
828
|
+
evidence: [<record id>, ...]`;
|
|
829
|
+
var Proposal = external_exports.object({
|
|
830
|
+
id: external_exports.string().min(1),
|
|
831
|
+
definition: external_exports.string().trim().min(3).max(400),
|
|
832
|
+
evidence: external_exports.array(external_exports.coerce.string()).default([])
|
|
833
|
+
});
|
|
834
|
+
function block(e) {
|
|
835
|
+
return toCanonicalYaml({
|
|
836
|
+
id: e.id,
|
|
837
|
+
term: e.term,
|
|
838
|
+
usedBy: (e.code?.uses ?? []).map((u) => ({
|
|
839
|
+
id: u.record,
|
|
840
|
+
kind: u.kind,
|
|
841
|
+
name: u.name,
|
|
842
|
+
...u.evidence.path ? {
|
|
843
|
+
code: `${u.evidence.repo ? `${u.evidence.repo}:` : ""}${u.evidence.path}${u.evidence.line ? `:${u.evidence.line}` : ""}`
|
|
844
|
+
} : {}
|
|
845
|
+
}))
|
|
846
|
+
}, ["id", "term", "usedBy", "id", "kind", "name", "code"]).trimEnd();
|
|
847
|
+
}
|
|
848
|
+
function parseDefinerOutput(output, entries, meta) {
|
|
849
|
+
let data;
|
|
850
|
+
try {
|
|
851
|
+
data = (0, import_yaml.parse)(output.replace(/^```(?:ya?ml)?\s*\n|\n```\s*$/g, ""));
|
|
852
|
+
} catch (e) {
|
|
853
|
+
return {
|
|
854
|
+
proposals: [],
|
|
855
|
+
discarded: [{ reason: `the output is not YAML: ${e.message.split("\n")[0]}` }]
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
const list = data?.terms;
|
|
859
|
+
if (!Array.isArray(list))
|
|
860
|
+
return { proposals: [], discarded: [{ reason: "the output has no terms list" }] };
|
|
861
|
+
const byId = new Map(entries.map((e) => [e.id, e]));
|
|
862
|
+
const out = { proposals: [], discarded: [] };
|
|
863
|
+
for (const raw of list) {
|
|
864
|
+
const p = Proposal.safeParse(raw);
|
|
865
|
+
const id = typeof raw?.id === "string" ? raw.id : void 0;
|
|
866
|
+
if (!p.success) {
|
|
867
|
+
out.discarded.push({ ...id ? { term: id } : {}, reason: "incomplete proposal" });
|
|
868
|
+
continue;
|
|
869
|
+
}
|
|
870
|
+
const e = byId.get(p.data.id);
|
|
871
|
+
if (!e) {
|
|
872
|
+
out.discarded.push({
|
|
873
|
+
term: p.data.id,
|
|
874
|
+
reason: `${p.data.id} isn't one of the terms it was given`
|
|
875
|
+
});
|
|
876
|
+
continue;
|
|
877
|
+
}
|
|
878
|
+
if (out.proposals.some((x) => x.term === e.id)) {
|
|
879
|
+
out.discarded.push({ term: e.id, reason: "a second proposal for the same term" });
|
|
880
|
+
continue;
|
|
881
|
+
}
|
|
882
|
+
const known = new Set((e.code?.uses ?? []).map((u) => u.record));
|
|
883
|
+
const evidence = [...new Set(p.data.evidence.filter((x) => known.has(x)))];
|
|
884
|
+
if (!evidence.length) {
|
|
885
|
+
out.discarded.push({ term: e.id, reason: "it cites no record that uses the term" });
|
|
886
|
+
continue;
|
|
887
|
+
}
|
|
888
|
+
out.proposals.push({
|
|
889
|
+
term: e.id,
|
|
890
|
+
definition: p.data.definition.replace(/\s+/g, " "),
|
|
891
|
+
evidence,
|
|
892
|
+
agent: meta.agent,
|
|
893
|
+
at: meta.at,
|
|
894
|
+
...meta.runId ? { runId: meta.runId } : {}
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
return out;
|
|
898
|
+
}
|
|
899
|
+
function undefinedCodeTerms(entries) {
|
|
900
|
+
return entries.filter((e) => e.sources.length === 1 && e.sources[0] === "code" && (e.code?.uses.length ?? 0) > 0);
|
|
901
|
+
}
|
|
902
|
+
async function proposeDefinitions(entries, runtime, o = {}) {
|
|
903
|
+
const todo = [...entries].sort((a, b) => a.id.localeCompare(b.id));
|
|
904
|
+
if (!todo.length)
|
|
905
|
+
return { proposals: [], discarded: [] };
|
|
906
|
+
const agent = glossaryDefinerAgent(o.orgPack);
|
|
907
|
+
const now2 = o.now ?? /* @__PURE__ */ new Date();
|
|
908
|
+
const at3 = now2.toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
909
|
+
const levels = o.orgPack?.pack?.classification ?? [
|
|
910
|
+
"public",
|
|
911
|
+
"internal",
|
|
912
|
+
"confidential",
|
|
913
|
+
"restricted"
|
|
914
|
+
];
|
|
915
|
+
const sources = [
|
|
916
|
+
{
|
|
917
|
+
id: "0-instructions",
|
|
918
|
+
kind: "instruction",
|
|
919
|
+
origin: "atticus/glossary",
|
|
920
|
+
text: INSTRUCTIONS,
|
|
921
|
+
classification: "public",
|
|
922
|
+
trusted: true
|
|
923
|
+
},
|
|
924
|
+
...todo.map((e) => ({
|
|
925
|
+
id: e.id,
|
|
926
|
+
kind: "catalog",
|
|
927
|
+
origin: `catalog/generated/glossaryterm/${e.id}.yaml`,
|
|
928
|
+
text: block(e),
|
|
929
|
+
classification: "internal",
|
|
930
|
+
priority: 10
|
|
931
|
+
}))
|
|
932
|
+
];
|
|
933
|
+
const pack = buildContextPack({
|
|
934
|
+
change: GLOSSARY_CHANGE,
|
|
935
|
+
agent: agent.id,
|
|
936
|
+
budget: o.orgPack?.runtime.budgets.perAgent[agent.id] ?? agent.tokenBudget,
|
|
937
|
+
sources,
|
|
938
|
+
levels,
|
|
939
|
+
maxClassification: levels.includes("internal") ? "internal" : levels.at(-1),
|
|
940
|
+
now: now2
|
|
941
|
+
});
|
|
942
|
+
const sent = todo.filter((e) => pack.manifest.sources.some((s) => s.id === e.id));
|
|
943
|
+
const discarded = todo.filter((e) => !sent.includes(e)).map((e) => ({
|
|
944
|
+
term: e.id,
|
|
945
|
+
reason: "left out of the context pack (budget or classification)"
|
|
946
|
+
}));
|
|
947
|
+
const prepared = await runtime.prepare({
|
|
948
|
+
change: GLOSSARY_CHANGE,
|
|
949
|
+
agent,
|
|
950
|
+
pack,
|
|
951
|
+
outputPath: GLOSSARY_OUTPUT
|
|
952
|
+
});
|
|
953
|
+
const run = await runtime.run(prepared.runId);
|
|
954
|
+
if (run.state === "waiting" || run.state === "prepared")
|
|
955
|
+
return {
|
|
956
|
+
proposals: [],
|
|
957
|
+
discarded,
|
|
958
|
+
waiting: run.runId,
|
|
959
|
+
promptSha256: pack.manifest.promptSha256
|
|
960
|
+
};
|
|
961
|
+
if (run.state !== "validated" || run.output === void 0)
|
|
962
|
+
return {
|
|
963
|
+
proposals: [],
|
|
964
|
+
discarded: [
|
|
965
|
+
...discarded,
|
|
966
|
+
{ reason: `the glossary definer ${run.state}: ${run.errors.join("; ") || "no output"}` }
|
|
967
|
+
],
|
|
968
|
+
run: toRunSummary(run),
|
|
969
|
+
promptSha256: pack.manifest.promptSha256
|
|
970
|
+
};
|
|
971
|
+
const parsed = parseDefinerOutput(run.output, sent, { agent: agent.id, at: at3, runId: run.runId });
|
|
972
|
+
return {
|
|
973
|
+
proposals: parsed.proposals,
|
|
974
|
+
discarded: [...discarded, ...parsed.discarded],
|
|
975
|
+
run: toRunSummary(run),
|
|
976
|
+
promptSha256: pack.manifest.promptSha256
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// packages/glossary/dist/decisions.js
|
|
981
|
+
import { createHash } from "node:crypto";
|
|
982
|
+
import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
|
|
983
|
+
import { dirname, join as join2 } from "node:path";
|
|
984
|
+
var GlossaryError = class extends Error {
|
|
985
|
+
code;
|
|
986
|
+
constructor(code, message) {
|
|
987
|
+
super(message);
|
|
988
|
+
this.code = code;
|
|
989
|
+
this.name = "GlossaryError";
|
|
990
|
+
}
|
|
991
|
+
};
|
|
992
|
+
var now = () => (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
993
|
+
function decisionId(prefix, key) {
|
|
994
|
+
const id = `${prefix}.${key}`;
|
|
995
|
+
if (id.length <= 120)
|
|
996
|
+
return id;
|
|
997
|
+
return `${id.slice(0, 110)}-${createHash("sha256").update(id).digest("hex").slice(0, 8)}`;
|
|
998
|
+
}
|
|
999
|
+
function decision(id, target, action, value, reason, o) {
|
|
1000
|
+
if (!o.by.trim())
|
|
1001
|
+
throw new GlossaryError("invalid", "Say who decides (--by <handle>).");
|
|
1002
|
+
const p = parseRecord({
|
|
1003
|
+
schemaVersion: 2,
|
|
1004
|
+
kind: "CuratedDecision",
|
|
1005
|
+
id,
|
|
1006
|
+
status: "current",
|
|
1007
|
+
confidence: "asserted",
|
|
1008
|
+
evidence: [{ source: "manual", by: o.by }],
|
|
1009
|
+
target,
|
|
1010
|
+
action,
|
|
1011
|
+
value,
|
|
1012
|
+
reason: o.reason?.trim() || reason,
|
|
1013
|
+
by: o.by,
|
|
1014
|
+
at: o.at ?? now()
|
|
1015
|
+
});
|
|
1016
|
+
if (!p.ok)
|
|
1017
|
+
throw new GlossaryError("invalid", `That decision isn't valid: ${p.issues.map((i) => `${i.path}: ${i.message}`).join("; ")}`);
|
|
1018
|
+
return p.record;
|
|
1019
|
+
}
|
|
1020
|
+
function resolveTermId(termOrId) {
|
|
1021
|
+
const t = termOrId.trim();
|
|
1022
|
+
if (/^term\.[a-z0-9][a-z0-9._-]*$/.test(t))
|
|
1023
|
+
return t;
|
|
1024
|
+
const id = termId(t);
|
|
1025
|
+
if (id === "term.")
|
|
1026
|
+
throw new GlossaryError("invalid", `"${termOrId}" is not a term.`);
|
|
1027
|
+
return id;
|
|
1028
|
+
}
|
|
1029
|
+
function defineDecision(term, definition, o) {
|
|
1030
|
+
const def = definition.trim().replace(/\s+/g, " ");
|
|
1031
|
+
if (def.length < 3)
|
|
1032
|
+
throw new GlossaryError("invalid", "A definition needs a sentence.");
|
|
1033
|
+
const id = resolveTermId(term);
|
|
1034
|
+
const name = o.existing?.term ?? (term.startsWith("term.") ? void 0 : displayTerm(term));
|
|
1035
|
+
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);
|
|
1036
|
+
}
|
|
1037
|
+
function aliasDecision(term, aliases, o) {
|
|
1038
|
+
const id = resolveTermId(term);
|
|
1039
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
1040
|
+
for (const a of aliases.map((x) => x.trim().replace(/\s+/g, " ")).filter(Boolean))
|
|
1041
|
+
if (!byKey.has(termKey(a)))
|
|
1042
|
+
byKey.set(termKey(a), a);
|
|
1043
|
+
const names = [...byKey.values()].sort();
|
|
1044
|
+
if (!names.length)
|
|
1045
|
+
throw new GlossaryError("invalid", "Give at least one other name.");
|
|
1046
|
+
return decision(decisionId("glossary.alias", id.slice("term.".length)), id, "alias", names, `Other names for ${id}`, o);
|
|
1047
|
+
}
|
|
1048
|
+
function linkDecision(term, recordId, records, o) {
|
|
1049
|
+
const byId = new Map(records.map((r) => [r.id, r]));
|
|
1050
|
+
const [base2, field] = recordId.split("#");
|
|
1051
|
+
const target = byId.get(base2);
|
|
1052
|
+
if (!target)
|
|
1053
|
+
throw new GlossaryError("not-found", `There is no record ${base2} in the catalog.`);
|
|
1054
|
+
if (field !== void 0) {
|
|
1055
|
+
if (target.kind !== "Schema" || !target.fields.some((f) => f.name === field))
|
|
1056
|
+
throw new GlossaryError("not-found", `${base2} has no schema field ${field}.`);
|
|
1057
|
+
} else if (target.kind === "Column") {
|
|
1058
|
+
return decision(decisionId("glossary.link", target.id), target.id, "assert", { glossaryTerm: term.id }, `${target.id} holds the glossary term ${term.term}`, o);
|
|
1059
|
+
}
|
|
1060
|
+
const fields = [.../* @__PURE__ */ new Set([...term.fields, recordId])].sort();
|
|
1061
|
+
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
1062
|
}
|
|
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);
|
|
1063
|
+
async function writeDecisions(curatedDir, decisions) {
|
|
1043
1064
|
const out = [];
|
|
1044
|
-
for (const
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
out.push(r);
|
|
1065
|
+
for (const d of decisions) {
|
|
1066
|
+
const rel = recordPath(d);
|
|
1067
|
+
const full = join2(curatedDir, ...rel.split("/"));
|
|
1068
|
+
await mkdir2(dirname(full), { recursive: true });
|
|
1069
|
+
await writeFile2(full, serializeRecord(d), "utf8");
|
|
1070
|
+
out.push(rel);
|
|
1051
1071
|
}
|
|
1052
|
-
return
|
|
1072
|
+
return out;
|
|
1053
1073
|
}
|
|
1054
1074
|
|
|
1055
1075
|
// packages/glossary/dist/scan.js
|
|
1076
|
+
import { mkdir as mkdir3, readdir as readdir2, readFile as readFile2, rm as rm2, writeFile as writeFile3 } from "node:fs/promises";
|
|
1077
|
+
import { dirname as dirname2, join as join3, relative, sep } from "node:path";
|
|
1056
1078
|
async function readRecords(dir) {
|
|
1057
1079
|
const records = [];
|
|
1058
1080
|
const problems = [];
|
|
@@ -1174,16 +1196,16 @@ async function scanGlossary(root, o = {}) {
|
|
|
1174
1196
|
}
|
|
1175
1197
|
}
|
|
1176
1198
|
const before = new Map(g.base.map((r) => [`${r.kind}/${r.id}`, r]));
|
|
1177
|
-
const put = async (rel,
|
|
1199
|
+
const put = async (rel, text2) => {
|
|
1178
1200
|
const full = join3(generated, ...rel.split("/"));
|
|
1179
1201
|
await mkdir3(dirname2(full), { recursive: true });
|
|
1180
|
-
await writeFile3(full,
|
|
1202
|
+
await writeFile3(full, text2, "utf8");
|
|
1181
1203
|
written.push(rel);
|
|
1182
1204
|
};
|
|
1183
|
-
for (const [rel,
|
|
1205
|
+
for (const [rel, text2] of want) {
|
|
1184
1206
|
const cur = await readFile2(join3(generated, ...rel.split("/")), "utf8").catch(() => void 0);
|
|
1185
|
-
if (cur !==
|
|
1186
|
-
await put(rel,
|
|
1207
|
+
if (cur !== text2)
|
|
1208
|
+
await put(rel, text2);
|
|
1187
1209
|
}
|
|
1188
1210
|
const termIds = new Set(g.terms.map((t) => t.id));
|
|
1189
1211
|
for (const r of g.records) {
|
|
@@ -1221,6 +1243,49 @@ async function completeGlossaryDefinitions(root, run, o = {}) {
|
|
|
1221
1243
|
return { ...scanned, propose };
|
|
1222
1244
|
}
|
|
1223
1245
|
|
|
1246
|
+
// packages/scanner/dist/federate-loose.js
|
|
1247
|
+
function resolveFederatedLooseEnds(o) {
|
|
1248
|
+
const originOf = (r) => (r.flags ?? []).find((f) => f.startsWith("federated:"))?.slice("federated:".length);
|
|
1249
|
+
const loose = o.federated.filter((r) => r.kind === "Unresolved");
|
|
1250
|
+
if (!loose.length)
|
|
1251
|
+
return { federated: o.federated, resolved: /* @__PURE__ */ new Map() };
|
|
1252
|
+
const fedComponents = o.federated.filter((r) => r.kind === "Component");
|
|
1253
|
+
const result = o.merge([...o.raw, ...fedComponents, ...loose]);
|
|
1254
|
+
const have = new Set(o.local.map((r) => `${r.kind}/${r.id}`));
|
|
1255
|
+
const known = /* @__PURE__ */ new Set([
|
|
1256
|
+
...o.local.filter((r) => r.kind === "Component" || r.kind === "System").map((r) => r.id),
|
|
1257
|
+
...fedComponents.map((r) => r.id)
|
|
1258
|
+
]);
|
|
1259
|
+
const origin = new Map(fedComponents.map((c) => [c.id, originOf(c)]));
|
|
1260
|
+
const stillLoose = new Set(result.records.filter((r) => r.kind === "Unresolved").map((r) => r.id));
|
|
1261
|
+
const added = [];
|
|
1262
|
+
for (const r of result.records) {
|
|
1263
|
+
if (r.kind !== "Interface" || have.has(`Interface/${r.id}`))
|
|
1264
|
+
continue;
|
|
1265
|
+
const i = r;
|
|
1266
|
+
const from = origin.get(i.from) ?? origin.get(i.to);
|
|
1267
|
+
if (!from || !known.has(i.from) || !known.has(i.to))
|
|
1268
|
+
continue;
|
|
1269
|
+
added.push({ ...r, flags: [.../* @__PURE__ */ new Set([...r.flags ?? [], `federated:${from}`])] });
|
|
1270
|
+
}
|
|
1271
|
+
const key = (e) => JSON.stringify(e);
|
|
1272
|
+
const carried = new Set(added.flatMap((i) => i.evidence.map(key)));
|
|
1273
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
1274
|
+
const kept2 = o.federated.filter((r) => {
|
|
1275
|
+
if (r.kind !== "Unresolved" || stillLoose.has(r.id))
|
|
1276
|
+
return true;
|
|
1277
|
+
if (!r.evidence.some((e) => e.source !== "export" && carried.has(key(e))))
|
|
1278
|
+
return true;
|
|
1279
|
+
const id = originOf(r) ?? "";
|
|
1280
|
+
resolved.set(id, (resolved.get(id) ?? 0) + 1);
|
|
1281
|
+
return false;
|
|
1282
|
+
});
|
|
1283
|
+
return {
|
|
1284
|
+
federated: [...kept2, ...added].sort((a, b) => a.kind === b.kind ? a.id.localeCompare(b.id) : a.kind.localeCompare(b.kind)),
|
|
1285
|
+
resolved
|
|
1286
|
+
};
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1224
1289
|
// packages/scanner/dist/files.js
|
|
1225
1290
|
import { readdir as readdir3, readFile as readFile3, rm as rm3 } from "node:fs/promises";
|
|
1226
1291
|
import { join as join4, relative as relative2, resolve, sep as sep2 } from "node:path";
|
|
@@ -1274,8 +1339,8 @@ async function readCatalogFiles(dir, cacheRoot) {
|
|
|
1274
1339
|
name: "catalog",
|
|
1275
1340
|
version: CATALOG_CACHE_VERSION,
|
|
1276
1341
|
cacheRoot,
|
|
1277
|
-
parse: (
|
|
1278
|
-
const r = deserializeRecord(
|
|
1342
|
+
parse: (text2) => {
|
|
1343
|
+
const r = deserializeRecord(text2);
|
|
1279
1344
|
return r.ok ? { ok: true, value: r.record } : { ok: false, message: r.issues.map((i) => `${i.path}: ${i.message}`).join("; ") };
|
|
1280
1345
|
}
|
|
1281
1346
|
})) {
|
|
@@ -1292,14 +1357,14 @@ async function writeCatalog(dir, records) {
|
|
|
1292
1357
|
const wanted = new Map(records.map((r) => [recordPath(r), serializeRecord(r)]));
|
|
1293
1358
|
let written = 0;
|
|
1294
1359
|
let unchanged = 0;
|
|
1295
|
-
for (const [rel,
|
|
1360
|
+
for (const [rel, text2] of wanted) {
|
|
1296
1361
|
const full = join4(dir, ...rel.split("/"));
|
|
1297
1362
|
const current = await readFile3(full, "utf8").catch(() => void 0);
|
|
1298
|
-
if (current ===
|
|
1363
|
+
if (current?.replace(/\r\n?/g, "\n") === text2) {
|
|
1299
1364
|
unchanged++;
|
|
1300
1365
|
continue;
|
|
1301
1366
|
}
|
|
1302
|
-
await writeFileAtomic(full,
|
|
1367
|
+
await writeFileAtomic(full, text2);
|
|
1303
1368
|
written++;
|
|
1304
1369
|
}
|
|
1305
1370
|
let removed = 0;
|
|
@@ -1380,16 +1445,19 @@ function prototypeScanNotes(op, only) {
|
|
|
1380
1445
|
function isPrototypeRepo(op, repo) {
|
|
1381
1446
|
return estateRepos(op).some((r) => r.id === repo && isPrototypeType(r.type));
|
|
1382
1447
|
}
|
|
1383
|
-
function estateMergeInput(op, repos, records, modulesOf = (id) => repos.find((r) => r.id === id)?.modules ?? []) {
|
|
1448
|
+
function estateMergeInput(op, repos, records, modulesOf = (id) => repos.find((r) => r.id === id)?.modules ?? [], read) {
|
|
1384
1449
|
return {
|
|
1385
1450
|
records,
|
|
1451
|
+
// ADR 0180: the organisation's component types, so a cited type is never demoted in a merge.
|
|
1452
|
+
componentTypes: new Set(effectiveComponentTypes(op.componentTypes).map((t) => t.id)),
|
|
1386
1453
|
repos: repos.flatMap((r) => [
|
|
1387
1454
|
{
|
|
1388
1455
|
id: r.id,
|
|
1389
1456
|
type: r.type,
|
|
1390
1457
|
...r.component ? { component: r.component } : {},
|
|
1391
1458
|
...r.system ? { system: r.system } : {},
|
|
1392
|
-
hosts: r.hosts
|
|
1459
|
+
hosts: r.hosts,
|
|
1460
|
+
...read?.has(r.id) ? { read: true } : {}
|
|
1393
1461
|
},
|
|
1394
1462
|
// A module's hosts join calls to its own component (ADR 0057).
|
|
1395
1463
|
...modulesOf(r.id).filter((m) => m.hosts.length > 0).map((m) => ({
|
|
@@ -1405,15 +1473,346 @@ function estateMergeInput(op, repos, records, modulesOf = (id) => repos.find((r)
|
|
|
1405
1473
|
profilePath: op.profiles[0] ? `org-pack/profiles/${op.profiles[0].platform}/profile.yaml` : void 0
|
|
1406
1474
|
};
|
|
1407
1475
|
}
|
|
1408
|
-
function mergeEstate(op, repos, records, modulesOf) {
|
|
1409
|
-
return mergeCatalog(estateMergeInput(op, repos, records, modulesOf));
|
|
1476
|
+
function mergeEstate(op, repos, records, modulesOf, read) {
|
|
1477
|
+
return mergeCatalog(estateMergeInput(op, repos, records, modulesOf, read));
|
|
1410
1478
|
}
|
|
1411
1479
|
function curatedDirOf(root, op) {
|
|
1412
1480
|
return join5(root, ...op.sources.layout.catalog.split("/"), "curated");
|
|
1413
1481
|
}
|
|
1414
1482
|
|
|
1483
|
+
// packages/scanner/dist/facts-scan.js
|
|
1484
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
1485
|
+
import { join as join6 } from "node:path";
|
|
1486
|
+
|
|
1487
|
+
// packages/scanner/dist/facts-catalog.js
|
|
1488
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
1489
|
+
|
|
1490
|
+
// packages/scanner/dist/target-suggest.js
|
|
1491
|
+
var TARGETS_MAX_SUGGESTED = 5;
|
|
1492
|
+
var TARGETS_ANOTHER = TARGETS_ANOTHER_LABEL;
|
|
1493
|
+
var TARGETS_NO_EVIDENCE = "no evidence; listed so you can pick it";
|
|
1494
|
+
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(" "));
|
|
1495
|
+
function nameWords(s) {
|
|
1496
|
+
if (!s)
|
|
1497
|
+
return [];
|
|
1498
|
+
return [
|
|
1499
|
+
...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))
|
|
1500
|
+
];
|
|
1501
|
+
}
|
|
1502
|
+
var segs = (p) => p.split(/[?#]/)[0].split("/").filter(Boolean).map((x) => /^[{:<$]/.test(x) ? "{}" : x);
|
|
1503
|
+
var sameSeg = (a, b) => a === "{}" || b === "{}" || a === b;
|
|
1504
|
+
var under = (path, prefix) => {
|
|
1505
|
+
const a = segs(prefix);
|
|
1506
|
+
const b = segs(path);
|
|
1507
|
+
return a.length > 0 && a.length <= b.length && a.every((x, i) => x === b[i]);
|
|
1508
|
+
};
|
|
1509
|
+
function serves(entry, call, bases) {
|
|
1510
|
+
const e = segs(entry);
|
|
1511
|
+
const tries = [
|
|
1512
|
+
segs(call),
|
|
1513
|
+
...bases.filter((b) => under(call, b)).map((b) => segs(call).slice(segs(b).length))
|
|
1514
|
+
];
|
|
1515
|
+
return tries.some((c) => c.length === e.length && c.every((x, i) => sameSeg(x, e[i])));
|
|
1516
|
+
}
|
|
1517
|
+
function clientOf(f) {
|
|
1518
|
+
const q = f.cite.quote ?? "";
|
|
1519
|
+
const named2 = /\b([A-Z][A-Za-z0-9]*(?:Client|Service|Api|Feign|Gateway|Proxy))\b/.exec(q)?.[1];
|
|
1520
|
+
if (named2)
|
|
1521
|
+
return named2;
|
|
1522
|
+
const file = f.cite.file.split("/").pop().replace(/\.[^.]+$/, "");
|
|
1523
|
+
return /(Client|Service|Api|Feign|Gateway|Proxy)$/.test(file) ? file : void 0;
|
|
1524
|
+
}
|
|
1525
|
+
function hostOf(f, facts) {
|
|
1526
|
+
const fromUrl = (u) => /^[a-z][a-z0-9+.-]*:\/\/([^/:?#]+)/i.exec(u)?.[1];
|
|
1527
|
+
if (f.host)
|
|
1528
|
+
return fromUrl(f.host) ?? f.host.split(/[/:]/)[0];
|
|
1529
|
+
if (f.base) {
|
|
1530
|
+
const cfg = facts.find((x) => x.kind === "config" && x.key === f.base && !!x.value);
|
|
1531
|
+
if (!cfg?.value)
|
|
1532
|
+
return void 0;
|
|
1533
|
+
return fromUrl(cfg.value) ?? (/^[a-z0-9-]+(\.[a-z0-9-]+)+(:\d+)?$/i.test(cfg.value) ? cfg.value.split(":")[0] : void 0);
|
|
1534
|
+
}
|
|
1535
|
+
return void 0;
|
|
1536
|
+
}
|
|
1537
|
+
var answerLabel = (q) => q.answer?.option !== void 0 ? q.options[q.answer.option - 1]?.label : q.answer?.text;
|
|
1538
|
+
var NOT_A_TARGET = /* @__PURE__ */ new Set(["None of these", TARGETS_SPLIT_LABEL, TARGETS_ANOTHER_LABEL]);
|
|
1539
|
+
function candidates(callerRepo, ctx) {
|
|
1540
|
+
const manifest = estateRepos(ctx.op).filter((r) => r.id !== callerRepo || !!ctx.only);
|
|
1541
|
+
const systems = ctx.op.profiles.flatMap((p) => p.profile?.systems ?? []);
|
|
1542
|
+
const reposOfSystem = /* @__PURE__ */ new Map();
|
|
1543
|
+
for (const r of estateRepos(ctx.op))
|
|
1544
|
+
if (r.system)
|
|
1545
|
+
reposOfSystem.set(r.system, (reposOfSystem.get(r.system) ?? 0) + 1);
|
|
1546
|
+
const read = new Set((ctx.estate ?? []).filter((x) => x.facts.length > 0).map((x) => x.repo));
|
|
1547
|
+
const repoOf = /* @__PURE__ */ new Map();
|
|
1548
|
+
for (const r of ctx.records)
|
|
1549
|
+
if (r.kind === "Component" && r.repo)
|
|
1550
|
+
repoOf.set(r.id, r.repo);
|
|
1551
|
+
if (!ctx.estate)
|
|
1552
|
+
for (const repo of repoOf.values())
|
|
1553
|
+
read.add(repo);
|
|
1554
|
+
const endpoints = ctx.records.filter((r) => r.kind === "Endpoint");
|
|
1555
|
+
const deploys = (ctx.estate ?? []).flatMap((x) => x.facts.filter((f) => f.kind === "deploy" && !!f.basePath));
|
|
1556
|
+
const out = [];
|
|
1557
|
+
for (const m of manifest) {
|
|
1558
|
+
const own2 = new Set([m.id, m.component, ...m.modules.map((x) => x.component)].filter((x) => !!x).map(factIdPart));
|
|
1559
|
+
const sys = m.system && reposOfSystem.get(m.system) === 1 ? systems.find((s) => s.id === m.system) : void 0;
|
|
1560
|
+
const names = /* @__PURE__ */ new Map();
|
|
1561
|
+
for (const n of [m.id, m.component, ...m.modules.map((x) => x.component)])
|
|
1562
|
+
for (const w of nameWords(n))
|
|
1563
|
+
if (!names.has(w))
|
|
1564
|
+
names.set(w, n);
|
|
1565
|
+
for (const a of sys ? [sys.id, ...sys.aliases] : [])
|
|
1566
|
+
for (const w of nameWords(a))
|
|
1567
|
+
if (!names.has(w))
|
|
1568
|
+
names.set(w, a);
|
|
1569
|
+
const bases = [];
|
|
1570
|
+
if (m.basePath)
|
|
1571
|
+
bases.push({ path: m.basePath, from: "the manifest's basePath" });
|
|
1572
|
+
for (const x of m.modules)
|
|
1573
|
+
if (x.basePath)
|
|
1574
|
+
bases.push({ path: x.basePath, from: `the basePath of its module ${x.component}` });
|
|
1575
|
+
for (const d of deploys) {
|
|
1576
|
+
const reaches = d.exposes ? factIdPart(d.exposes) : d.repo === m.id ? m.id : void 0;
|
|
1577
|
+
if (!reaches || !(own2.has(reaches) || d.repo === m.id && !d.exposes))
|
|
1578
|
+
continue;
|
|
1579
|
+
const what2 = d.platform === "apigee" ? "its proxy's BasePath" : `the ${d.resourceType ?? "route"} ${d.resourceName ?? d.id}`;
|
|
1580
|
+
bases.push({ path: d.basePath, from: `${what2} (${d.repo} ${d.cite.file}:${d.cite.line})` });
|
|
1581
|
+
}
|
|
1582
|
+
const comps = [
|
|
1583
|
+
...new Set(endpoints.filter((e) => repoOf.get(e.component) === m.id).map((e) => e.component))
|
|
1584
|
+
].sort();
|
|
1585
|
+
const isRead = read.has(m.id);
|
|
1586
|
+
const forLabel = comps.length ? comps : [m.id];
|
|
1587
|
+
for (const label2 of forLabel) {
|
|
1588
|
+
const mine = endpoints.filter((e) => e.component === label2);
|
|
1589
|
+
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) => ({
|
|
1590
|
+
...f.method ? { method: f.method.toUpperCase() } : {},
|
|
1591
|
+
path: f.path,
|
|
1592
|
+
at: `${f.cite.file}:${f.cite.line}`
|
|
1593
|
+
}));
|
|
1594
|
+
for (const e of mine)
|
|
1595
|
+
if (!entries.some((x) => x.path === e.pathOrTopic))
|
|
1596
|
+
entries.push({
|
|
1597
|
+
...e.method ? { method: e.method.toUpperCase() } : {},
|
|
1598
|
+
path: e.pathOrTopic,
|
|
1599
|
+
at: e.id
|
|
1600
|
+
});
|
|
1601
|
+
out.push({
|
|
1602
|
+
s: { label: label2, kind: "repo", repo: m.id, read: isRead, score: 0, why: [] },
|
|
1603
|
+
names,
|
|
1604
|
+
bases,
|
|
1605
|
+
entries,
|
|
1606
|
+
...m.description?.text ? { description: m.description.text } : {},
|
|
1607
|
+
hosts: m.hosts.map((h) => h.toLowerCase())
|
|
1608
|
+
});
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
for (const c of ctx.only ?? [])
|
|
1612
|
+
if (!out.some((x) => x.s.label === c))
|
|
1613
|
+
out.push({
|
|
1614
|
+
s: {
|
|
1615
|
+
label: c,
|
|
1616
|
+
kind: "repo",
|
|
1617
|
+
repo: repoOf.get(c) ?? callerRepo,
|
|
1618
|
+
read: true,
|
|
1619
|
+
score: 0,
|
|
1620
|
+
why: []
|
|
1621
|
+
},
|
|
1622
|
+
names: /* @__PURE__ */ new Map(),
|
|
1623
|
+
bases: [],
|
|
1624
|
+
entries: [],
|
|
1625
|
+
hosts: []
|
|
1626
|
+
});
|
|
1627
|
+
const own = new Set(estateRepos(ctx.op).flatMap((r) => r.system ? [r.system] : []));
|
|
1628
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1629
|
+
for (const s of systems) {
|
|
1630
|
+
if (!(s.boundary === "vendor" || !own.has(s.id)) || seen.has(s.id))
|
|
1631
|
+
continue;
|
|
1632
|
+
seen.add(s.id);
|
|
1633
|
+
const names = /* @__PURE__ */ new Map();
|
|
1634
|
+
for (const n of [s.id, s.name, ...s.aliases])
|
|
1635
|
+
for (const w of nameWords(n))
|
|
1636
|
+
if (!names.has(w))
|
|
1637
|
+
names.set(w, n);
|
|
1638
|
+
out.push({
|
|
1639
|
+
s: {
|
|
1640
|
+
label: s.id,
|
|
1641
|
+
kind: "outside",
|
|
1642
|
+
read: false,
|
|
1643
|
+
name: s.name,
|
|
1644
|
+
boundary: s.boundary,
|
|
1645
|
+
score: 0,
|
|
1646
|
+
why: []
|
|
1647
|
+
},
|
|
1648
|
+
names,
|
|
1649
|
+
bases: [],
|
|
1650
|
+
entries: [],
|
|
1651
|
+
hosts: s.aliases.map((a) => a.toLowerCase())
|
|
1652
|
+
});
|
|
1653
|
+
}
|
|
1654
|
+
return out;
|
|
1655
|
+
}
|
|
1656
|
+
function targetSuggestions(f, ctx) {
|
|
1657
|
+
const all = candidates(f.repo, ctx);
|
|
1658
|
+
const df = /* @__PURE__ */ new Map();
|
|
1659
|
+
for (const c of all)
|
|
1660
|
+
for (const w of c.names.keys())
|
|
1661
|
+
df.set(w, (df.get(w) ?? 0) + 1);
|
|
1662
|
+
const telling = (w) => (df.get(w) ?? 0) <= Math.max(2, Math.floor(all.length / 3));
|
|
1663
|
+
const host = hostOf(f, ctx.facts)?.toLowerCase();
|
|
1664
|
+
const client = clientOf(f);
|
|
1665
|
+
const said = [
|
|
1666
|
+
{ from: f.base ? `the config key ${f.base}` : "", words: nameWords(f.base), weight: 4 },
|
|
1667
|
+
{ from: client ? `the client ${client}` : "", words: nameWords(client), weight: 4 },
|
|
1668
|
+
{
|
|
1669
|
+
from: host ? `the host label ${host.split(".")[0]} (${host})` : "",
|
|
1670
|
+
words: host ? nameWords(host.split(".")[0]) : [],
|
|
1671
|
+
weight: 4
|
|
1672
|
+
},
|
|
1673
|
+
{
|
|
1674
|
+
from: `the path ${f.path}`,
|
|
1675
|
+
words: nameWords(segs(f.path).filter((x) => x !== "{}").join(" ")),
|
|
1676
|
+
weight: 2
|
|
1677
|
+
}
|
|
1678
|
+
].filter((x) => x.from && x.words.length);
|
|
1679
|
+
const method = f.method?.toUpperCase();
|
|
1680
|
+
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}`));
|
|
1681
|
+
const only = ctx.only ? new Set(ctx.only) : void 0;
|
|
1682
|
+
const out = [];
|
|
1683
|
+
for (const c of all) {
|
|
1684
|
+
if (only && c.s.kind === "repo" && !only.has(c.s.label))
|
|
1685
|
+
continue;
|
|
1686
|
+
const s = { ...c.s, why: [] };
|
|
1687
|
+
const add = (n, why) => {
|
|
1688
|
+
s.score += n;
|
|
1689
|
+
s.why.push(why);
|
|
1690
|
+
};
|
|
1691
|
+
if (only?.has(c.s.label))
|
|
1692
|
+
add(6, "the join found an entry of it that serves the call");
|
|
1693
|
+
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)));
|
|
1694
|
+
if (hit && !only) {
|
|
1695
|
+
const what2 = [hit.method, hit.path].filter(Boolean).join(" ");
|
|
1696
|
+
add(6, `serves ${what2} (${hit.at})`);
|
|
1697
|
+
s.serves = [what2];
|
|
1698
|
+
}
|
|
1699
|
+
const base2 = c.bases.find((b) => under(f.path, b.path));
|
|
1700
|
+
if (base2)
|
|
1701
|
+
add(5, `its basePath ${base2.path} matches: ${base2.from}`);
|
|
1702
|
+
if (host && c.hosts.includes(host))
|
|
1703
|
+
add(8, `it lists ${host} under hosts`);
|
|
1704
|
+
for (const x of said) {
|
|
1705
|
+
const w = x.words.find((y) => c.names.has(y) && telling(y));
|
|
1706
|
+
if (w)
|
|
1707
|
+
add(x.weight, `named like ${x.from}`);
|
|
1708
|
+
}
|
|
1709
|
+
if (c.description) {
|
|
1710
|
+
const d = new Set(nameWords(c.description));
|
|
1711
|
+
const w = said.flatMap((x) => x.words).find((y) => d.has(y) && telling(y));
|
|
1712
|
+
if (w)
|
|
1713
|
+
add(2, `its description mentions ${w}`);
|
|
1714
|
+
}
|
|
1715
|
+
const named2 = earlier.filter((q) => {
|
|
1716
|
+
const l = answerLabel(q);
|
|
1717
|
+
if (!l || NOT_A_TARGET.has(l))
|
|
1718
|
+
return false;
|
|
1719
|
+
const t = slugify(l);
|
|
1720
|
+
return t === slugify(c.s.label) || !!c.s.repo && t === slugify(c.s.repo);
|
|
1721
|
+
}).length;
|
|
1722
|
+
if (named2)
|
|
1723
|
+
add(3, `picked for ${named2} other ${named2 === 1 ? "call" : "calls"} of ${f.repo}`);
|
|
1724
|
+
if (!s.score)
|
|
1725
|
+
s.why.push(TARGETS_NO_EVIDENCE);
|
|
1726
|
+
s.why = [...new Set(s.why)];
|
|
1727
|
+
out.push(s);
|
|
1728
|
+
}
|
|
1729
|
+
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) || a.label.localeCompare(b.label));
|
|
1730
|
+
}
|
|
1731
|
+
function offeredSuggestions(ranked, o) {
|
|
1732
|
+
const max = o.max ?? TARGETS_MAX_SUGGESTED;
|
|
1733
|
+
const strong = ranked.filter((s) => s.score >= 2);
|
|
1734
|
+
const fill = ranked.filter((s) => s.score < 2 && (s.kind === "outside" || s.read && o.serving.has(s.label)));
|
|
1735
|
+
return [...strong, ...fill].slice(0, max);
|
|
1736
|
+
}
|
|
1737
|
+
function targetPicker(op, read = /* @__PURE__ */ new Set()) {
|
|
1738
|
+
const repos = estateRepos(op).map((r) => ({
|
|
1739
|
+
id: r.id,
|
|
1740
|
+
kind: "repo",
|
|
1741
|
+
name: r.component && r.component !== r.id ? `${r.id} (${r.component})` : r.id,
|
|
1742
|
+
read: read.has(r.id)
|
|
1743
|
+
})).sort((a, b) => a.id.localeCompare(b.id));
|
|
1744
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1745
|
+
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) => a.id.localeCompare(b.id));
|
|
1746
|
+
const owned = new Set(op.profiles.flatMap((p) => (p.profile?.systems ?? []).flatMap((s) => s.owner ? [s.owner] : [])));
|
|
1747
|
+
const teams = (op.roles?.teams ?? []).map((t) => {
|
|
1748
|
+
const name = t.name ?? t.id;
|
|
1749
|
+
const has = owned.has(t.id);
|
|
1750
|
+
return {
|
|
1751
|
+
id: t.id,
|
|
1752
|
+
kind: "team",
|
|
1753
|
+
name,
|
|
1754
|
+
...has ? {} : {
|
|
1755
|
+
adds: {
|
|
1756
|
+
id: slugify(`${t.id}-system`),
|
|
1757
|
+
name: `${name}'s system`,
|
|
1758
|
+
boundary: "internal",
|
|
1759
|
+
owner: t.id
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
};
|
|
1763
|
+
}).sort((a, b) => a.id.localeCompare(b.id));
|
|
1764
|
+
return [...repos, ...systems, ...teams];
|
|
1765
|
+
}
|
|
1766
|
+
function pickerLines(picks) {
|
|
1767
|
+
const of = (k) => picks.filter((p) => p.kind === k);
|
|
1768
|
+
return [
|
|
1769
|
+
`Repos (${of("repo").length}): ${of("repo").map((p) => `${p.id}${p.read ? "" : " (not read yet)"}`).join(", ") || "none"}`,
|
|
1770
|
+
`Systems (${of("system").length}): ${of("system").map((p) => `${p.id} (${p.boundary})`).join(", ") || "none"}`,
|
|
1771
|
+
`Teams (${of("team").length}): ${of("team").map((p) => `${p.id}${p.adds ? " (no system yet)" : ""}`).join(", ") || "none"}`
|
|
1772
|
+
];
|
|
1773
|
+
}
|
|
1774
|
+
function topicQuestionDrafts(repo, records) {
|
|
1775
|
+
const out = /* @__PURE__ */ new Map();
|
|
1776
|
+
for (const r of records) {
|
|
1777
|
+
if (r.kind !== "Unresolved")
|
|
1778
|
+
continue;
|
|
1779
|
+
const x = r;
|
|
1780
|
+
if (x.unresolvedKind !== "event-publish" && x.unresolvedKind !== "event-subscribe")
|
|
1781
|
+
continue;
|
|
1782
|
+
const topic = String(x.raw);
|
|
1783
|
+
if (!topic || topic.includes("${") || /\s/.test(topic))
|
|
1784
|
+
continue;
|
|
1785
|
+
const ev = x.evidence.filter((e) => e.repo === repo && e.path && e.line && !/[\s:]/.test(e.path)).map((e) => `${e.path}:${e.line}`).sort();
|
|
1786
|
+
if (!ev.length)
|
|
1787
|
+
continue;
|
|
1788
|
+
const side = x.unresolvedKind === "event-publish" ? "publish" : "listen";
|
|
1789
|
+
const question = topicQuestionText(topic, x.owner ?? repo, side);
|
|
1790
|
+
const draft = {
|
|
1791
|
+
question,
|
|
1792
|
+
options: [
|
|
1793
|
+
{
|
|
1794
|
+
label: TOPICS_OUTSIDE_LABEL,
|
|
1795
|
+
means: `${topic} belongs to another team's system or a vendor's: name it, or the team, from the list`,
|
|
1796
|
+
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`
|
|
1797
|
+
},
|
|
1798
|
+
{
|
|
1799
|
+
label: "None of these",
|
|
1800
|
+
means: `Nothing outside the estate owns ${topic}, or a repo not read yet serves it`,
|
|
1801
|
+
changes: `It stays a loose end, and this isn't asked again`
|
|
1802
|
+
}
|
|
1803
|
+
],
|
|
1804
|
+
evidence: [...new Set(ev)],
|
|
1805
|
+
scope: "repo"
|
|
1806
|
+
};
|
|
1807
|
+
const id = questionId({ step: TOPICS_STEP_ID, repo, question, evidence: draft.evidence });
|
|
1808
|
+
if (!out.has(id))
|
|
1809
|
+
out.set(id, { topic, id, draft });
|
|
1810
|
+
}
|
|
1811
|
+
return [...out.values()].sort((a, b) => a.topic.localeCompare(b.topic));
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1415
1814
|
// packages/scanner/dist/facts-catalog.js
|
|
1416
|
-
var TARGETS_STEP =
|
|
1815
|
+
var TARGETS_STEP = TARGETS_STEP_ID;
|
|
1417
1816
|
var TARGETS_NONE = "None of these";
|
|
1418
1817
|
var byKindId = (a, b) => a.kind === b.kind ? a.id.localeCompare(b.id) : a.kind.localeCompare(b.kind);
|
|
1419
1818
|
var URL_RE = /^[a-z][a-z0-9+.-]*:\/\//i;
|
|
@@ -1433,6 +1832,42 @@ var EVENT_CALLS = /* @__PURE__ */ new Set(["event-topic", "mq-queue"]);
|
|
|
1433
1832
|
function needsTarget(f, facts) {
|
|
1434
1833
|
return f.kind === "call" && !f.to && !EVENT_CALLS.has(f.callType) && !(HTTP_CALLS.has(f.callType) && urlOf(f, facts));
|
|
1435
1834
|
}
|
|
1835
|
+
var UNRESOLVED_HOST = /@[A-Za-z_][A-Za-z0-9_.-]*@|\$\{|\{[^}]*\}/;
|
|
1836
|
+
function ruleMayName(f, facts) {
|
|
1837
|
+
if (f.kind !== "call" || EVENT_CALLS.has(f.callType))
|
|
1838
|
+
return false;
|
|
1839
|
+
if (!f.to && !(HTTP_CALLS.has(f.callType) && urlOf(f, facts)))
|
|
1840
|
+
return true;
|
|
1841
|
+
if (f.to)
|
|
1842
|
+
return f.by !== "person" && UNRESOLVED_HOST.test(f.to);
|
|
1843
|
+
if (!HTTP_CALLS.has(f.callType))
|
|
1844
|
+
return false;
|
|
1845
|
+
const url = urlOf(f, facts);
|
|
1846
|
+
const host = url ? /^[a-z][a-z0-9+.-]*:\/\/([^/?#]*)/i.exec(url)?.[1] ?? "" : "";
|
|
1847
|
+
if (!host || UNRESOLVED_HOST.test(host) || UNRESOLVED_HOST.test(f.host ?? ""))
|
|
1848
|
+
return true;
|
|
1849
|
+
if (f.host || !f.base)
|
|
1850
|
+
return false;
|
|
1851
|
+
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));
|
|
1852
|
+
return hosts.size > 1;
|
|
1853
|
+
}
|
|
1854
|
+
var MAX_ID = 120;
|
|
1855
|
+
function remapIds(remap) {
|
|
1856
|
+
return (id) => {
|
|
1857
|
+
const exact = remap.get(id);
|
|
1858
|
+
if (exact)
|
|
1859
|
+
return exact;
|
|
1860
|
+
for (const [from, to] of remap)
|
|
1861
|
+
if (id.startsWith(`${from}.`)) {
|
|
1862
|
+
const out = to + id.slice(from.length);
|
|
1863
|
+
if (out.length <= MAX_ID)
|
|
1864
|
+
return out;
|
|
1865
|
+
const hash = createHash2("sha256").update(out, "utf8").digest("hex").slice(0, 16);
|
|
1866
|
+
return `${out.slice(0, MAX_ID - hash.length - 1).replace(/[-._]+$/, "")}.${hash}`;
|
|
1867
|
+
}
|
|
1868
|
+
return id;
|
|
1869
|
+
};
|
|
1870
|
+
}
|
|
1436
1871
|
function targetQuestionFor(f) {
|
|
1437
1872
|
if (f.kind !== "call")
|
|
1438
1873
|
return void 0;
|
|
@@ -1449,62 +1884,301 @@ var SERVES = {
|
|
|
1449
1884
|
webhook: ["webhook"],
|
|
1450
1885
|
sftp: ["file-drop"]
|
|
1451
1886
|
};
|
|
1452
|
-
function
|
|
1887
|
+
function targetAsks(facts, records, asks) {
|
|
1453
1888
|
const repoOf = /* @__PURE__ */ new Map();
|
|
1454
1889
|
for (const r of records)
|
|
1455
1890
|
if (r.kind === "Component" && r.repo)
|
|
1456
1891
|
repoOf.set(r.id, r.repo);
|
|
1457
|
-
const
|
|
1892
|
+
const list = [];
|
|
1893
|
+
for (const f of [...facts].sort((a, b) => a.id.localeCompare(b.id))) {
|
|
1894
|
+
const ask = asks?.find((a) => a.reason === "ambiguous" && a.call.repo === f.repo && a.call.fact === f.id);
|
|
1895
|
+
if (f.kind !== "call" || !needsTarget(f, facts) && !ask)
|
|
1896
|
+
continue;
|
|
1897
|
+
const own = slugify(f.component ?? f.repo).replace(/\./g, "-");
|
|
1898
|
+
const types = new Set(SERVES[f.callType] ?? []);
|
|
1899
|
+
const servers = ask ? [...new Set(ask.candidates.map((c) => c.component))].sort() : [
|
|
1900
|
+
...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))
|
|
1901
|
+
].sort();
|
|
1902
|
+
list.push({ f, q: targetQuestionFor(f), servers });
|
|
1903
|
+
}
|
|
1904
|
+
return { list, repoOf };
|
|
1905
|
+
}
|
|
1906
|
+
var outsideAs = (s) => s.boundary === "vendor" ? "an outside system" : "another team's system";
|
|
1907
|
+
var callWhat = (f) => [f.method?.toUpperCase(), f.path].filter(Boolean).join(" ");
|
|
1908
|
+
function suggestionWhy(s, prefix) {
|
|
1909
|
+
const served = s.serves ?? [];
|
|
1910
|
+
let why = s.why;
|
|
1911
|
+
if (prefix !== void 0 && served.length > 1) {
|
|
1912
|
+
const under2 = prefix === "/" ? "" : ` under ${prefix}`;
|
|
1913
|
+
why = [
|
|
1914
|
+
`serves ${served[0]} and ${served.length - 1} more${under2}`,
|
|
1915
|
+
...why.filter((w) => !w.startsWith("serves "))
|
|
1916
|
+
];
|
|
1917
|
+
}
|
|
1918
|
+
return (why.length ? why : [TARGETS_NO_EVIDENCE]).slice(0, 3).join("; ");
|
|
1919
|
+
}
|
|
1920
|
+
function suggestionOption(s, callerRepo, n, prefix) {
|
|
1921
|
+
const why = suggestionWhy(s, prefix);
|
|
1922
|
+
if (s.kind === "outside")
|
|
1923
|
+
return {
|
|
1924
|
+
label: s.label,
|
|
1925
|
+
means: `${s.name}, ${outsideAs({ boundary: s.boundary ?? "vendor" })}: ${why}`,
|
|
1926
|
+
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`
|
|
1927
|
+
};
|
|
1928
|
+
const who = s.label === s.repo ? `${s.repo}${s.read ? "" : " (not read yet)"}` : `${s.label}, built in ${s.repo}${s.read ? "" : " (not read yet)"}`;
|
|
1929
|
+
const them = n ? `The ${n} calls` : "The call";
|
|
1930
|
+
return {
|
|
1931
|
+
label: s.label,
|
|
1932
|
+
means: `${who}: ${why}`,
|
|
1933
|
+
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`
|
|
1934
|
+
};
|
|
1935
|
+
}
|
|
1936
|
+
var anotherOption = (op) => {
|
|
1937
|
+
const repos = estateRepos(op).length;
|
|
1938
|
+
const systems = new Set(op.profiles.flatMap((p) => (p.profile?.systems ?? []).map((s) => s.id))).size;
|
|
1939
|
+
const teams = op.roles?.teams?.length ?? 0;
|
|
1940
|
+
return {
|
|
1941
|
+
label: TARGETS_ANOTHER,
|
|
1942
|
+
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`,
|
|
1943
|
+
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"
|
|
1944
|
+
};
|
|
1945
|
+
};
|
|
1946
|
+
function offered(members, facts, records, op, asks, ctx) {
|
|
1947
|
+
const merged = /* @__PURE__ */ new Map();
|
|
1948
|
+
for (const m of members) {
|
|
1949
|
+
const ambiguous = asks?.some((a) => a.reason === "ambiguous" && a.call.repo === m.f.repo && a.call.fact === m.f.id);
|
|
1950
|
+
for (const s of targetSuggestions(m.f, {
|
|
1951
|
+
op,
|
|
1952
|
+
records,
|
|
1953
|
+
facts,
|
|
1954
|
+
...ctx.estate ? { estate: ctx.estate } : {},
|
|
1955
|
+
...ctx.questions ? { questions: ctx.questions } : {},
|
|
1956
|
+
...ambiguous ? { only: m.servers } : {}
|
|
1957
|
+
})) {
|
|
1958
|
+
const x = merged.get(s.label);
|
|
1959
|
+
if (!x)
|
|
1960
|
+
merged.set(s.label, { ...s, why: [...s.why], serves: [...s.serves ?? []] });
|
|
1961
|
+
else {
|
|
1962
|
+
x.score += s.score;
|
|
1963
|
+
for (const w of s.why)
|
|
1964
|
+
if (!x.why.includes(w))
|
|
1965
|
+
x.why.push(w);
|
|
1966
|
+
for (const w of s.serves ?? [])
|
|
1967
|
+
if (!x.serves.includes(w))
|
|
1968
|
+
x.serves.push(w);
|
|
1969
|
+
if (x.why.length > 1)
|
|
1970
|
+
x.why = x.why.filter((w) => w !== TARGETS_NO_EVIDENCE);
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
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) || a.label.localeCompare(b.label));
|
|
1975
|
+
const serving = new Set(members.flatMap((m) => m.servers));
|
|
1976
|
+
const types = new Set(members.flatMap((m) => SERVES[m.f.callType] ?? []));
|
|
1977
|
+
return offeredSuggestions(ranked, { types, serving });
|
|
1978
|
+
}
|
|
1979
|
+
function targetQuestionDrafts(facts, records, op, asks, ctx = {}) {
|
|
1980
|
+
const { list } = targetAsks(facts, records, asks);
|
|
1981
|
+
return list.map((m) => {
|
|
1982
|
+
const { f, q } = m;
|
|
1983
|
+
const draft = {
|
|
1984
|
+
question: q.question,
|
|
1985
|
+
options: [
|
|
1986
|
+
...offered([m], facts, records, op, asks, ctx).map((s) => suggestionOption(s, f.repo)),
|
|
1987
|
+
anotherOption(op),
|
|
1988
|
+
{
|
|
1989
|
+
label: TARGETS_NONE,
|
|
1990
|
+
means: "Nothing in the estate or the org pack's systems answers it",
|
|
1991
|
+
changes: "The call stays a loose end, and this isn't asked again"
|
|
1992
|
+
}
|
|
1993
|
+
],
|
|
1994
|
+
evidence: q.evidence,
|
|
1995
|
+
scope: "repo"
|
|
1996
|
+
};
|
|
1997
|
+
return {
|
|
1998
|
+
factId: f.id,
|
|
1999
|
+
id: questionId({
|
|
2000
|
+
step: TARGETS_STEP,
|
|
2001
|
+
repo: f.repo,
|
|
2002
|
+
question: q.question,
|
|
2003
|
+
evidence: q.evidence
|
|
2004
|
+
}),
|
|
2005
|
+
repo: f.repo,
|
|
2006
|
+
draft
|
|
2007
|
+
};
|
|
2008
|
+
});
|
|
2009
|
+
}
|
|
2010
|
+
var TARGETS_SPLIT = TARGETS_SPLIT_LABEL;
|
|
2011
|
+
var PREFIX_ROOT = /^(api|rest|services?|v\d+(\.\d+)*)$/i;
|
|
2012
|
+
function targetPathPrefix(path) {
|
|
2013
|
+
const lead = path.startsWith("/") ? "/" : "";
|
|
2014
|
+
const out = [];
|
|
2015
|
+
for (const s of path.split(/[?#]/)[0].split("/").filter(Boolean)) {
|
|
2016
|
+
if (/^[{:<]/.test(s))
|
|
2017
|
+
break;
|
|
2018
|
+
out.push(s);
|
|
2019
|
+
if (!PREFIX_ROOT.test(s))
|
|
2020
|
+
break;
|
|
2021
|
+
}
|
|
2022
|
+
return out.length ? `${lead}${out.join("/")}` : "/";
|
|
2023
|
+
}
|
|
2024
|
+
function groupVia(f) {
|
|
2025
|
+
if (f.to)
|
|
2026
|
+
return ` naming ${f.to}`;
|
|
2027
|
+
if (f.host)
|
|
2028
|
+
return ` on ${f.host}`;
|
|
2029
|
+
if (f.base)
|
|
2030
|
+
return ` through ${f.base}`;
|
|
2031
|
+
return "";
|
|
2032
|
+
}
|
|
2033
|
+
function targetGroupQuestion(repo, f, facts) {
|
|
2034
|
+
const proxy = facts ? proxyOf(f, facts) : void 0;
|
|
2035
|
+
const through = f.base ?? f.host ?? f.to;
|
|
2036
|
+
if (proxy && through) {
|
|
2037
|
+
const p = targetPathPrefix(f.path);
|
|
2038
|
+
return `Which repo serves the routes under ${p === "/" ? "" : p}/** that ${proxy} forwards to ${through}?`;
|
|
2039
|
+
}
|
|
2040
|
+
return `Which repo or system answers the calls ${repo} makes under ${targetPathPrefix(f.path)}${groupVia(f)}?`;
|
|
2041
|
+
}
|
|
2042
|
+
function proxyOf(f, facts) {
|
|
2043
|
+
if (!f.from)
|
|
2044
|
+
return void 0;
|
|
2045
|
+
const from = facts.find((x) => x.id === f.from);
|
|
2046
|
+
if (!from || from.kind !== "entry")
|
|
2047
|
+
return void 0;
|
|
2048
|
+
const proxy = facts.find((x) => x.kind === "deploy" && x.repo === f.repo && x.platform === "apigee" && !!x.basePath);
|
|
2049
|
+
return proxy ? proxy.resourceName ?? f.repo : void 0;
|
|
2050
|
+
}
|
|
2051
|
+
function targetGroupWordings(repo, f, facts) {
|
|
2052
|
+
return [.../* @__PURE__ */ new Set([targetGroupQuestion(repo, f, facts), targetGroupQuestion(repo, f)])];
|
|
2053
|
+
}
|
|
2054
|
+
var repo0 = (members) => members[0].f.repo;
|
|
2055
|
+
var byLine = (a, b) => a.localeCompare(b, "en", { numeric: true });
|
|
2056
|
+
var answerLabel2 = (q) => q.answer?.option !== void 0 ? q.options[q.answer.option - 1]?.label : q.answer?.text;
|
|
2057
|
+
function splitTargetGroups(questions) {
|
|
2058
|
+
return new Set(questions.filter((q) => q.askedBy?.step === TARGETS_STEP && q.state === "answered" && answerLabel2(q) === TARGETS_SPLIT).map((q) => q.question.trim()));
|
|
2059
|
+
}
|
|
2060
|
+
function groupedTargetDrafts(facts, records, op, asks, o) {
|
|
2061
|
+
const { list } = targetAsks(facts, records, asks);
|
|
2062
|
+
const ctx = {
|
|
2063
|
+
...o.estate ? { estate: o.estate } : {},
|
|
2064
|
+
...o.questions ? { questions: o.questions } : {}
|
|
2065
|
+
};
|
|
2066
|
+
const own = new Map(targetQuestionDrafts(facts, records, op, asks, ctx).map((d) => [d.factId, d]));
|
|
2067
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2068
|
+
for (const a of list) {
|
|
2069
|
+
if (!o.ask.has(a.f.id))
|
|
2070
|
+
continue;
|
|
2071
|
+
const k = targetGroupQuestion(a.f.repo, a.f, facts);
|
|
2072
|
+
groups.set(k, [...groups.get(k) ?? [], a]);
|
|
2073
|
+
}
|
|
1458
2074
|
const out = [];
|
|
1459
|
-
for (const
|
|
1460
|
-
const
|
|
1461
|
-
if (
|
|
2075
|
+
for (const [question, members] of groups) {
|
|
2076
|
+
const split = targetGroupWordings(repo0(members), members[0].f, facts).some((w) => o.split?.has(w));
|
|
2077
|
+
if (members.length === 1 || split) {
|
|
2078
|
+
for (const m of members)
|
|
2079
|
+
out.push({ ...own.get(m.f.id), factIds: [m.f.id] });
|
|
1462
2080
|
continue;
|
|
1463
|
-
|
|
1464
|
-
const
|
|
1465
|
-
const
|
|
1466
|
-
const
|
|
1467
|
-
|
|
1468
|
-
].sort();
|
|
1469
|
-
const what = [f.method?.toUpperCase(), f.path].filter(Boolean).join(" ");
|
|
2081
|
+
}
|
|
2082
|
+
const repo = members[0].f.repo;
|
|
2083
|
+
const whats = [...new Set(members.map((m) => callWhat(m.f)))];
|
|
2084
|
+
const listed = whats.length > 5 ? `${whats.slice(0, 5).join(", ")} and ${whats.length - 5} more` : whats.join(", ");
|
|
2085
|
+
const n = members.length;
|
|
2086
|
+
const evidence = [...new Set(members.flatMap((m) => m.q.evidence))].sort(byLine);
|
|
1470
2087
|
const draft = {
|
|
1471
|
-
question
|
|
2088
|
+
question,
|
|
1472
2089
|
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
|
-
})),
|
|
2090
|
+
...offered(members, facts, records, op, asks, ctx).map((s) => suggestionOption(s, repo, n, targetPathPrefix(members[0].f.path))),
|
|
2091
|
+
anotherOption(op),
|
|
2092
|
+
{
|
|
2093
|
+
label: TARGETS_SPLIT,
|
|
2094
|
+
means: `The ${n} calls (${listed}) don't all go to one place`,
|
|
2095
|
+
changes: "Nothing is joined from this answer; each call is asked on its own when the facts are next written"
|
|
2096
|
+
},
|
|
1483
2097
|
{
|
|
1484
2098
|
label: TARGETS_NONE,
|
|
1485
|
-
means: "Nothing in the estate or the org pack's systems answers
|
|
1486
|
-
changes: "The
|
|
2099
|
+
means: "Nothing in the estate or the org pack's systems answers any of them",
|
|
2100
|
+
changes: "The calls stay loose ends, and this isn't asked again"
|
|
1487
2101
|
}
|
|
1488
2102
|
],
|
|
1489
|
-
evidence
|
|
2103
|
+
evidence,
|
|
1490
2104
|
scope: "repo"
|
|
1491
2105
|
};
|
|
1492
2106
|
out.push({
|
|
1493
|
-
factId: f.id,
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
question: q.question,
|
|
1498
|
-
evidence: q.evidence
|
|
1499
|
-
}),
|
|
1500
|
-
repo: f.repo,
|
|
2107
|
+
factId: members[0].f.id,
|
|
2108
|
+
factIds: members.map((m) => m.f.id),
|
|
2109
|
+
id: questionId({ step: TARGETS_STEP, repo, question, evidence }),
|
|
2110
|
+
repo,
|
|
1501
2111
|
draft
|
|
1502
2112
|
});
|
|
1503
2113
|
}
|
|
2114
|
+
return out.sort((a, b) => a.factId.localeCompare(b.factId));
|
|
2115
|
+
}
|
|
2116
|
+
function targetRuleOffers(questions, factsOf) {
|
|
2117
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
2118
|
+
for (const q of questions) {
|
|
2119
|
+
if (q.state !== "open" || q.askedBy?.step !== TARGETS_STEP)
|
|
2120
|
+
continue;
|
|
2121
|
+
const facts = factsOf(q.repo) ?? [];
|
|
2122
|
+
const at3 = new Set(q.evidence);
|
|
2123
|
+
const calls = facts.filter((f) => f.kind === "call" && f.repo === q.repo && at3.has(`${f.cite.file}:${f.cite.line}`));
|
|
2124
|
+
if (!calls.length)
|
|
2125
|
+
continue;
|
|
2126
|
+
const prefixes = new Set(calls.map((c) => targetPathPrefix(c.path)));
|
|
2127
|
+
const hosts = new Set(calls.map((c) => c.host?.replace(/^[a-z][a-z0-9+.-]*:\/\//i, "").split(/[/:]/)[0]));
|
|
2128
|
+
const [prefix] = prefixes;
|
|
2129
|
+
const [host] = hosts;
|
|
2130
|
+
const match = prefixes.size === 1 && prefix !== "/" ? `${prefix}/**` : hosts.size === 1 && host ? host : void 0;
|
|
2131
|
+
if (!match)
|
|
2132
|
+
continue;
|
|
2133
|
+
const key = `${q.repo}
|
|
2134
|
+
${match}`;
|
|
2135
|
+
const x = byKey.get(key) ?? { match, ids: [], calls: 0 };
|
|
2136
|
+
x.ids.push(q.id);
|
|
2137
|
+
x.calls += calls.length;
|
|
2138
|
+
byKey.set(key, x);
|
|
2139
|
+
}
|
|
2140
|
+
const out = /* @__PURE__ */ new Map();
|
|
2141
|
+
for (const x of byKey.values()) {
|
|
2142
|
+
if (x.ids.length < 2)
|
|
2143
|
+
continue;
|
|
2144
|
+
const ids = [...x.ids].sort();
|
|
2145
|
+
for (const id of ids)
|
|
2146
|
+
out.set(id, { match: x.match, ids, calls: x.calls });
|
|
2147
|
+
}
|
|
2148
|
+
return out;
|
|
2149
|
+
}
|
|
2150
|
+
var segmentsOf = (p) => p.split(/[?#]/)[0].split("/").filter(Boolean).map((x) => /^[{:<]/.test(x) ? "{}" : x);
|
|
2151
|
+
function ruleCoversCall(match, f, facts) {
|
|
2152
|
+
if (isPathMatch(match)) {
|
|
2153
|
+
const want = segmentsOf(match.replace(/\/\*\*$/, ""));
|
|
2154
|
+
const got = segmentsOf(f.path);
|
|
2155
|
+
return want.length <= got.length && want.every((x, i) => x === "{}" || got[i] === "{}" || x === got[i]);
|
|
2156
|
+
}
|
|
2157
|
+
const url = urlOf(f, facts);
|
|
2158
|
+
const hp = url ? urlHostPort(url) : void 0;
|
|
2159
|
+
return !!hp?.host && hostMatches(match, hp.host, hp.port);
|
|
2160
|
+
}
|
|
2161
|
+
function ruledTargets(repo, facts, questions, o = {}) {
|
|
2162
|
+
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 || b.answer.at.localeCompare(a.answer.at) || a.id.localeCompare(b.id));
|
|
2163
|
+
const out = /* @__PURE__ */ new Map();
|
|
2164
|
+
if (!rules.length)
|
|
2165
|
+
return out;
|
|
2166
|
+
for (const f of facts) {
|
|
2167
|
+
if (!ruleMayName(f, facts))
|
|
2168
|
+
continue;
|
|
2169
|
+
const q = rules.find((r) => ruleCoversCall(r.answer.match, f, facts));
|
|
2170
|
+
const label2 = q ? answerLabel2(q) : void 0;
|
|
2171
|
+
if (q && label2 && label2 !== TARGETS_SPLIT)
|
|
2172
|
+
out.set(f.id, { id: q.id, match: q.answer.match, label: label2 });
|
|
2173
|
+
}
|
|
1504
2174
|
return out;
|
|
1505
2175
|
}
|
|
1506
|
-
function targetsFromAnswers(repo, facts, questions) {
|
|
2176
|
+
function targetsFromAnswers(repo, facts, questions, o = {}) {
|
|
1507
2177
|
const byId = new Map(questions.map((q) => [q.id, q]));
|
|
2178
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2179
|
+
for (const q of [...questions].filter((x) => x.askedBy?.step === TARGETS_STEP && x.state === "answered" && !!x.answer && typeof x.question === "string").sort((a, b) => a.answer.at.localeCompare(b.answer.at) || a.id.localeCompare(b.id)))
|
|
2180
|
+
groups.set(q.question.trim(), q);
|
|
2181
|
+
const ruled = ruledTargets(repo, facts, questions, o);
|
|
1508
2182
|
const out = {};
|
|
1509
2183
|
for (const f of [...facts].sort((a, b) => a.id.localeCompare(b.id))) {
|
|
1510
2184
|
if (f.kind !== "call")
|
|
@@ -1512,18 +2186,104 @@ function targetsFromAnswers(repo, facts, questions) {
|
|
|
1512
2186
|
const q = targetQuestionFor(f);
|
|
1513
2187
|
if (!q)
|
|
1514
2188
|
continue;
|
|
1515
|
-
|
|
1516
|
-
if (!asked || asked.state !== "answered" || !asked.answer)
|
|
2189
|
+
let asked = byId.get(questionId({ step: TARGETS_STEP, repo, ...q }));
|
|
2190
|
+
if (!asked || asked.state !== "answered" || !asked.answer) {
|
|
2191
|
+
const g = targetGroupWordings(repo, f, facts).map((w) => groups.get(w)).find((x) => !!x);
|
|
2192
|
+
asked = g && (needsTarget(f, facts) || g.evidence.includes(q.evidence[0])) ? g : void 0;
|
|
2193
|
+
}
|
|
2194
|
+
let label2 = asked && asked.state === "answered" && asked.answer ? answerLabel2(asked) : void 0;
|
|
2195
|
+
let byAi = label2 && asked?.answer && isAiBy(asked.answer.by) ? asked.answer.by : void 0;
|
|
2196
|
+
if (!label2 || label2 === TARGETS_SPLIT || label2 === TARGETS_ANOTHER) {
|
|
2197
|
+
label2 = ruled.get(f.id)?.label;
|
|
2198
|
+
byAi = void 0;
|
|
2199
|
+
}
|
|
2200
|
+
if (!label2 || label2 === TARGETS_SPLIT || label2 === TARGETS_ANOTHER)
|
|
1517
2201
|
continue;
|
|
1518
|
-
|
|
1519
|
-
|
|
2202
|
+
if (byAi && o.ai)
|
|
2203
|
+
o.ai[f.id] = { runtime: byAi.runtime, run: byAi.run };
|
|
2204
|
+
if (label2 === TARGETS_NONE) {
|
|
2205
|
+
out[f.id] = NO_TARGET;
|
|
1520
2206
|
continue;
|
|
2207
|
+
}
|
|
1521
2208
|
const to = slugify(label2);
|
|
1522
2209
|
if (to)
|
|
1523
2210
|
out[f.id] = to;
|
|
1524
2211
|
}
|
|
1525
2212
|
return out;
|
|
1526
2213
|
}
|
|
2214
|
+
function answeredQuestionOfCall(repo, facts, questions, factId) {
|
|
2215
|
+
const f = facts.find((x) => x.id === factId);
|
|
2216
|
+
if (!f || f.kind !== "call")
|
|
2217
|
+
return void 0;
|
|
2218
|
+
const own = targetQuestionFor(f);
|
|
2219
|
+
const ownId = questionId({ step: TARGETS_STEP, repo, ...own });
|
|
2220
|
+
const answered = questions.filter((q2) => q2.askedBy?.step === TARGETS_STEP && q2.state === "answered" && q2.repo === repo);
|
|
2221
|
+
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]));
|
|
2222
|
+
return q?.answer?.match ? void 0 : q;
|
|
2223
|
+
}
|
|
2224
|
+
function answerReconfirmed(q) {
|
|
2225
|
+
if (!q.answer || !q.reopened)
|
|
2226
|
+
return false;
|
|
2227
|
+
const now2 = answerLabel2(q);
|
|
2228
|
+
const was = answerLabel2({ ...q, answer: q.reopened.answer });
|
|
2229
|
+
return !!now2 && !!was && slugify(now2) === slugify(was);
|
|
2230
|
+
}
|
|
2231
|
+
var JOINED_BY = {
|
|
2232
|
+
named: "the call names it",
|
|
2233
|
+
repo: "the call names its repo",
|
|
2234
|
+
host: "the manifest's hosts and base paths decide it",
|
|
2235
|
+
topic: "it listens on the call's topic"
|
|
2236
|
+
};
|
|
2237
|
+
function callsOfTargetQuestion(repo, facts, q) {
|
|
2238
|
+
return facts.filter((f) => {
|
|
2239
|
+
if (f.kind !== "call")
|
|
2240
|
+
return false;
|
|
2241
|
+
const own = targetQuestionFor(f);
|
|
2242
|
+
if (questionId({ step: TARGETS_STEP, repo, ...own }) === q.id)
|
|
2243
|
+
return true;
|
|
2244
|
+
return targetGroupWordings(repo, f, facts).includes(q.question.trim()) && q.evidence.includes(own.evidence[0]);
|
|
2245
|
+
});
|
|
2246
|
+
}
|
|
2247
|
+
function staleTargetQuestions(input) {
|
|
2248
|
+
const drafted = new Set(input.drafted.map((d) => d.id));
|
|
2249
|
+
const out = [];
|
|
2250
|
+
for (const q of input.open) {
|
|
2251
|
+
if (q.state !== "open" || q.askedBy.step !== TARGETS_STEP || drafted.has(q.id))
|
|
2252
|
+
continue;
|
|
2253
|
+
const calls = callsOfTargetQuestion(input.repo, input.facts, q);
|
|
2254
|
+
if (!calls.length)
|
|
2255
|
+
continue;
|
|
2256
|
+
const ids = new Set(calls.map((c) => c.id));
|
|
2257
|
+
const by = input.drafted.filter((d) => d.factIds.some((x) => ids.has(x))).map((d) => d.id);
|
|
2258
|
+
if (by.length) {
|
|
2259
|
+
out.push({ id: q.id, why: `its calls are asked now in ${by.join(", ")}`, replacedBy: by });
|
|
2260
|
+
continue;
|
|
2261
|
+
}
|
|
2262
|
+
if (calls.some((c) => input.asked.has(c.id)))
|
|
2263
|
+
continue;
|
|
2264
|
+
const reasons = calls.map((c) => {
|
|
2265
|
+
const rule = input.ruled?.get(c.id);
|
|
2266
|
+
if (rule)
|
|
2267
|
+
return `${callWhat(c)} is answered by rule in ${rule.id} (${rule.match})`;
|
|
2268
|
+
const j = input.join?.joins.find((x) => x.call.repo === input.repo && x.call.fact === c.id && x.how !== "answer");
|
|
2269
|
+
if (j && !isGuessedJoin(j) && JOINED_BY[j.how])
|
|
2270
|
+
return `${callWhat(c)} joins ${j.entry.component} in ${j.entry.repo}: ${JOINED_BY[j.how]}`;
|
|
2271
|
+
if (!j && c.to)
|
|
2272
|
+
return `${callWhat(c)} now names ${c.to} in the code`;
|
|
2273
|
+
return void 0;
|
|
2274
|
+
});
|
|
2275
|
+
const rules = [
|
|
2276
|
+
...new Set(calls.flatMap((c) => input.ruled?.get(c.id) ? [input.ruled.get(c.id).id] : []))
|
|
2277
|
+
].sort();
|
|
2278
|
+
if (reasons.every((r) => r !== void 0))
|
|
2279
|
+
out.push({
|
|
2280
|
+
id: q.id,
|
|
2281
|
+
why: reasons.join("; "),
|
|
2282
|
+
...rules.length ? { replacedBy: rules } : {}
|
|
2283
|
+
});
|
|
2284
|
+
}
|
|
2285
|
+
return out.sort((a, b) => a.id.localeCompare(b.id));
|
|
2286
|
+
}
|
|
1527
2287
|
function componentRenames(repos, op, taken = []) {
|
|
1528
2288
|
const manifests = estateRepos(op);
|
|
1529
2289
|
const users = /* @__PURE__ */ new Map();
|
|
@@ -1566,7 +2326,7 @@ function factsRecords(input) {
|
|
|
1566
2326
|
const facts = withComponents(repo, input.facts, op, input.renames).map((f) => {
|
|
1567
2327
|
let g = f;
|
|
1568
2328
|
const to = input.targets?.[g.id];
|
|
1569
|
-
if (g.kind === "call" && !g.to && to)
|
|
2329
|
+
if (g.kind === "call" && !g.to && to && to !== NO_TARGET)
|
|
1570
2330
|
g = { ...g, to };
|
|
1571
2331
|
return g;
|
|
1572
2332
|
});
|
|
@@ -1613,20 +2373,33 @@ function factsRecords(input) {
|
|
|
1613
2373
|
return { facts, projected, factById, newRecords, newFlows, loose, looseFact };
|
|
1614
2374
|
}
|
|
1615
2375
|
function outsideSystems(op) {
|
|
1616
|
-
return op
|
|
2376
|
+
return outsideProfileSystems(op).flatMap((s) => [s.id, s.name, ...s.aliases]).sort();
|
|
2377
|
+
}
|
|
2378
|
+
function outsideProfileSystems(op) {
|
|
2379
|
+
const own = new Set(estateRepos(op).flatMap((r) => r.system ? [r.system] : []));
|
|
2380
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2381
|
+
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
2382
|
}
|
|
2383
|
+
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
2384
|
function joinReposOf(op, list, renames) {
|
|
1619
2385
|
const manifests = estateRepos(op);
|
|
1620
2386
|
return list.map(({ repo, facts }) => {
|
|
1621
2387
|
const m = manifests.find((r) => r.id === repo);
|
|
1622
|
-
const modules = (m?.modules ?? []).filter((x) => x.hosts.length > 0);
|
|
2388
|
+
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
2389
|
const component = m?.component ? renames?.[repo]?.[factIdPart(m.component)] ?? m.component : void 0;
|
|
1624
2390
|
return {
|
|
1625
2391
|
repo,
|
|
1626
2392
|
facts: renames?.[repo] ? withComponents(repo, facts, op, renames) : facts,
|
|
1627
2393
|
...component ? { component } : {},
|
|
1628
2394
|
...m?.hosts.length ? { hosts: m.hosts } : {},
|
|
1629
|
-
...
|
|
2395
|
+
...m?.basePath ? { basePath: m.basePath } : {},
|
|
2396
|
+
...modules.length ? {
|
|
2397
|
+
modules: modules.map((x) => ({
|
|
2398
|
+
component: x.component,
|
|
2399
|
+
hosts: x.hosts,
|
|
2400
|
+
...x.basePath ? { basePath: x.basePath } : {}
|
|
2401
|
+
}))
|
|
2402
|
+
} : {},
|
|
1630
2403
|
// An API gateway's entries are routes the join follows through (ADR 0124 addendum A-gateway).
|
|
1631
2404
|
// A type that says which side it faces (`gateway-external`, `gateway-internal`) marks each
|
|
1632
2405
|
// hop through it (A-gateway2); a plain `gateway` leaves that to the proxy's virtual host.
|
|
@@ -1668,12 +2441,7 @@ function applyFactJoins(records, join10, repos, withCallers) {
|
|
|
1668
2441
|
} else if (!(r.kind === "FieldMapping" && ifaces.has(r.interface)))
|
|
1669
2442
|
kept2.push(r);
|
|
1670
2443
|
}
|
|
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
|
-
};
|
|
2444
|
+
const re = remapIds(was);
|
|
1677
2445
|
const joined = joinRecords(join10, repos);
|
|
1678
2446
|
const callers = withCallers ? callerRecords(withCallers.callers, repos, withCallers.join, {
|
|
1679
2447
|
...withCallers.only ? { only: withCallers.only } : {},
|
|
@@ -1694,20 +2462,28 @@ function applyFactJoins(records, join10, repos, withCallers) {
|
|
|
1694
2462
|
}
|
|
1695
2463
|
var entryText = (j) => j.method ? `${j.method} ${j.path}` : j.path;
|
|
1696
2464
|
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))
|
|
2465
|
+
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
|
|
2466
|
+
// written again with it, so a changed value moves the link.
|
|
2467
|
+
[...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).
|
|
2468
|
+
[...j.exposedBy ?? [], ...(j.gateway ?? []).flatMap((h) => h.exposedBy ?? [])].some((t) => t.repo === repo));
|
|
1698
2469
|
const ifaces = new Set(joins.map((j) => j.interface));
|
|
2470
|
+
const outside = (all.outside ?? []).filter((o) => o.call.repo === repo);
|
|
2471
|
+
const exposure = (all.exposure ?? []).filter((x) => x.exposure.repo === repo || "repo" in x && x.repo === repo);
|
|
1699
2472
|
return {
|
|
1700
2473
|
joins,
|
|
1701
2474
|
asks: all.asks.filter((a) => a.call.repo === repo),
|
|
1702
2475
|
misses: all.misses.filter((m) => m.call.repo === repo),
|
|
2476
|
+
...outside.length ? { outside } : {},
|
|
2477
|
+
...exposure.length ? { exposure } : {},
|
|
1703
2478
|
lineage: all.lineage.filter((l) => ifaces.has(l.interface)),
|
|
1704
2479
|
gaps: all.gaps.filter((g) => ifaces.has(g.interface))
|
|
1705
2480
|
};
|
|
1706
2481
|
}
|
|
1707
|
-
function catalogWithFacts(
|
|
1708
|
-
const { repo, op } =
|
|
2482
|
+
function catalogWithFacts(given) {
|
|
2483
|
+
const { repo, op } = given;
|
|
1709
2484
|
if (isPrototypeRepo(op, repo))
|
|
1710
|
-
return prototypeLeftOut(
|
|
2485
|
+
return prototypeLeftOut(given);
|
|
2486
|
+
const input = { ...given, existing: stripSharedMaps(stripLinkEvidence(given.existing)) };
|
|
1711
2487
|
const others = (input.estate ?? []).filter((x) => x.repo !== repo && !isPrototypeRepo(op, x.repo));
|
|
1712
2488
|
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
2489
|
const { facts, projected, newRecords, newFlows, looseFact } = factsRecords({
|
|
@@ -1757,20 +2533,46 @@ function catalogWithFacts(input) {
|
|
|
1757
2533
|
const remap = /* @__PURE__ */ new Map();
|
|
1758
2534
|
const stepTo = /* @__PURE__ */ new Map();
|
|
1759
2535
|
let join10;
|
|
2536
|
+
let answerMisses;
|
|
2537
|
+
let estateJoin;
|
|
2538
|
+
let terms;
|
|
1760
2539
|
let records0 = [...kept2, ...newRecords];
|
|
1761
2540
|
if (input.estate) {
|
|
1762
2541
|
const repos = joinReposOf(op, [{ repo, facts }, ...others], renames);
|
|
1763
2542
|
const whole = joinFacts({
|
|
1764
2543
|
repos,
|
|
1765
2544
|
targets: { ...input.estateTargets, [repo]: input.targets ?? {} },
|
|
1766
|
-
|
|
2545
|
+
pairs: { ...input.estatePairs, [repo]: input.pairs ?? {} },
|
|
2546
|
+
aiTargets: { ...input.estateAiTargets, [repo]: input.aiTargets ?? {} },
|
|
2547
|
+
outside: outsideSystems(op),
|
|
2548
|
+
...joinConventions(op),
|
|
2549
|
+
// G4: the walk through gateways is a stack helper (ATTICUS_HELPERS_OFF=gateway-walk).
|
|
2550
|
+
walkGateways: helperOn("gateway-walk")
|
|
1767
2551
|
});
|
|
1768
2552
|
join10 = joinAbout(whole, repo);
|
|
2553
|
+
estateJoin = { whole, repos };
|
|
2554
|
+
const named2 = { ...input.estateTargets, [repo]: input.targets ?? {} };
|
|
2555
|
+
const mine2 = /* @__PURE__ */ new Set([
|
|
2556
|
+
factIdPart(repo),
|
|
2557
|
+
...repos.filter((r) => r.repo === repo).flatMap((r) => [r.component, ...(r.modules ?? []).map((m) => m.component)]).filter((x) => !!x).map(factIdPart),
|
|
2558
|
+
...facts.flatMap((f) => f.component ? [factIdPart(f.component)] : [])
|
|
2559
|
+
]);
|
|
2560
|
+
answerMisses = whole.misses.filter((m) => {
|
|
2561
|
+
const t = named2[m.call.repo]?.[m.call.fact];
|
|
2562
|
+
return !!t && t !== NO_TARGET && (m.call.repo === repo || mine2.has(factIdPart(m.target)));
|
|
2563
|
+
});
|
|
1769
2564
|
records0 = applyFactJoins(records0, join10, repos, {
|
|
1770
|
-
callers: joinCallers({
|
|
2565
|
+
callers: joinCallers({
|
|
2566
|
+
repos,
|
|
2567
|
+
join: whole,
|
|
2568
|
+
declared: declaredCallsOf(op.profiles),
|
|
2569
|
+
...joinConventions(op)
|
|
2570
|
+
}),
|
|
1771
2571
|
join: whole,
|
|
1772
2572
|
only: [repo]
|
|
1773
2573
|
});
|
|
2574
|
+
terms = joinTerms({ repos, join: whole, bundleFolders: joinConventions(op).bundleFolders });
|
|
2575
|
+
records0 = [...records0, ...termGrantRecords(terms, repos, { only: [repo] })];
|
|
1774
2576
|
for (const j of join10.joins) {
|
|
1775
2577
|
const id = j.call.repo === repo ? projected.ids[j.call.fact] : void 0;
|
|
1776
2578
|
if (!id || remap.has(id))
|
|
@@ -1779,7 +2581,10 @@ function catalogWithFacts(input) {
|
|
|
1779
2581
|
stepTo.set(id, { component: j.entry.component, entry: entryText(j) });
|
|
1780
2582
|
}
|
|
1781
2583
|
}
|
|
1782
|
-
const merged0 = mergeEstate(op, estateRepos(op), records0.sort(byKindId)
|
|
2584
|
+
const merged0 = mergeEstate(op, estateRepos(op), records0.sort(byKindId), void 0, /* @__PURE__ */ new Set([
|
|
2585
|
+
repo,
|
|
2586
|
+
...input.estate ? others.filter((r) => r.facts.length > 0).map((r) => r.repo) : []
|
|
2587
|
+
]));
|
|
1783
2588
|
const heard = new Set((join10?.joins ?? []).filter((j) => j.how === "topic").map((j) => `${j.entry.repo}|${j.entry.at}|${j.entry.component}`));
|
|
1784
2589
|
const merged = {
|
|
1785
2590
|
...merged0,
|
|
@@ -1806,15 +2611,7 @@ function catalogWithFacts(input) {
|
|
|
1806
2611
|
joinedTo.set(id, iface.to);
|
|
1807
2612
|
}
|
|
1808
2613
|
}
|
|
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
|
-
};
|
|
2614
|
+
const re = remapIds(remap);
|
|
1818
2615
|
const records = merged.records.map((r) => {
|
|
1819
2616
|
if (r.kind === "Schema")
|
|
1820
2617
|
return { ...r, id: re(r.id), ref: re(r.ref) };
|
|
@@ -1824,7 +2621,25 @@ function catalogWithFacts(input) {
|
|
|
1824
2621
|
return { ...r, interface: re(r.interface) };
|
|
1825
2622
|
return r;
|
|
1826
2623
|
});
|
|
1827
|
-
const
|
|
2624
|
+
const curated = applyCurated([...records].sort(byKindId), input.decisions);
|
|
2625
|
+
const linkEvidence = estateJoin ? linkEvidenceOf({
|
|
2626
|
+
join: estateJoin.whole,
|
|
2627
|
+
repos: estateJoin.repos,
|
|
2628
|
+
contracts: input.linkEvidence?.contracts ?? [],
|
|
2629
|
+
traffic: input.linkEvidence?.traffic ?? []
|
|
2630
|
+
}) : void 0;
|
|
2631
|
+
const evidenced = linkEvidence ? { ...curated, records: applyLinkEvidence(curated.records, linkEvidence.marks) } : curated;
|
|
2632
|
+
const systems = outsideProfileSystems(op);
|
|
2633
|
+
const sharedWhole = estateJoin && input.shared?.length ? joinSharedMaps({
|
|
2634
|
+
outside: estateJoin.whole.outside ?? [],
|
|
2635
|
+
repos: estateJoin.repos,
|
|
2636
|
+
maps: input.shared,
|
|
2637
|
+
systems
|
|
2638
|
+
}) : void 0;
|
|
2639
|
+
const applied = {
|
|
2640
|
+
...evidenced,
|
|
2641
|
+
records: sharedWhole ? applySharedMaps(evidenced.records, sharedWhole, systems) : stripSharedMaps(evidenced.records)
|
|
2642
|
+
};
|
|
1828
2643
|
const newIds = new Set(applied.records.filter((r) => r.evidence.some(mine)).map((r) => r.id));
|
|
1829
2644
|
const oldIds = new Set([...dropped].map((k) => k.slice(k.indexOf("|") + 1)));
|
|
1830
2645
|
const flows = newFlows.map((f) => {
|
|
@@ -1865,7 +2680,11 @@ function catalogWithFacts(input) {
|
|
|
1865
2680
|
projectionIssues: projected.issues.filter((i) => i.code !== "unresolved-target"),
|
|
1866
2681
|
invalid: merged.invalid,
|
|
1867
2682
|
notes: merged.notes,
|
|
1868
|
-
...join10 ? { join: join10 } : {}
|
|
2683
|
+
...join10 ? { join: join10 } : {},
|
|
2684
|
+
...linkEvidence ? { linkEvidence } : {},
|
|
2685
|
+
...sharedWhole ? { shared: sharedJoinAbout(sharedWhole, repo) } : {},
|
|
2686
|
+
...terms ? { terms } : {},
|
|
2687
|
+
...answerMisses?.length ? { answerMisses } : {}
|
|
1869
2688
|
};
|
|
1870
2689
|
}
|
|
1871
2690
|
function prototypeLeftOut(input) {
|
|
@@ -1888,16 +2707,99 @@ function prototypeLeftOut(input) {
|
|
|
1888
2707
|
]
|
|
1889
2708
|
};
|
|
1890
2709
|
}
|
|
2710
|
+
function estateFromFacts(input) {
|
|
2711
|
+
const repos = input.repos.filter((x) => !isPrototypeRepo(input.op, x.repo));
|
|
2712
|
+
let records = [...input.existing ?? []];
|
|
2713
|
+
const specs = [];
|
|
2714
|
+
const invalid = [];
|
|
2715
|
+
for (const { repo, facts } of repos) {
|
|
2716
|
+
const out = catalogWithFacts({
|
|
2717
|
+
repo,
|
|
2718
|
+
facts,
|
|
2719
|
+
existing: records,
|
|
2720
|
+
op: input.op,
|
|
2721
|
+
decisions: input.decisions ?? [],
|
|
2722
|
+
estate: repos.filter((x) => x.repo !== repo)
|
|
2723
|
+
});
|
|
2724
|
+
records = out.records;
|
|
2725
|
+
specs.push({ repo, flows: out.flows });
|
|
2726
|
+
invalid.push(...out.invalid);
|
|
2727
|
+
}
|
|
2728
|
+
const renames = componentRenames(repos, input.op);
|
|
2729
|
+
const join10 = joinFacts({
|
|
2730
|
+
repos: joinReposOf(input.op, repos.map(({ repo, facts }) => ({
|
|
2731
|
+
repo,
|
|
2732
|
+
facts: factsRecords({ repo, facts, op: input.op, renames }).facts
|
|
2733
|
+
}))),
|
|
2734
|
+
outside: outsideSystems(input.op),
|
|
2735
|
+
...joinConventions(input.op),
|
|
2736
|
+
walkGateways: helperOn("gateway-walk")
|
|
2737
|
+
});
|
|
2738
|
+
return { records, specs, join: join10, invalid };
|
|
2739
|
+
}
|
|
2740
|
+
function answerRuleOffers(input) {
|
|
2741
|
+
const ruled = input.questions.filter((q) => q.askedBy?.step === TARGETS_STEP && q.state === "answered" && !!q.answer?.match);
|
|
2742
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2743
|
+
for (const r of input.repos)
|
|
2744
|
+
for (const f of r.facts) {
|
|
2745
|
+
if (f.kind !== "call")
|
|
2746
|
+
continue;
|
|
2747
|
+
const q = answeredQuestionOfCall(r.repo, r.facts, input.questions, f.id);
|
|
2748
|
+
const label2 = q ? answerLabel2(q) : void 0;
|
|
2749
|
+
if (!q || !label2 || label2 === TARGETS_NONE || label2 === TARGETS_SPLIT || label2 === TARGETS_ANOTHER)
|
|
2750
|
+
continue;
|
|
2751
|
+
const url = urlOf(f, r.facts);
|
|
2752
|
+
const host = url ? urlHostPort(url)?.host : void 0;
|
|
2753
|
+
const via = host ? `on ${host.toLowerCase()}` : f.base ? `through ${f.base}` : void 0;
|
|
2754
|
+
if (!via)
|
|
2755
|
+
continue;
|
|
2756
|
+
groups.set(via, [
|
|
2757
|
+
...groups.get(via) ?? [],
|
|
2758
|
+
{ repo: r.repo, q, label: label2, f, facts: r.facts, ...r.type ? { type: r.type } : {} }
|
|
2759
|
+
]);
|
|
2760
|
+
}
|
|
2761
|
+
const out = [];
|
|
2762
|
+
for (const [via, xs] of [...groups].sort(([a], [b]) => a.localeCompare(b))) {
|
|
2763
|
+
const repos = [...new Set(xs.map((x) => x.repo))].sort();
|
|
2764
|
+
const labels = new Set(xs.map((x) => slugify(x.label)));
|
|
2765
|
+
if (repos.length < 2 || labels.size !== 1)
|
|
2766
|
+
continue;
|
|
2767
|
+
if (xs.every((x) => ruled.some((q) => ruleCoversCall(q.answer.match, x.f, x.facts))))
|
|
2768
|
+
continue;
|
|
2769
|
+
const label2 = xs[0].label;
|
|
2770
|
+
const prefixes = new Set(xs.map((x) => targetPathPrefix(x.f.path)));
|
|
2771
|
+
const prefix = prefixes.size === 1 ? [...prefixes][0] : void 0;
|
|
2772
|
+
const match = via.startsWith("on ") ? via.slice(3) : prefix && prefix !== "/" ? `${prefix}/**` : void 0;
|
|
2773
|
+
const types = new Set(xs.map((x) => x.type));
|
|
2774
|
+
const type = types.size === 1 ? xs[0].type : void 0;
|
|
2775
|
+
const questions = [...new Set(xs.map((x) => x.q.id))].sort();
|
|
2776
|
+
const first = xs.find((x) => x.q.id === questions[0]).q;
|
|
2777
|
+
const n = first.options.findIndex((o) => o.label === label2);
|
|
2778
|
+
const pick = n >= 0 ? `--option ${n + 1}` : `--text "${label2}"`;
|
|
2779
|
+
if (!match || !type)
|
|
2780
|
+
continue;
|
|
2781
|
+
const command = `atticus questions answer ${first.id} ${pick} --match "${match}" --scope repo-type --type ${type}`;
|
|
2782
|
+
out.push({
|
|
2783
|
+
via,
|
|
2784
|
+
label: label2,
|
|
2785
|
+
repos,
|
|
2786
|
+
questions,
|
|
2787
|
+
match,
|
|
2788
|
+
type,
|
|
2789
|
+
command,
|
|
2790
|
+
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}.`
|
|
2791
|
+
});
|
|
2792
|
+
}
|
|
2793
|
+
return out;
|
|
2794
|
+
}
|
|
1891
2795
|
|
|
1892
2796
|
// packages/scanner/dist/facts-scan.js
|
|
1893
|
-
import { readFile as readFile4 } from "node:fs/promises";
|
|
1894
|
-
import { join as join6 } from "node:path";
|
|
1895
2797
|
async function factsForScan(root, op, repo, commit, readerRecords, at3 = {}) {
|
|
1896
2798
|
const rel = `${factsRelOf(root)}/${repo}.jsonl`;
|
|
1897
|
-
const
|
|
1898
|
-
if (
|
|
2799
|
+
const text2 = await readFile4(join6(factsDirOf(root), `${repo}.jsonl`), "utf8").catch(() => void 0);
|
|
2800
|
+
if (text2 === void 0 || text2.trim() === "")
|
|
1899
2801
|
return {};
|
|
1900
|
-
const parsed = parseFacts(
|
|
2802
|
+
const parsed = parseFacts(text2);
|
|
1901
2803
|
if (!parsed.ok)
|
|
1902
2804
|
return {
|
|
1903
2805
|
note: `${repo}: ${rel} doesn't read as facts (${parsed.issues[0]?.message ?? "invalid"}); its readers' records are used. Run atticus verify ${repo}.`
|
|
@@ -1910,29 +2812,45 @@ async function factsForScan(root, op, repo, commit, readerRecords, at3 = {}) {
|
|
|
1910
2812
|
};
|
|
1911
2813
|
const older = commit ? facts.filter((f) => !sameCommit(f.commit, commit)) : [];
|
|
1912
2814
|
let carriedSaid = "";
|
|
2815
|
+
let unread = [];
|
|
1913
2816
|
if (older.length) {
|
|
1914
2817
|
const diff = at3.dir ? await changesSince(at3.dir, older.map((f) => f.commit), commit, at3.git) : void 0;
|
|
1915
2818
|
if (!diff || diff.missing.length)
|
|
1916
2819
|
return {
|
|
1917
2820
|
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
2821
|
};
|
|
2822
|
+
const off = stackHelpersOff();
|
|
1919
2823
|
const report = await verifyFacts({
|
|
1920
2824
|
facts,
|
|
1921
2825
|
source: dirSource(at3.dir),
|
|
2826
|
+
...off.has("derived-names") ? { derivedNames: false } : {},
|
|
2827
|
+
...off.has("readers") ? { readers: false } : {},
|
|
1922
2828
|
commit,
|
|
1923
|
-
carry: diff.changes
|
|
2829
|
+
carry: diff.changes,
|
|
2830
|
+
conventions: conventionsOf(op),
|
|
2831
|
+
// ADR 0180: a type taken out of the organisation's set after a read is flagged here too.
|
|
2832
|
+
componentTypes: new Set(effectiveComponentTypes(op.componentTypes).map((t) => t.id))
|
|
1924
2833
|
});
|
|
1925
2834
|
const from = [...new Set(older.map((f) => f.commit.slice(0, 7)))].join(", ");
|
|
1926
2835
|
if (!report.accepted.length)
|
|
1927
2836
|
return {
|
|
1928
2837
|
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
2838
|
};
|
|
2839
|
+
unread = rereadPlan({
|
|
2840
|
+
facts,
|
|
2841
|
+
commit,
|
|
2842
|
+
carry: diff.changes,
|
|
2843
|
+
source: dirSource(at3.dir),
|
|
2844
|
+
conventions: conventionsOf(op)
|
|
2845
|
+
}).files.map((f) => f.path);
|
|
1930
2846
|
facts = report.accepted;
|
|
1931
2847
|
const left = report.rejected.length;
|
|
1932
2848
|
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
2849
|
}
|
|
1934
|
-
const questions = (await listQuestions(root, {
|
|
1935
|
-
const targets = targetsFromAnswers(repo, facts, questions
|
|
2850
|
+
const questions = (await listQuestions(root, { step: TARGETS_STEP }).catch(() => ({ questions: [] }))).questions;
|
|
2851
|
+
const targets = targetsFromAnswers(repo, facts, questions, {
|
|
2852
|
+
repoType: estateRepos(op).find((r) => r.id === repo)?.type
|
|
2853
|
+
});
|
|
1936
2854
|
const others = (await estateFacts(root, op, repo)).repos;
|
|
1937
2855
|
const renames = componentRenames([{ repo, facts }, ...others], op);
|
|
1938
2856
|
const { newRecords, facts: used } = factsRecords({ repo, facts, op, targets, renames });
|
|
@@ -1963,35 +2881,52 @@ async function factsForScan(root, op, repo, commit, readerRecords, at3 = {}) {
|
|
|
1963
2881
|
records: newRecords,
|
|
1964
2882
|
facts: used,
|
|
1965
2883
|
targets,
|
|
2884
|
+
basis: { from: "facts", unread },
|
|
1966
2885
|
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
2886
|
};
|
|
1968
2887
|
}
|
|
2888
|
+
async function pairsOfRepo(root, repo, facts) {
|
|
2889
|
+
const text2 = await readFile4(join6(factsDirOf(root), `${repo}.corrections.jsonl`), "utf8").catch(() => void 0);
|
|
2890
|
+
if (!text2 || !text2.includes('"link"'))
|
|
2891
|
+
return {};
|
|
2892
|
+
return pairsFromCorrections(facts, parseCorrections(text2).corrections);
|
|
2893
|
+
}
|
|
1969
2894
|
async function estateFacts(root, op, except) {
|
|
1970
2895
|
const repos = [];
|
|
1971
2896
|
const targets = {};
|
|
2897
|
+
const aiTargets = {};
|
|
2898
|
+
const pairs = {};
|
|
1972
2899
|
const notes = [];
|
|
2900
|
+
const unverified = [];
|
|
2901
|
+
const questions = (await listQuestions(root, { step: TARGETS_STEP }).catch(() => ({ questions: [] }))).questions;
|
|
1973
2902
|
for (const r of productionRepos(op)) {
|
|
1974
2903
|
if (r.id === except)
|
|
1975
2904
|
continue;
|
|
1976
|
-
const
|
|
1977
|
-
if (
|
|
2905
|
+
const text2 = await readFile4(join6(factsDirOf(root), `${r.id}.jsonl`), "utf8").catch(() => void 0);
|
|
2906
|
+
if (text2 === void 0 || text2.trim() === "")
|
|
1978
2907
|
continue;
|
|
1979
|
-
const parsed = parseFacts(
|
|
2908
|
+
const parsed = parseFacts(text2);
|
|
1980
2909
|
if (!parsed.ok) {
|
|
1981
2910
|
notes.push(`${r.id}: its facts don't read, so the estate join leaves them out.`);
|
|
1982
2911
|
continue;
|
|
1983
2912
|
}
|
|
1984
2913
|
if (parsed.facts.some((f) => f.verified === "none")) {
|
|
2914
|
+
unverified.push(r.id);
|
|
1985
2915
|
notes.push(`${r.id}: some of its facts aren't verified yet, so the estate join leaves them out (atticus verify ${r.id}).`);
|
|
1986
2916
|
continue;
|
|
1987
2917
|
}
|
|
1988
2918
|
repos.push({ repo: r.id, facts: parsed.facts });
|
|
1989
|
-
const
|
|
1990
|
-
const named2 = targetsFromAnswers(r.id, parsed.facts, questions);
|
|
2919
|
+
const ai = {};
|
|
2920
|
+
const named2 = targetsFromAnswers(r.id, parsed.facts, questions, { repoType: r.type, ai });
|
|
1991
2921
|
if (Object.keys(named2).length)
|
|
1992
2922
|
targets[r.id] = named2;
|
|
2923
|
+
if (Object.keys(ai).length)
|
|
2924
|
+
aiTargets[r.id] = ai;
|
|
2925
|
+
const paired = await pairsOfRepo(root, r.id, parsed.facts);
|
|
2926
|
+
if (Object.keys(paired).length)
|
|
2927
|
+
pairs[r.id] = paired;
|
|
1993
2928
|
}
|
|
1994
|
-
return { repos, targets, notes };
|
|
2929
|
+
return { repos, targets, pairs, notes, unverified, aiTargets };
|
|
1995
2930
|
}
|
|
1996
2931
|
|
|
1997
2932
|
// packages/scanner/dist/scan-settings.js
|
|
@@ -2006,10 +2941,10 @@ async function citedLine(repo, at3) {
|
|
|
2006
2941
|
const i = at3.lastIndexOf(":");
|
|
2007
2942
|
const path = at3.slice(0, i);
|
|
2008
2943
|
const n = Number(at3.slice(i + 1));
|
|
2009
|
-
const
|
|
2010
|
-
if (
|
|
2944
|
+
const text2 = await repo.readText(path).catch(() => void 0);
|
|
2945
|
+
if (text2 === void 0)
|
|
2011
2946
|
return void 0;
|
|
2012
|
-
return
|
|
2947
|
+
return text2.replace(/\r\n?/g, "\n").split("\n")[n - 1];
|
|
2013
2948
|
}
|
|
2014
2949
|
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
2950
|
var NOT_OWN = /(^|\/)(node_modules|\.git|target|bin|obj|dist|build|\.atticus|vendor)\//;
|
|
@@ -2068,8 +3003,8 @@ async function checkScanSettings(s, repo) {
|
|
|
2068
3003
|
if (!(isConfigFile(file) && names)) {
|
|
2069
3004
|
const dropped = code.filter((f) => re.test(f));
|
|
2070
3005
|
const root = mainRoots.find((d) => {
|
|
2071
|
-
const
|
|
2072
|
-
return
|
|
3006
|
+
const under2 = code.filter((f) => f.startsWith(`${d}/`));
|
|
3007
|
+
return under2.length && under2.every((f) => re.test(f));
|
|
2073
3008
|
});
|
|
2074
3009
|
const why = " so it is still read; only a build or config file that leaves it out itself can say so";
|
|
2075
3010
|
if (dropped.length * 2 > code.length) {
|
|
@@ -2126,9 +3061,9 @@ async function namesNotIn(repo, paths, names) {
|
|
|
2126
3061
|
for await (const f of repo.listFiles(["**/*"])) {
|
|
2127
3062
|
if (!paths.some((x) => x.test(f)))
|
|
2128
3063
|
continue;
|
|
2129
|
-
const
|
|
3064
|
+
const text2 = await repo.readText(f).catch(() => "");
|
|
2130
3065
|
for (const n of [...left])
|
|
2131
|
-
if (
|
|
3066
|
+
if (text2.includes(n))
|
|
2132
3067
|
left.delete(n);
|
|
2133
3068
|
if (left.size === 0)
|
|
2134
3069
|
break;
|
|
@@ -2355,54 +3290,6 @@ function withPluginScan(scan, extra) {
|
|
|
2355
3290
|
};
|
|
2356
3291
|
}
|
|
2357
3292
|
|
|
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
3293
|
// packages/scanner/dist/scan.js
|
|
2407
3294
|
var STRUCTURED = /\.(ya?ml|json|xml|tf|tfvars|properties|toml|conf|hcl)$/i;
|
|
2408
3295
|
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 +3305,6 @@ async function orgPackDirFor(root) {
|
|
|
2418
3305
|
return { dir: join9(root, ".atticus", "org-pack"), adopted: true };
|
|
2419
3306
|
return { dir: join9(root, "org-pack"), adopted: false };
|
|
2420
3307
|
}
|
|
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
3308
|
async function federate(local, sources, problems, root) {
|
|
2429
3309
|
const have = new Set(local.map((r) => `${r.kind}/${r.id}`));
|
|
2430
3310
|
const records = [];
|
|
@@ -2553,7 +3433,8 @@ async function scanEstate(o) {
|
|
|
2553
3433
|
}));
|
|
2554
3434
|
const brokenDialects = /* @__PURE__ */ new Set();
|
|
2555
3435
|
const dialects = await usableDialects(op, orgPackDir, problems, brokenDialects);
|
|
2556
|
-
const
|
|
3436
|
+
const off = stackHelpersOff();
|
|
3437
|
+
const frameworks = off.has("framework-models") ? [] : await usableFrameworks(op, problems);
|
|
2557
3438
|
const pluginSet = await usablePlugins(op);
|
|
2558
3439
|
problems.push(...pluginSet.problems);
|
|
2559
3440
|
const byName = new Map(dialects.map((d) => [d.name, d]));
|
|
@@ -2563,6 +3444,7 @@ async function scanEstate(o) {
|
|
|
2563
3444
|
const repos = productionRepos(op, o.repos);
|
|
2564
3445
|
const results = [];
|
|
2565
3446
|
const factNotes = prototypeScanNotes(op, o.repos);
|
|
3447
|
+
const basis = {};
|
|
2566
3448
|
const factRepos = [];
|
|
2567
3449
|
const structuredOf = /* @__PURE__ */ new Map();
|
|
2568
3450
|
const modulesOf = /* @__PURE__ */ new Map();
|
|
@@ -2570,7 +3452,7 @@ async function scanEstate(o) {
|
|
|
2570
3452
|
let done = 0;
|
|
2571
3453
|
for (const r of repos) {
|
|
2572
3454
|
throwIfCancelled();
|
|
2573
|
-
const dir = o.repoDir?.(r.id) ??
|
|
3455
|
+
const dir = o.repoDir?.(r.id) ?? inPlaceDir(o.root, r.url) ?? baseClonePath(o.root, r.id);
|
|
2574
3456
|
let state;
|
|
2575
3457
|
if (!o.repoState && !await stat(dir).catch(() => void 0)) {
|
|
2576
3458
|
problems.push({
|
|
@@ -2590,7 +3472,7 @@ async function scanEstate(o) {
|
|
|
2590
3472
|
});
|
|
2591
3473
|
continue;
|
|
2592
3474
|
}
|
|
2593
|
-
const extractors = [
|
|
3475
|
+
const extractors = off.has("readers") ? [] : [
|
|
2594
3476
|
...BUILT_IN_EXTRACTORS,
|
|
2595
3477
|
...o.extractors ?? [],
|
|
2596
3478
|
...dialectExtractors.filter(({ d }) => r.dialects.includes(d.name) || d.appliesTo.repoTypes.includes(r.type) || // "*": any repo where the marker matches (ADR 0062).
|
|
@@ -2670,6 +3552,7 @@ async function scanEstate(o) {
|
|
|
2670
3552
|
});
|
|
2671
3553
|
if (fromFacts.note)
|
|
2672
3554
|
factNotes.push(fromFacts.note);
|
|
3555
|
+
basis[r.id] = fromFacts.records && fromFacts.basis ? fromFacts.basis : { from: "readers" };
|
|
2673
3556
|
if (fromFacts.facts)
|
|
2674
3557
|
factRepos.push({ repo: r.id, facts: fromFacts.facts, targets: fromFacts.targets ?? {} });
|
|
2675
3558
|
results.push({ ...scan, records: fromFacts.records ?? readerRecords });
|
|
@@ -2682,10 +3565,17 @@ async function scanEstate(o) {
|
|
|
2682
3565
|
const join10 = joinFacts({
|
|
2683
3566
|
repos: joinRepos,
|
|
2684
3567
|
targets: Object.fromEntries(factRepos.map((x) => [x.repo, x.targets])),
|
|
2685
|
-
outside: outsideSystems(op)
|
|
3568
|
+
outside: outsideSystems(op),
|
|
3569
|
+
...joinConventions(op),
|
|
3570
|
+
walkGateways: helperOn("gateway-walk")
|
|
2686
3571
|
});
|
|
2687
3572
|
codeRecords = applyFactJoins(codeRecords, join10, joinRepos, {
|
|
2688
|
-
callers: joinCallers({
|
|
3573
|
+
callers: joinCallers({
|
|
3574
|
+
repos: joinRepos,
|
|
3575
|
+
join: join10,
|
|
3576
|
+
declared: declaredCallsOf(op.profiles),
|
|
3577
|
+
...joinConventions(op)
|
|
3578
|
+
}),
|
|
2689
3579
|
join: join10
|
|
2690
3580
|
});
|
|
2691
3581
|
}
|
|
@@ -2800,7 +3690,8 @@ async function scanEstate(o) {
|
|
|
2800
3690
|
...inputs ? { inputs } : {},
|
|
2801
3691
|
curated: { applied: applied.applied, suppressed: applied.suppressed },
|
|
2802
3692
|
notes,
|
|
2803
|
-
...federated ? { federated } : {}
|
|
3693
|
+
...federated ? { federated } : {},
|
|
3694
|
+
basis
|
|
2804
3695
|
};
|
|
2805
3696
|
}
|
|
2806
3697
|
|
|
@@ -2836,8 +3727,8 @@ var RepoFiles = class {
|
|
|
2836
3727
|
}
|
|
2837
3728
|
async linesOf(path) {
|
|
2838
3729
|
if (!this.lines.has(path)) {
|
|
2839
|
-
const
|
|
2840
|
-
this.lines.set(path,
|
|
3730
|
+
const text2 = await this.repo.readText(path).catch(() => void 0);
|
|
3731
|
+
this.lines.set(path, text2 === void 0 ? null : text2.replace(/\r\n?/g, "\n").split("\n"));
|
|
2841
3732
|
}
|
|
2842
3733
|
return this.lines.get(path);
|
|
2843
3734
|
}
|
|
@@ -2913,13 +3804,13 @@ async function scoreType(t, files) {
|
|
|
2913
3804
|
};
|
|
2914
3805
|
}
|
|
2915
3806
|
function typeFromWords(description, types) {
|
|
2916
|
-
const
|
|
2917
|
-
if (!
|
|
3807
|
+
const text2 = (description ?? "").toLowerCase();
|
|
3808
|
+
if (!text2)
|
|
2918
3809
|
return void 0;
|
|
2919
3810
|
const hits = [];
|
|
2920
3811
|
for (const t of types)
|
|
2921
3812
|
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(
|
|
3813
|
+
if (words2.length > 2 && new RegExp(`(^|[^a-z0-9])${escapeRe(words2)}([^a-z0-9]|$)`).test(text2))
|
|
2923
3814
|
hits.push({ type: t.id, words: words2 });
|
|
2924
3815
|
const ids = new Set(hits.map((h) => h.type));
|
|
2925
3816
|
if (ids.size !== 1)
|
|
@@ -2975,8 +3866,8 @@ async function readRule(rule, path, files) {
|
|
|
2975
3866
|
const key = rule.key;
|
|
2976
3867
|
if (/\.ya?ml$/i.test(path)) {
|
|
2977
3868
|
const counter = new import_yaml3.LineCounter();
|
|
2978
|
-
const
|
|
2979
|
-
for (const doc of (0, import_yaml3.parseAllDocuments)(
|
|
3869
|
+
const text2 = lines.join("\n");
|
|
3870
|
+
for (const doc of (0, import_yaml3.parseAllDocuments)(text2, { lineCounter: counter })) {
|
|
2980
3871
|
if (!("getIn" in doc))
|
|
2981
3872
|
continue;
|
|
2982
3873
|
for (const node of [doc.getIn(key.split("."), true), doc.get(key, true)]) {
|
|
@@ -3015,8 +3906,8 @@ async function resolveSettingsTemplate(t, repo, repoId) {
|
|
|
3015
3906
|
out.exclude.push({ path: e.path, why: e.why, at: `${first}:1` });
|
|
3016
3907
|
}
|
|
3017
3908
|
for (const rule of t.settings.prefixes) {
|
|
3018
|
-
const
|
|
3019
|
-
for (const f of
|
|
3909
|
+
const candidates2 = (await files.matching(rule.files)).sort((a, b) => a.length - b.length || a.localeCompare(b));
|
|
3910
|
+
for (const f of candidates2) {
|
|
3020
3911
|
const hit = await readRule(rule, f, files);
|
|
3021
3912
|
if (!hit)
|
|
3022
3913
|
continue;
|
|
@@ -3049,15 +3940,15 @@ var BUILD_FILES = [
|
|
|
3049
3940
|
["composer.json", "composer.json"]
|
|
3050
3941
|
].map(([name, glob]) => ({ name, glob, re: globToRegExp(`**/${glob}`) }));
|
|
3051
3942
|
var PACKAGE_NAME = /^[A-Za-z0-9_.@/-]+$/;
|
|
3052
|
-
function declaredDependencies(path,
|
|
3943
|
+
function declaredDependencies(path, text2) {
|
|
3053
3944
|
const out = [];
|
|
3054
3945
|
const all = (re, g = 1) => {
|
|
3055
|
-
for (const m of
|
|
3946
|
+
for (const m of text2.matchAll(re))
|
|
3056
3947
|
if (m[g])
|
|
3057
3948
|
out.push(m[g].trim());
|
|
3058
3949
|
};
|
|
3059
3950
|
if (path.endsWith("pom.xml")) {
|
|
3060
|
-
for (const block2 of
|
|
3951
|
+
for (const block2 of text2.matchAll(/<(parent|dependency|plugin)>([\s\S]*?)<\/\1>/g)) {
|
|
3061
3952
|
const a = /<artifactId>\s*([^<\s]+)\s*<\/artifactId>/.exec(block2[2]);
|
|
3062
3953
|
if (a)
|
|
3063
3954
|
out.push(a[1]);
|
|
@@ -3066,7 +3957,7 @@ function declaredDependencies(path, text) {
|
|
|
3066
3957
|
all(/['"]([\w.-]+):([\w.-]+)(?::[^'"]*)?['"]/g, 2);
|
|
3067
3958
|
} else if (path.endsWith("package.json")) {
|
|
3068
3959
|
try {
|
|
3069
|
-
const j = JSON.parse(
|
|
3960
|
+
const j = JSON.parse(text2);
|
|
3070
3961
|
const deps = Object.keys(j.dependencies ?? {});
|
|
3071
3962
|
out.push(...deps.length ? deps : Object.keys(j.devDependencies ?? {}));
|
|
3072
3963
|
} catch {
|
|
@@ -3176,9 +4067,9 @@ async function draftRepoType(repo, id) {
|
|
|
3176
4067
|
fingerprint.push({ kind: "build-file", globs: buildGlobs, weight: 2, required: true });
|
|
3177
4068
|
const deps = /* @__PURE__ */ new Map();
|
|
3178
4069
|
for (const f of builds) {
|
|
3179
|
-
const
|
|
4070
|
+
const text2 = (await files.linesOf(f) ?? []).join("\n");
|
|
3180
4071
|
const glob = buildGlobs.find((g) => globToRegExp(g).test(f));
|
|
3181
|
-
for (const d of declaredDependencies(f,
|
|
4072
|
+
for (const d of declaredDependencies(f, text2)) {
|
|
3182
4073
|
if (!PACKAGE_NAME.test(d) || d === repo.name)
|
|
3183
4074
|
continue;
|
|
3184
4075
|
if (!deps.has(d))
|
|
@@ -3740,7 +4631,7 @@ function parseEntry(s) {
|
|
|
3740
4631
|
const m = /^([A-Za-z]+)\s+(\S+)$/.exec(s.trim());
|
|
3741
4632
|
return m ? { method: m[1].toUpperCase(), path: m[2] } : { path: s.trim() };
|
|
3742
4633
|
}
|
|
3743
|
-
var
|
|
4634
|
+
var segs2 = (p) => normalizePath(p).toLowerCase().split("/").filter(Boolean);
|
|
3744
4635
|
var sameMethod = (a, b) => !a || !b || a.toUpperCase() === b.toUpperCase();
|
|
3745
4636
|
async function joinEstate(o) {
|
|
3746
4637
|
const byId = new Map(o.records.map((r) => [r.id, r]));
|
|
@@ -3752,8 +4643,8 @@ async function joinEstate(o) {
|
|
|
3752
4643
|
const out = [i.normalizedPath];
|
|
3753
4644
|
const route = i.route ? byId.get(i.route) : void 0;
|
|
3754
4645
|
if (route?.kind === "GatewayRoute") {
|
|
3755
|
-
const base2 =
|
|
3756
|
-
const p =
|
|
4646
|
+
const base2 = segs2(route.basepath);
|
|
4647
|
+
const p = segs2(i.normalizedPath);
|
|
3757
4648
|
if (base2.length && base2.every((s, k) => p[k] === s))
|
|
3758
4649
|
out.push(`/${p.slice(base2.length).join("/")}`);
|
|
3759
4650
|
}
|
|
@@ -3868,27 +4759,27 @@ async function joinEstate(o) {
|
|
|
3868
4759
|
within = "estate";
|
|
3869
4760
|
hits = servingExactly(flow.component, want);
|
|
3870
4761
|
}
|
|
3871
|
-
const
|
|
3872
|
-
if (
|
|
4762
|
+
const candidates2 = [...new Set(hits.map((e) => e.component))].sort();
|
|
4763
|
+
if (candidates2.length === 0) {
|
|
3873
4764
|
miss({
|
|
3874
4765
|
reason: "no-target",
|
|
3875
4766
|
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
4767
|
});
|
|
3877
4768
|
continue;
|
|
3878
4769
|
}
|
|
3879
|
-
if (
|
|
3880
|
-
const repos = [...new Set(
|
|
4770
|
+
if (candidates2.length > 1) {
|
|
4771
|
+
const repos = [...new Set(candidates2.map(repoOfComponent))];
|
|
3881
4772
|
const of = repos.length === 1 && repos[0] ? repos[0] : "the estate";
|
|
3882
4773
|
miss({
|
|
3883
4774
|
reason: "no-target",
|
|
3884
|
-
message: `${s.to.component} isn't a component in the catalog, and ${
|
|
4775
|
+
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
4776
|
});
|
|
3886
4777
|
asks.push({
|
|
3887
4778
|
from,
|
|
3888
4779
|
caller: flow.component,
|
|
3889
4780
|
named: s.to.component,
|
|
3890
4781
|
what: s.to.entry,
|
|
3891
|
-
candidates
|
|
4782
|
+
candidates: candidates2
|
|
3892
4783
|
});
|
|
3893
4784
|
continue;
|
|
3894
4785
|
}
|
|
@@ -4116,6 +5007,17 @@ function fieldLineage(f, field) {
|
|
|
4116
5007
|
}
|
|
4117
5008
|
|
|
4118
5009
|
// packages/scanner/dist/hosts-asked.js
|
|
5010
|
+
var segs3 = (p) => p.split(/[?#]/)[0].split("/").filter(Boolean).map((x) => /^[{:<$]/.test(x) ? "{}" : x);
|
|
5011
|
+
var underPath = (path, base2) => {
|
|
5012
|
+
const b = segs3(base2);
|
|
5013
|
+
const c = segs3(path);
|
|
5014
|
+
return b.length > 0 && b.length <= c.length && b.every((x, i) => x === c[i]);
|
|
5015
|
+
};
|
|
5016
|
+
var servesPath = (entry, path) => {
|
|
5017
|
+
const e = segs3(entry);
|
|
5018
|
+
const c = segs3(path);
|
|
5019
|
+
return e.length === c.length && e.every((x, i) => x === "{}" || c[i] === "{}" || x === c[i]);
|
|
5020
|
+
};
|
|
4119
5021
|
var HTTP_TYPES = /* @__PURE__ */ new Set(["rest", "graphql", "soap", "websocket", "sse", "webhook"]);
|
|
4120
5022
|
var CONFIG_FILES = [
|
|
4121
5023
|
"**/application*.properties",
|
|
@@ -4178,8 +5080,12 @@ async function unknownHosts(o) {
|
|
|
4178
5080
|
const entry = byHost.get(h.label) ?? {
|
|
4179
5081
|
...h.port !== void 0 ? { port: h.port } : {},
|
|
4180
5082
|
explicit: h.label !== h.host,
|
|
4181
|
-
calls: []
|
|
5083
|
+
calls: [],
|
|
5084
|
+
paths: /* @__PURE__ */ new Set()
|
|
4182
5085
|
};
|
|
5086
|
+
const path = /^[a-z][a-z0-9+.-]*:\/\/[^/?#]*(\/[^?#]*)?/i.exec(url)?.[1];
|
|
5087
|
+
if (path)
|
|
5088
|
+
entry.paths.add(path);
|
|
4183
5089
|
const at3 = `${e.path}:${e.line}`;
|
|
4184
5090
|
if (!entry.calls.some((c) => c.repo === repo && c.at === at3))
|
|
4185
5091
|
entry.calls.push({ repo, at: at3 });
|
|
@@ -4232,6 +5138,25 @@ async function unknownHosts(o) {
|
|
|
4232
5138
|
}
|
|
4233
5139
|
const suggest = hits.length === 1 ? hits[0] : void 0;
|
|
4234
5140
|
const calls = entry.calls.sort((a, b) => a.repo.localeCompare(b.repo) || a.at.localeCompare(b.at, "en", { numeric: true }));
|
|
5141
|
+
const byPath = [];
|
|
5142
|
+
for (const g of o.records) {
|
|
5143
|
+
if (g.kind !== "GatewayRoute")
|
|
5144
|
+
continue;
|
|
5145
|
+
const repo = g.evidence.find((e) => e.repo)?.repo;
|
|
5146
|
+
if (!repo || byPath.some((b) => b.repo === repo))
|
|
5147
|
+
continue;
|
|
5148
|
+
const under2 = [...entry.paths].filter((p) => underPath(p, g.basepath));
|
|
5149
|
+
if (!under2.length)
|
|
5150
|
+
continue;
|
|
5151
|
+
const comps = new Set([...compRepo].filter(([, r]) => r === repo).map(([c]) => c));
|
|
5152
|
+
const hit = o.records.find((r) => r.kind === "Endpoint" && comps.has(r.component) && under2.some((p) => servesPath(r.pathOrTopic, p)));
|
|
5153
|
+
byPath.push({
|
|
5154
|
+
repo,
|
|
5155
|
+
serves: hit ? [hit.method?.toUpperCase(), hit.pathOrTopic].filter(Boolean).join(" ") : `the routes under ${g.basepath}`
|
|
5156
|
+
});
|
|
5157
|
+
}
|
|
5158
|
+
byPath.sort((a, b) => a.repo.localeCompare(b.repo));
|
|
5159
|
+
const first = suggest ? [suggest.repo] : byPath.map((b) => b.repo).filter((x) => servers.includes(x));
|
|
4235
5160
|
const about = Object.fromEntries(servers.flatMap((s) => {
|
|
4236
5161
|
const d = o.repos.find((r) => r.id === s)?.description;
|
|
4237
5162
|
return d ? [[s, { text: d.text, trust: d.trust }]] : [];
|
|
@@ -4239,9 +5164,10 @@ async function unknownHosts(o) {
|
|
|
4239
5164
|
asks.push({
|
|
4240
5165
|
host,
|
|
4241
5166
|
calls,
|
|
4242
|
-
servers:
|
|
5167
|
+
servers: [...first, ...servers.filter((s) => !first.includes(s))],
|
|
4243
5168
|
...suggest ? { suggest } : {},
|
|
4244
|
-
...Object.keys(about).length ? { about } : {}
|
|
5169
|
+
...Object.keys(about).length ? { about } : {},
|
|
5170
|
+
...byPath.length ? { byPath } : {}
|
|
4245
5171
|
});
|
|
4246
5172
|
}
|
|
4247
5173
|
return asks;
|
|
@@ -4252,23 +5178,38 @@ function aboutOf(a, repo) {
|
|
|
4252
5178
|
return "";
|
|
4253
5179
|
return d.trust === "suggested" ? `. What it is, as your assistant drafted it (unconfirmed): ${d.text}` : `. What it is: ${d.text}`;
|
|
4254
5180
|
}
|
|
5181
|
+
function gatewayHostFix(host, byPath) {
|
|
5182
|
+
const repos = byPath.map((b) => b.repo);
|
|
5183
|
+
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`;
|
|
5184
|
+
}
|
|
4255
5185
|
function hostQuestionDrafts(asks) {
|
|
4256
5186
|
return asks.map((a) => {
|
|
4257
5187
|
const repo = a.calls[0].repo;
|
|
5188
|
+
const byPath = new Map((a.byPath ?? []).map((b) => [b.repo, b.serves]));
|
|
4258
5189
|
return {
|
|
4259
5190
|
repo,
|
|
5191
|
+
...a.byPath?.length ? { byPath: a.byPath } : {},
|
|
4260
5192
|
draft: {
|
|
4261
5193
|
question: hostQuestionText(a.host),
|
|
4262
5194
|
options: [
|
|
4263
5195
|
...a.servers.map((s) => ({
|
|
4264
5196
|
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`
|
|
5197
|
+
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),
|
|
5198
|
+
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`
|
|
5199
|
+
})),
|
|
5200
|
+
// O1: a system outside the estate, declared in the profile with the host as its alias.
|
|
5201
|
+
...[
|
|
5202
|
+
[HOSTS_OUTSIDE_INTERNAL, "internal", "another team's system"],
|
|
5203
|
+
[HOSTS_OUTSIDE_VENDOR, "vendor", "a vendor's system"]
|
|
5204
|
+
].map(([label2, boundary, what2]) => ({
|
|
5205
|
+
label: label2,
|
|
5206
|
+
means: `${a.host} is ${what2}, outside this estate, that the org pack doesn't name yet`,
|
|
5207
|
+
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
5208
|
})),
|
|
4268
5209
|
{
|
|
4269
5210
|
label: HOSTS_NONE,
|
|
4270
|
-
means: `
|
|
4271
|
-
changes: "Nothing is added to the manifest; the calls stay loose ends, and this isn't asked again"
|
|
5211
|
+
means: `Neither a repo of this estate nor a system to add to the org pack answers on ${a.host}`,
|
|
5212
|
+
changes: "Nothing is added to the manifest or the profile; the calls stay loose ends, and this isn't asked again"
|
|
4272
5213
|
}
|
|
4273
5214
|
],
|
|
4274
5215
|
evidence: a.calls.filter((c) => c.repo === repo).slice(0, 3).map((c) => c.at),
|
|
@@ -4278,6 +5219,375 @@ function hostQuestionDrafts(asks) {
|
|
|
4278
5219
|
});
|
|
4279
5220
|
}
|
|
4280
5221
|
|
|
5222
|
+
// packages/scanner/dist/measure-links.js
|
|
5223
|
+
var import_yaml4 = __toESM(require_dist(), 1);
|
|
5224
|
+
var METHODS = /^(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS|TRACE|ANY)\s+(\S.*)$/i;
|
|
5225
|
+
var NEAR = 3;
|
|
5226
|
+
var AT = /^.+:\d+$/;
|
|
5227
|
+
var AT_HELP = "at is file:line, e.g. src/main/java/Client.java:16";
|
|
5228
|
+
var isObj2 = (x) => typeof x === "object" && x !== null && !Array.isArray(x);
|
|
5229
|
+
var text = (x) => typeof x === "string" && x.trim() ? x.trim() : typeof x === "number" ? String(x) : void 0;
|
|
5230
|
+
function parseLinkReference(source) {
|
|
5231
|
+
let raw;
|
|
5232
|
+
try {
|
|
5233
|
+
raw = (0, import_yaml4.parse)(source);
|
|
5234
|
+
} catch (e) {
|
|
5235
|
+
return { ok: false, issues: [`the file isn't YAML: ${e.message}`] };
|
|
5236
|
+
}
|
|
5237
|
+
const issues = [];
|
|
5238
|
+
const say = (where, message) => issues.push(`${where}: ${message}`);
|
|
5239
|
+
const only = (where, o, keys) => {
|
|
5240
|
+
for (const k of Object.keys(o))
|
|
5241
|
+
if (!keys.includes(k))
|
|
5242
|
+
say(where, `unknown key ${k}`);
|
|
5243
|
+
};
|
|
5244
|
+
if (!isObj2(raw) || !Array.isArray(raw.repos))
|
|
5245
|
+
return { ok: false, issues: ["repos: list the repos, each with its entries and calls"] };
|
|
5246
|
+
only("(top)", raw, ["estate", "repos"]);
|
|
5247
|
+
const repos = [];
|
|
5248
|
+
raw.repos.forEach((r, i) => {
|
|
5249
|
+
const w = `repos.${i}`;
|
|
5250
|
+
if (!isObj2(r) || !text(r.repo))
|
|
5251
|
+
return say(w, "each repo needs repo: <its id>");
|
|
5252
|
+
only(w, r, ["repo", "entries", "calls"]);
|
|
5253
|
+
const entries = [];
|
|
5254
|
+
const calls = [];
|
|
5255
|
+
const list = (x, name) => {
|
|
5256
|
+
if (x === void 0 || x === null)
|
|
5257
|
+
return [];
|
|
5258
|
+
if (Array.isArray(x))
|
|
5259
|
+
return x;
|
|
5260
|
+
say(`${w}.${name}`, `${name} is a list`);
|
|
5261
|
+
return [];
|
|
5262
|
+
};
|
|
5263
|
+
list(r.entries, "entries").forEach((e, j) => {
|
|
5264
|
+
const we = `${w}.entries.${j}`;
|
|
5265
|
+
if (text(e))
|
|
5266
|
+
return entries.push({ entry: text(e) });
|
|
5267
|
+
if (!isObj2(e))
|
|
5268
|
+
return say(we, "an entry is METHOD /path, or { entry, at, fact }");
|
|
5269
|
+
only(we, e, ["entry", "at", "fact"]);
|
|
5270
|
+
const at3 = text(e.at);
|
|
5271
|
+
if (at3 && !AT.test(at3))
|
|
5272
|
+
return say(`${we}.at`, AT_HELP);
|
|
5273
|
+
if (!text(e.entry) && !at3 && !text(e.fact))
|
|
5274
|
+
return say(we, "give the entry (METHOD /path), its line (at: file:line) or its fact id");
|
|
5275
|
+
entries.push(stripped({ entry: text(e.entry), at: at3, fact: text(e.fact) }));
|
|
5276
|
+
});
|
|
5277
|
+
list(r.calls, "calls").forEach((c, j) => {
|
|
5278
|
+
const wc = `${w}.calls.${j}`;
|
|
5279
|
+
if (!isObj2(c))
|
|
5280
|
+
return say(wc, "a call is { call, at, reaches } or { call, at, outside }");
|
|
5281
|
+
only(wc, c, ["call", "at", "fact", "reaches", "outside", "note"]);
|
|
5282
|
+
const reaches = text(c.reaches);
|
|
5283
|
+
const outside = text(c.outside);
|
|
5284
|
+
const at3 = text(c.at);
|
|
5285
|
+
let bad = false;
|
|
5286
|
+
if (reaches && outside) {
|
|
5287
|
+
say(wc, "give one of reaches (a repo, or none) and outside (a system), not both");
|
|
5288
|
+
bad = true;
|
|
5289
|
+
} else if (!reaches && !outside) {
|
|
5290
|
+
say(wc, "give one of reaches (a repo, or none) and outside (a system)");
|
|
5291
|
+
bad = true;
|
|
5292
|
+
}
|
|
5293
|
+
if (!text(c.call) && !at3 && !text(c.fact)) {
|
|
5294
|
+
say(wc, "give the call (METHOD /path), its line (at: file:line) or its fact id");
|
|
5295
|
+
bad = true;
|
|
5296
|
+
}
|
|
5297
|
+
if (at3 && !AT.test(at3)) {
|
|
5298
|
+
say(`${wc}.at`, AT_HELP);
|
|
5299
|
+
bad = true;
|
|
5300
|
+
}
|
|
5301
|
+
if (bad)
|
|
5302
|
+
return;
|
|
5303
|
+
const want = outside ? { outside } : reaches.toLowerCase() === "none" ? { none: true } : { repo: reaches };
|
|
5304
|
+
calls.push({
|
|
5305
|
+
...stripped({ call: text(c.call), at: at3, fact: text(c.fact) }),
|
|
5306
|
+
want
|
|
5307
|
+
});
|
|
5308
|
+
});
|
|
5309
|
+
repos.push({ repo: text(r.repo), entries, calls });
|
|
5310
|
+
});
|
|
5311
|
+
if (issues.length)
|
|
5312
|
+
return { ok: false, issues };
|
|
5313
|
+
const estate = text(raw.estate);
|
|
5314
|
+
return { ok: true, reference: { ...estate ? { estate } : {}, repos } };
|
|
5315
|
+
}
|
|
5316
|
+
var stripped = (o) => Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0));
|
|
5317
|
+
function segs4(path) {
|
|
5318
|
+
return path.split(/[?#]/)[0].split("/").filter(Boolean).map((s) => /^(\{[^}]*\}|:\w+|\*)$/.test(s) ? "{}" : s.toLowerCase());
|
|
5319
|
+
}
|
|
5320
|
+
function pathMatch(a, b) {
|
|
5321
|
+
const x = segs4(a);
|
|
5322
|
+
const y = segs4(b);
|
|
5323
|
+
if (x.length === y.length && x.every((s, i) => s === y[i]))
|
|
5324
|
+
return 2;
|
|
5325
|
+
const [short, long] = x.length < y.length ? [x, y] : [y, x];
|
|
5326
|
+
if (!short.length)
|
|
5327
|
+
return 0;
|
|
5328
|
+
const off = long.length - short.length;
|
|
5329
|
+
return short.every((s, i) => s === long[off + i]) ? 1 : 0;
|
|
5330
|
+
}
|
|
5331
|
+
function parseWhat(what2) {
|
|
5332
|
+
if (!what2)
|
|
5333
|
+
return {};
|
|
5334
|
+
const m = METHODS.exec(what2.trim());
|
|
5335
|
+
return m ? { method: m[1].toUpperCase(), path: m[2].trim() } : { path: what2.trim() };
|
|
5336
|
+
}
|
|
5337
|
+
function parseAt(at3) {
|
|
5338
|
+
if (!at3)
|
|
5339
|
+
return void 0;
|
|
5340
|
+
const i = at3.lastIndexOf(":");
|
|
5341
|
+
return { file: at3.slice(0, i).replace(/^\.\//, ""), line: Number(at3.slice(i + 1)) };
|
|
5342
|
+
}
|
|
5343
|
+
function finder(facts) {
|
|
5344
|
+
const used = /* @__PURE__ */ new Set();
|
|
5345
|
+
return (ref) => {
|
|
5346
|
+
const { method, path } = parseWhat(ref.what);
|
|
5347
|
+
const at3 = parseAt(ref.at);
|
|
5348
|
+
let best;
|
|
5349
|
+
for (const f of facts) {
|
|
5350
|
+
if (used.has(f.id))
|
|
5351
|
+
continue;
|
|
5352
|
+
if (ref.fact) {
|
|
5353
|
+
if (f.id === ref.fact)
|
|
5354
|
+
best = { f, score: 1e6 };
|
|
5355
|
+
continue;
|
|
5356
|
+
}
|
|
5357
|
+
let score = 0;
|
|
5358
|
+
if (at3) {
|
|
5359
|
+
const d = Math.abs(f.cite.line - at3.line);
|
|
5360
|
+
if (f.cite.file !== at3.file || d > NEAR)
|
|
5361
|
+
continue;
|
|
5362
|
+
score += 10 * (NEAR + 1 - d);
|
|
5363
|
+
}
|
|
5364
|
+
if (method && f.method && f.method.toUpperCase() !== method && method !== "ANY")
|
|
5365
|
+
continue;
|
|
5366
|
+
if (path) {
|
|
5367
|
+
const p = pathMatch(path, f.path);
|
|
5368
|
+
if (!p)
|
|
5369
|
+
continue;
|
|
5370
|
+
score += 100 * p;
|
|
5371
|
+
}
|
|
5372
|
+
if (!best || score > best.score)
|
|
5373
|
+
best = { f, score };
|
|
5374
|
+
}
|
|
5375
|
+
if (best)
|
|
5376
|
+
used.add(best.f.id);
|
|
5377
|
+
return best?.f;
|
|
5378
|
+
};
|
|
5379
|
+
}
|
|
5380
|
+
var round = (n, d) => d ? Math.round(n / d * 1e3) / 1e3 : null;
|
|
5381
|
+
var what = (f) => [f.method?.toUpperCase(), f.path].filter(Boolean).join(" ");
|
|
5382
|
+
function measureLinks(input) {
|
|
5383
|
+
const { op } = input;
|
|
5384
|
+
const repos = input.repos.filter((x) => !isPrototypeRepo(op, x.repo));
|
|
5385
|
+
const renames = componentRenames(repos, op);
|
|
5386
|
+
const join10 = joinFacts({
|
|
5387
|
+
repos: joinReposOf(op, repos, renames),
|
|
5388
|
+
...input.targets ? { targets: input.targets } : {},
|
|
5389
|
+
outside: outsideSystems(op),
|
|
5390
|
+
...joinConventions(op),
|
|
5391
|
+
walkGateways: input.walkGateways ?? helperOn("gateway-walk")
|
|
5392
|
+
});
|
|
5393
|
+
const key = (repo, fact) => `${repo}\0${fact}`;
|
|
5394
|
+
const joins = /* @__PURE__ */ new Map();
|
|
5395
|
+
for (const j of join10.joins) {
|
|
5396
|
+
const k = key(j.call.repo, j.call.fact);
|
|
5397
|
+
joins.set(k, [...joins.get(k) ?? [], j]);
|
|
5398
|
+
}
|
|
5399
|
+
const asks = new Map(join10.asks.map((a) => [key(a.call.repo, a.call.fact), a]));
|
|
5400
|
+
const misses = new Map(join10.misses.map((m) => [key(m.call.repo, m.call.fact), m]));
|
|
5401
|
+
const outsideOf = new Map((join10.outside ?? []).map((o) => [key(o.call.repo, o.call.fact), o]));
|
|
5402
|
+
const own = new Set(estateRepos(op).flatMap((r) => r.system ? [r.system] : []));
|
|
5403
|
+
const systemOf = /* @__PURE__ */ new Map();
|
|
5404
|
+
for (const s of op.profiles.flatMap((p) => p.profile?.systems ?? []))
|
|
5405
|
+
if (s.boundary === "vendor" || !own.has(s.id))
|
|
5406
|
+
for (const n of [s.id, s.name, ...s.aliases])
|
|
5407
|
+
systemOf.set(factIdPart(n), s.id);
|
|
5408
|
+
const outsideName = (n) => n ? systemOf.get(factIdPart(n)) : void 0;
|
|
5409
|
+
const manifests = estateRepos(op);
|
|
5410
|
+
const repoOf = (name) => {
|
|
5411
|
+
const slug = factIdPart(name);
|
|
5412
|
+
const m = manifests.find((r) => r.id === name || factIdPart(r.id) === slug) ?? manifests.find((r) => r.component && factIdPart(r.component) === slug);
|
|
5413
|
+
return m?.id ?? name;
|
|
5414
|
+
};
|
|
5415
|
+
const factsOf = new Map(repos.map((r) => [r.repo, r.facts]));
|
|
5416
|
+
const notes = [];
|
|
5417
|
+
const reading = {
|
|
5418
|
+
entries: { want: 0, found: 0 },
|
|
5419
|
+
calls: { want: 0, found: 0 },
|
|
5420
|
+
missed: []
|
|
5421
|
+
};
|
|
5422
|
+
const rows = [];
|
|
5423
|
+
for (const r of input.reference.repos) {
|
|
5424
|
+
const facts = factsOf.get(r.repo);
|
|
5425
|
+
if (!facts)
|
|
5426
|
+
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}).`);
|
|
5427
|
+
const all = facts ?? [];
|
|
5428
|
+
const findEntry = finder(all.filter((f) => f.kind === "entry"));
|
|
5429
|
+
for (const e of r.entries) {
|
|
5430
|
+
reading.entries.want++;
|
|
5431
|
+
if (findEntry({ what: e.entry, at: e.at, fact: e.fact }))
|
|
5432
|
+
reading.entries.found++;
|
|
5433
|
+
else
|
|
5434
|
+
reading.missed.push(`${r.repo} entry ${e.entry ?? e.fact ?? e.at}`);
|
|
5435
|
+
}
|
|
5436
|
+
const findCall = finder(all.filter((f) => f.kind === "call"));
|
|
5437
|
+
for (const c of r.calls) {
|
|
5438
|
+
reading.calls.want++;
|
|
5439
|
+
const f = findCall({ what: c.call, at: c.at, fact: c.fact });
|
|
5440
|
+
const label2 = c.call ?? (f ? what(f) : c.fact ?? c.at);
|
|
5441
|
+
const base2 = { repo: r.repo, call: label2, ...c.at ? { at: c.at } : {} };
|
|
5442
|
+
if (!f) {
|
|
5443
|
+
reading.missed.push(`${r.repo} call ${label2}`);
|
|
5444
|
+
rows.push({ ...base2, want: c.want, got: { kind: "not-read" }, right: false });
|
|
5445
|
+
continue;
|
|
5446
|
+
}
|
|
5447
|
+
reading.calls.found++;
|
|
5448
|
+
const got = gotOf(r.repo, f, all);
|
|
5449
|
+
rows.push({ ...base2, fact: f.id, want: c.want, got, right: isRight(c.want, got) });
|
|
5450
|
+
}
|
|
5451
|
+
}
|
|
5452
|
+
function gotOf(repo, f, facts) {
|
|
5453
|
+
const k = key(repo, f.id);
|
|
5454
|
+
const j = joins.get(k)?.[0];
|
|
5455
|
+
if (j) {
|
|
5456
|
+
const via = (j.gateway ?? []).map((h) => h.entry.repo);
|
|
5457
|
+
const inferred2 = joinGuessesOf(j)[0];
|
|
5458
|
+
return {
|
|
5459
|
+
kind: "joined",
|
|
5460
|
+
repo: j.entry.repo,
|
|
5461
|
+
component: j.entry.component,
|
|
5462
|
+
entry: [j.method, j.path].filter(Boolean).join(" "),
|
|
5463
|
+
...via.length ? { via } : {},
|
|
5464
|
+
...inferred2 ? { inferred: inferred2 } : {}
|
|
5465
|
+
};
|
|
5466
|
+
}
|
|
5467
|
+
const ask = asks.get(k);
|
|
5468
|
+
if (ask)
|
|
5469
|
+
return { kind: "asked", reason: ask.reason };
|
|
5470
|
+
const miss = misses.get(k);
|
|
5471
|
+
if (miss)
|
|
5472
|
+
return { kind: "missed", target: miss.target };
|
|
5473
|
+
const answer = input.targets?.[repo]?.[f.id];
|
|
5474
|
+
if (answer === NO_TARGET)
|
|
5475
|
+
return { kind: "unlinked" };
|
|
5476
|
+
const host = callHostOf(f, facts);
|
|
5477
|
+
const system = outsideName(answer) ?? outsideName(outsideOf.get(k)?.target) ?? outsideName(f.to) ?? outsideName(host);
|
|
5478
|
+
if (system)
|
|
5479
|
+
return { kind: "outside", system };
|
|
5480
|
+
if (f.to && host)
|
|
5481
|
+
return { kind: "outside", system: f.to };
|
|
5482
|
+
return { kind: "unlinked" };
|
|
5483
|
+
}
|
|
5484
|
+
function isRight(want, got) {
|
|
5485
|
+
if ("repo" in want) {
|
|
5486
|
+
if (got.kind !== "joined")
|
|
5487
|
+
return false;
|
|
5488
|
+
const w = repoOf(want.repo);
|
|
5489
|
+
return got.repo === w || got.component === factIdPart(want.repo);
|
|
5490
|
+
}
|
|
5491
|
+
if ("outside" in want) {
|
|
5492
|
+
if (got.kind !== "outside")
|
|
5493
|
+
return false;
|
|
5494
|
+
const w = outsideName(want.outside) ?? factIdPart(want.outside);
|
|
5495
|
+
return got.system === w || factIdPart(got.system) === factIdPart(want.outside);
|
|
5496
|
+
}
|
|
5497
|
+
return got.kind === "unlinked" || got.kind === "missed";
|
|
5498
|
+
}
|
|
5499
|
+
const toRepo = rows.filter((x) => "repo" in x.want);
|
|
5500
|
+
const joined = rows.filter((x) => x.got.kind === "joined");
|
|
5501
|
+
const right = joined.filter((x) => x.right).length;
|
|
5502
|
+
const inferred = joined.filter((x) => x.got.kind === "joined" && x.got.inferred);
|
|
5503
|
+
const outsideWant = rows.filter((x) => "outside" in x.want);
|
|
5504
|
+
const noneWant = rows.filter((x) => "none" in x.want);
|
|
5505
|
+
return {
|
|
5506
|
+
...input.reference.estate ? { estate: input.reference.estate } : {},
|
|
5507
|
+
reading,
|
|
5508
|
+
links: {
|
|
5509
|
+
want: toRepo.length,
|
|
5510
|
+
joined: joined.length,
|
|
5511
|
+
right,
|
|
5512
|
+
precision: round(right, joined.length),
|
|
5513
|
+
recall: round(toRepo.filter((x) => x.right).length, toRepo.length),
|
|
5514
|
+
asked: rows.filter((x) => x.got.kind === "asked").length,
|
|
5515
|
+
missed: rows.filter((x) => x.got.kind === "missed").length,
|
|
5516
|
+
unlinked: rows.filter((x) => x.got.kind === "unlinked").length,
|
|
5517
|
+
notRead: rows.filter((x) => x.got.kind === "not-read").length,
|
|
5518
|
+
inferred: { joined: inferred.length, right: inferred.filter((x) => x.right).length },
|
|
5519
|
+
outside: {
|
|
5520
|
+
want: outsideWant.length,
|
|
5521
|
+
got: rows.filter((x) => x.got.kind === "outside").length,
|
|
5522
|
+
right: outsideWant.filter((x) => x.right).length
|
|
5523
|
+
},
|
|
5524
|
+
none: { want: noneWant.length, right: noneWant.filter((x) => x.right).length }
|
|
5525
|
+
},
|
|
5526
|
+
calls: rows,
|
|
5527
|
+
notes
|
|
5528
|
+
};
|
|
5529
|
+
}
|
|
5530
|
+
|
|
5531
|
+
// packages/scanner/dist/audit-links.js
|
|
5532
|
+
function estateJoinOf(op, list, targets, pairs, aiTargets) {
|
|
5533
|
+
const repos = list.filter((x) => !isPrototypeRepo(op, x.repo));
|
|
5534
|
+
const renames = componentRenames(repos, op);
|
|
5535
|
+
return joinFacts({
|
|
5536
|
+
repos: joinReposOf(op, repos, renames),
|
|
5537
|
+
...targets ? { targets } : {},
|
|
5538
|
+
...aiTargets && Object.keys(aiTargets).length ? { aiTargets } : {},
|
|
5539
|
+
...pairs && Object.keys(pairs).length ? { pairs } : {},
|
|
5540
|
+
outside: outsideSystems(op),
|
|
5541
|
+
// The org pack's facing words and gateway bundle folders (ADR 0147), as the catalog joins.
|
|
5542
|
+
...joinConventions(op),
|
|
5543
|
+
walkGateways: helperOn("gateway-walk")
|
|
5544
|
+
});
|
|
5545
|
+
}
|
|
5546
|
+
function auditLinkCandidates(join10, list, repo) {
|
|
5547
|
+
const facts = /* @__PURE__ */ new Map();
|
|
5548
|
+
for (const r of list)
|
|
5549
|
+
for (const f of r.facts)
|
|
5550
|
+
facts.set(`${r.repo}\0${f.id}`, f);
|
|
5551
|
+
const mine = join10.joins.filter((j) => j.call.repo === repo);
|
|
5552
|
+
const perCall = /* @__PURE__ */ new Map();
|
|
5553
|
+
for (const j of mine)
|
|
5554
|
+
perCall.set(j.call.fact, (perCall.get(j.call.fact) ?? 0) + 1);
|
|
5555
|
+
const at3 = (e) => `${e.repo} ${e.at}`;
|
|
5556
|
+
const out = [];
|
|
5557
|
+
for (const j of mine) {
|
|
5558
|
+
const call = facts.get(`${j.call.repo}\0${j.call.fact}`);
|
|
5559
|
+
const said = [call?.method?.toUpperCase(), call?.path].filter(Boolean).join(" ");
|
|
5560
|
+
const via = (j.gateway ?? []).map((h) => h.entry.repo);
|
|
5561
|
+
const flags = joinFlagsOf(j);
|
|
5562
|
+
const cites = [
|
|
5563
|
+
at3(j.call),
|
|
5564
|
+
...j.tie ? [at3(j.tie)] : [],
|
|
5565
|
+
...(j.through ?? []).map(at3),
|
|
5566
|
+
...(j.exposedBy ?? []).map(at3),
|
|
5567
|
+
...(j.gateway ?? []).flatMap((h) => [
|
|
5568
|
+
at3(h.entry),
|
|
5569
|
+
at3(h.call),
|
|
5570
|
+
...h.tie ? [at3(h.tie)] : [],
|
|
5571
|
+
...(h.through ?? []).map(at3),
|
|
5572
|
+
...(h.exposedBy ?? []).map(at3)
|
|
5573
|
+
]),
|
|
5574
|
+
...(j.entryThrough ?? []).map(at3),
|
|
5575
|
+
at3(j.entry)
|
|
5576
|
+
];
|
|
5577
|
+
out.push({
|
|
5578
|
+
id: `link:${j.call.fact}${(perCall.get(j.call.fact) ?? 0) > 1 ? `~${j.entry.fact}` : ""}`,
|
|
5579
|
+
kind: "link",
|
|
5580
|
+
// A publish joined to its listener carries no flag: its topic says how (ADR 0135).
|
|
5581
|
+
of: flags[0] ?? (j.how === "topic" ? "topic" : `joined-by-${j.how}`),
|
|
5582
|
+
claim: `${repo} ${said || j.call.fact} -> ${j.entry.repo} ${[j.method, j.path].filter(Boolean).join(" ")}${via.length ? ` via ${via.join(", ")}` : ""}`,
|
|
5583
|
+
cites: [...new Set(cites)],
|
|
5584
|
+
fact: j.call.fact,
|
|
5585
|
+
to: j.entry.repo
|
|
5586
|
+
});
|
|
5587
|
+
}
|
|
5588
|
+
return out.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
5589
|
+
}
|
|
5590
|
+
|
|
4281
5591
|
export {
|
|
4282
5592
|
buildGlossary,
|
|
4283
5593
|
GLOSSARY_CHANGE,
|
|
@@ -4300,31 +5610,99 @@ export {
|
|
|
4300
5610
|
diffCatalogs,
|
|
4301
5611
|
diffIsEmpty,
|
|
4302
5612
|
estateRepos,
|
|
5613
|
+
productionRepos,
|
|
5614
|
+
prototypeScanNotes,
|
|
4303
5615
|
isPrototypeRepo,
|
|
5616
|
+
estateMergeInput,
|
|
5617
|
+
mergeEstate,
|
|
5618
|
+
curatedDirOf,
|
|
5619
|
+
TARGETS_MAX_SUGGESTED,
|
|
5620
|
+
TARGETS_ANOTHER,
|
|
5621
|
+
TARGETS_NO_EVIDENCE,
|
|
5622
|
+
nameWords,
|
|
5623
|
+
targetSuggestions,
|
|
5624
|
+
offeredSuggestions,
|
|
5625
|
+
targetPicker,
|
|
5626
|
+
pickerLines,
|
|
5627
|
+
topicQuestionDrafts,
|
|
4304
5628
|
TARGETS_STEP,
|
|
5629
|
+
TARGETS_NONE,
|
|
5630
|
+
remapIds,
|
|
5631
|
+
targetQuestionFor,
|
|
5632
|
+
suggestionWhy,
|
|
4305
5633
|
targetQuestionDrafts,
|
|
5634
|
+
TARGETS_SPLIT,
|
|
5635
|
+
targetPathPrefix,
|
|
5636
|
+
targetGroupQuestion,
|
|
5637
|
+
targetGroupWordings,
|
|
5638
|
+
splitTargetGroups,
|
|
5639
|
+
groupedTargetDrafts,
|
|
5640
|
+
targetRuleOffers,
|
|
5641
|
+
ruleCoversCall,
|
|
5642
|
+
ruledTargets,
|
|
4306
5643
|
targetsFromAnswers,
|
|
5644
|
+
answeredQuestionOfCall,
|
|
5645
|
+
answerReconfirmed,
|
|
5646
|
+
staleTargetQuestions,
|
|
5647
|
+
componentRenames,
|
|
5648
|
+
factsRecords,
|
|
4307
5649
|
outsideSystems,
|
|
5650
|
+
outsideProfileSystems,
|
|
5651
|
+
hostAliases,
|
|
4308
5652
|
joinReposOf,
|
|
5653
|
+
applyFactJoins,
|
|
4309
5654
|
catalogWithFacts,
|
|
4310
5655
|
prototypeLeftOut,
|
|
5656
|
+
estateFromFacts,
|
|
5657
|
+
answerRuleOffers,
|
|
5658
|
+
factsForScan,
|
|
5659
|
+
pairsOfRepo,
|
|
4311
5660
|
estateFacts,
|
|
5661
|
+
NO_SETTINGS,
|
|
4312
5662
|
citedLine,
|
|
5663
|
+
isCodeFile,
|
|
5664
|
+
isConfigFile,
|
|
5665
|
+
globName,
|
|
5666
|
+
prefixOf,
|
|
4313
5667
|
checkScanSettings,
|
|
5668
|
+
joinBase,
|
|
5669
|
+
applyClients,
|
|
5670
|
+
withExcludes,
|
|
5671
|
+
applyPrefixes,
|
|
5672
|
+
pluginExtractor,
|
|
4314
5673
|
runPluginTests,
|
|
5674
|
+
pluginApplies,
|
|
5675
|
+
usablePlugins,
|
|
5676
|
+
withPluginScan,
|
|
5677
|
+
orgPackDirFor,
|
|
4315
5678
|
scanEstate,
|
|
5679
|
+
CLEAR_MATCH,
|
|
5680
|
+
PARTIAL_MATCH,
|
|
5681
|
+
TIE_MARGIN,
|
|
4316
5682
|
matchRepoType,
|
|
4317
5683
|
resolveSettingsTemplate,
|
|
4318
5684
|
draftRepoType,
|
|
4319
5685
|
repoComponents,
|
|
5686
|
+
pathScore,
|
|
5687
|
+
resourceKey,
|
|
5688
|
+
familyMismatches,
|
|
4320
5689
|
specTriggers,
|
|
5690
|
+
lineNamesUiWord,
|
|
5691
|
+
uiFindings,
|
|
4321
5692
|
checkSpec,
|
|
4322
5693
|
staleFlows,
|
|
4323
5694
|
citedFileHashes,
|
|
4324
5695
|
estateStepKey,
|
|
4325
5696
|
joinEstate,
|
|
4326
5697
|
estateFlow,
|
|
5698
|
+
flattenEstateFlow,
|
|
4327
5699
|
fieldLineage,
|
|
5700
|
+
lineSetsPort,
|
|
4328
5701
|
unknownHosts,
|
|
4329
|
-
|
|
5702
|
+
gatewayHostFix,
|
|
5703
|
+
hostQuestionDrafts,
|
|
5704
|
+
parseLinkReference,
|
|
5705
|
+
measureLinks,
|
|
5706
|
+
estateJoinOf,
|
|
5707
|
+
auditLinkCandidates
|
|
4330
5708
|
};
|