@unbrained/pm-cli 2026.7.20 → 2026.7.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/.claude-plugin/marketplace.json +2 -2
- package/CHANGELOG.md +11 -0
- package/dist/cli/commander-usage.d.ts +1 -1
- package/dist/cli/commander-usage.js +5 -5
- package/dist/cli/commander-usage.js.map +1 -1
- package/dist/cli/commands/completion.js +13 -5
- package/dist/cli/commands/completion.js.map +1 -1
- package/dist/cli/commands/contracts.js +4 -3
- package/dist/cli/commands/contracts.js.map +1 -1
- package/dist/cli/error-guidance.d.ts +2 -0
- package/dist/cli/error-guidance.js +7 -2
- package/dist/cli/error-guidance.js.map +1 -1
- package/dist/cli/main.d.ts +1 -5
- package/dist/cli/main.js +12 -50
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/register-list-query.js +13 -3
- package/dist/cli/register-list-query.js.map +1 -1
- package/dist/cli/register-mutation.js +45 -3
- package/dist/cli/register-mutation.js.map +1 -1
- package/dist/cli/register-structured-mutation.d.ts +14 -0
- package/dist/cli/register-structured-mutation.js +62 -0
- package/dist/cli/register-structured-mutation.js.map +1 -0
- package/dist/cli/registration-helpers.js +5 -3
- package/dist/cli/registration-helpers.js.map +1 -1
- package/dist/cli-bundle/bundle-manifest.json +280 -296
- package/dist/cli-bundle/chunks/{append-E6V54WOQ.js → append-7OVFYR3C.js} +3 -3
- package/dist/cli-bundle/chunks/{append-NNJ3EFIT.js → append-DFETHAZL.js} +3 -3
- package/dist/cli-bundle/chunks/{chunk-C3KD7U3T.js → chunk-2L6ZP22J.js} +241 -10
- package/dist/cli-bundle/chunks/chunk-2L6ZP22J.js.map +7 -0
- package/dist/cli-bundle/chunks/{chunk-KZEANWJS.js → chunk-4PUUNQDV.js} +98 -30
- package/dist/cli-bundle/chunks/chunk-4PUUNQDV.js.map +7 -0
- package/dist/cli-bundle/chunks/{chunk-RDV4ZFYS.js → chunk-4QOB7L7I.js} +7 -9
- package/dist/cli-bundle/chunks/{chunk-RDV4ZFYS.js.map → chunk-4QOB7L7I.js.map} +1 -1
- package/dist/cli-bundle/chunks/{chunk-3LPFHE3P.js → chunk-4QSQ6XDK.js} +62 -20
- package/dist/cli-bundle/chunks/chunk-4QSQ6XDK.js.map +1 -0
- package/dist/cli-bundle/chunks/{chunk-QJKL5PHS.js → chunk-7656MAUL.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-3AO5VSDR.js → chunk-7RW4JCU2.js} +328 -6
- package/dist/cli-bundle/chunks/chunk-7RW4JCU2.js.map +7 -0
- package/dist/cli-bundle/chunks/{chunk-X6K5XTTN.js → chunk-7W2UAXXT.js} +5 -5
- package/dist/cli-bundle/chunks/{chunk-SSKOQNMS.js → chunk-ABWCF646.js} +9 -11
- package/dist/cli-bundle/chunks/{chunk-SSKOQNMS.js.map → chunk-ABWCF646.js.map} +1 -1
- package/dist/cli-bundle/chunks/{chunk-WVA4DVNV.js → chunk-ADBTSVGK.js} +71 -33
- package/dist/cli-bundle/chunks/chunk-ADBTSVGK.js.map +7 -0
- package/dist/cli-bundle/chunks/{chunk-J4FBIFU6.js → chunk-AELMUANP.js} +12 -6
- package/dist/cli-bundle/chunks/chunk-AELMUANP.js.map +1 -0
- package/dist/cli-bundle/chunks/{chunk-4F6BWJ4J.js → chunk-AMJPSANK.js} +57 -36
- package/dist/cli-bundle/chunks/chunk-AMJPSANK.js.map +7 -0
- package/dist/cli-bundle/chunks/{chunk-6VIJ7UA5.js → chunk-AS4SAZ5R.js} +850 -819
- package/dist/cli-bundle/chunks/chunk-AS4SAZ5R.js.map +7 -0
- package/dist/cli-bundle/chunks/{chunk-SG74J6MV.js → chunk-ATBCUC7Y.js} +574 -5
- package/dist/cli-bundle/chunks/chunk-ATBCUC7Y.js.map +1 -0
- package/dist/cli-bundle/chunks/{chunk-5OK4UGFV.js → chunk-BXNDKOR2.js} +3 -3
- package/dist/cli-bundle/chunks/{chunk-KSPWRNVP.js → chunk-CPX275C6.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-JH2ZSLO3.js → chunk-DZRV2OPA.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-L7BH7IWU.js → chunk-E5XMKOSB.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-CQTQKUUV.js → chunk-EWJWSNRU.js} +3 -3
- package/dist/cli-bundle/chunks/{chunk-DAQ45GCS.js → chunk-F2LBF7XJ.js} +22 -15
- package/dist/cli-bundle/chunks/chunk-F2LBF7XJ.js.map +7 -0
- package/dist/cli-bundle/chunks/{chunk-6XMUURSS.js → chunk-FJIFFBJX.js} +64 -33
- package/dist/cli-bundle/chunks/chunk-FJIFFBJX.js.map +1 -0
- package/dist/cli-bundle/chunks/{chunk-E7ICD63T.js → chunk-FO6OSVEN.js} +4 -6
- package/dist/cli-bundle/chunks/{chunk-E7ICD63T.js.map → chunk-FO6OSVEN.js.map} +1 -1
- package/dist/cli-bundle/chunks/{chunk-3MCTMJXI.js → chunk-FPBMLYPI.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-UZDQXNUB.js → chunk-FSE2IKJQ.js} +46 -5
- package/dist/cli-bundle/chunks/chunk-FSE2IKJQ.js.map +1 -0
- package/dist/cli-bundle/chunks/{chunk-IX677TRO.js → chunk-GKX4NJ7U.js} +8 -10
- package/dist/cli-bundle/chunks/{chunk-IX677TRO.js.map → chunk-GKX4NJ7U.js.map} +1 -1
- package/dist/cli-bundle/chunks/{chunk-DVDBQA3B.js → chunk-GTK4DTEW.js} +5 -5
- package/dist/cli-bundle/chunks/chunk-H7EAER5L.js +974 -0
- package/dist/cli-bundle/chunks/chunk-H7EAER5L.js.map +1 -0
- package/dist/cli-bundle/chunks/{chunk-5UX66S6U.js → chunk-ICDAQ4TD.js} +316 -8
- package/dist/cli-bundle/chunks/chunk-ICDAQ4TD.js.map +1 -0
- package/dist/cli-bundle/chunks/{chunk-YQQNAKW3.js → chunk-JFD5LPMI.js} +6 -8
- package/dist/cli-bundle/chunks/{chunk-YQQNAKW3.js.map → chunk-JFD5LPMI.js.map} +1 -1
- package/dist/cli-bundle/chunks/{chunk-QANSTTC7.js → chunk-JXCW2TI2.js} +6 -8
- package/dist/cli-bundle/chunks/{chunk-QANSTTC7.js.map → chunk-JXCW2TI2.js.map} +1 -1
- package/dist/cli-bundle/chunks/{chunk-GSJSM5BJ.js → chunk-K36WV5J6.js} +76 -13
- package/dist/cli-bundle/chunks/chunk-K36WV5J6.js.map +1 -0
- package/dist/cli-bundle/chunks/{chunk-GVI6FAFG.js → chunk-K55VMLSS.js} +7 -5
- package/dist/cli-bundle/chunks/chunk-K55VMLSS.js.map +7 -0
- package/dist/cli-bundle/chunks/{chunk-GIQSU6S2.js → chunk-KBLZ3AA5.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-XJF7CDNH.js → chunk-KH44WBAT.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-GV2262OF.js → chunk-LAV26CQG.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-VDO2JMD4.js → chunk-LVC72YRD.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-3VY6MIEO.js → chunk-LZYP423P.js} +35 -6
- package/dist/cli-bundle/chunks/chunk-LZYP423P.js.map +7 -0
- package/dist/cli-bundle/chunks/{chunk-ZYPFOO3L.js → chunk-MATHDCGG.js} +3 -3
- package/dist/cli-bundle/chunks/{chunk-OV7MSW3I.js → chunk-MWQ2HC2A.js} +99 -31
- package/dist/cli-bundle/chunks/chunk-MWQ2HC2A.js.map +1 -0
- package/dist/cli-bundle/chunks/{chunk-NHT3F4WZ.js → chunk-NUF7ROFH.js} +840 -816
- package/dist/cli-bundle/chunks/chunk-NUF7ROFH.js.map +1 -0
- package/dist/cli-bundle/chunks/{chunk-AOEOYL3S.js → chunk-O4BCOJTZ.js} +3 -3
- package/dist/cli-bundle/chunks/{chunk-RJO3YAFM.js → chunk-OCUBRA6D.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-5IZJWSDB.js → chunk-OG5DVFMA.js} +22 -15
- package/dist/cli-bundle/chunks/chunk-OG5DVFMA.js.map +1 -0
- package/dist/cli-bundle/chunks/{chunk-I577UHC7.js → chunk-OR42SO64.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-ZOJ3LR7O.js → chunk-OY7DMU5P.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-HEWM6CEN.js → chunk-P7MTHPJQ.js} +50 -9
- package/dist/cli-bundle/chunks/chunk-P7MTHPJQ.js.map +7 -0
- package/dist/cli-bundle/chunks/{chunk-2BO4CA46.js → chunk-PFNHX655.js} +236 -12
- package/dist/cli-bundle/chunks/{chunk-2BO4CA46.js.map → chunk-PFNHX655.js.map} +1 -1
- package/dist/cli-bundle/chunks/{chunk-ZV2NTVMJ.js → chunk-PONKZPCJ.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-THAOZG4U.js → chunk-PQSEQD6E.js} +8 -6
- package/dist/cli-bundle/chunks/chunk-PQSEQD6E.js.map +1 -0
- package/dist/cli-bundle/chunks/chunk-PWDEHT3L.js +991 -0
- package/dist/cli-bundle/chunks/chunk-PWDEHT3L.js.map +7 -0
- package/dist/cli-bundle/chunks/{chunk-UX6SSO2I.js → chunk-PZC2FUOQ.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-WUVEBQW6.js → chunk-QG4GFLQ6.js} +3 -5
- package/dist/cli-bundle/chunks/{chunk-WUVEBQW6.js.map → chunk-QG4GFLQ6.js.map} +1 -1
- package/dist/cli-bundle/chunks/{chunk-VQIHNHMJ.js → chunk-QIH77CAG.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-R3EWPG4A.js → chunk-QJIKE24M.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-QLZ643FM.js → chunk-QOOTOD3W.js} +10 -12
- package/dist/cli-bundle/chunks/{chunk-QLZ643FM.js.map → chunk-QOOTOD3W.js.map} +1 -1
- package/dist/cli-bundle/chunks/{chunk-MYSS5H2A.js → chunk-R3P6FZJW.js} +5 -7
- package/dist/cli-bundle/chunks/{chunk-MYSS5H2A.js.map → chunk-R3P6FZJW.js.map} +1 -1
- package/dist/cli-bundle/chunks/{chunk-G3COA6GS.js → chunk-RNPZYNHS.js} +56 -35
- package/dist/cli-bundle/chunks/chunk-RNPZYNHS.js.map +1 -0
- package/dist/cli-bundle/chunks/{chunk-T44FMVRL.js → chunk-RPESLSO5.js} +62 -20
- package/dist/cli-bundle/chunks/chunk-RPESLSO5.js.map +7 -0
- package/dist/cli-bundle/chunks/{chunk-INSTSQNZ.js → chunk-SY4SFZSD.js} +595 -3
- package/dist/cli-bundle/chunks/chunk-SY4SFZSD.js.map +7 -0
- package/dist/cli-bundle/chunks/{chunk-HB3P4M52.js → chunk-TRSHFPRZ.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-HRW7GF6T.js → chunk-UCBDQI5E.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-4U4Z6AHI.js → chunk-V22ZPOKN.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-JM6YAXTR.js → chunk-VHXHVTUU.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-LPEYZRUR.js → chunk-VJQG4BIM.js} +35 -6
- package/dist/cli-bundle/chunks/chunk-VJQG4BIM.js.map +1 -0
- package/dist/cli-bundle/chunks/{chunk-D6NXDNP3.js → chunk-VLRGDBLY.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-JAPE62Y5.js → chunk-VNTCM3VW.js} +3 -3
- package/dist/cli-bundle/chunks/{chunk-FDHPAVVB.js → chunk-VXOOB6YX.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-GFQP7HAZ.js → chunk-VZIZB6QH.js} +3 -5
- package/dist/cli-bundle/chunks/{chunk-GFQP7HAZ.js.map → chunk-VZIZB6QH.js.map} +1 -1
- package/dist/cli-bundle/chunks/{chunk-HQ3SB4CN.js → chunk-X3W65QK3.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-RZJLNXFG.js → chunk-XJTABV3X.js} +3 -3
- package/dist/cli-bundle/chunks/{chunk-GVT754PX.js → chunk-YCIUJJOD.js} +11 -5
- package/dist/cli-bundle/chunks/chunk-YCIUJJOD.js.map +7 -0
- package/dist/cli-bundle/chunks/{chunk-IJNK6WW2.js → chunk-Z75ONV25.js} +75 -12
- package/dist/cli-bundle/chunks/chunk-Z75ONV25.js.map +7 -0
- package/dist/cli-bundle/chunks/{chunk-4L23AGQ7.js → chunk-Z7VWXXJM.js} +5 -7
- package/dist/cli-bundle/chunks/{chunk-4L23AGQ7.js.map → chunk-Z7VWXXJM.js.map} +1 -1
- package/dist/cli-bundle/chunks/{chunk-YBT7O4KT.js → chunk-ZTVA4KLN.js} +4 -6
- package/dist/cli-bundle/chunks/{chunk-YBT7O4KT.js.map → chunk-ZTVA4KLN.js.map} +1 -1
- package/dist/cli-bundle/chunks/{chunk-TYHJDFYW.js → chunk-ZVZGXPQ5.js} +2 -2
- package/dist/cli-bundle/chunks/{close-many-SDA426VT.js → close-many-5RZXVN4J.js} +5 -6
- package/dist/cli-bundle/chunks/{close-many-UTZANOCQ.js → close-many-RZVTUGBG.js} +5 -6
- package/dist/cli-bundle/chunks/{comments-LZN6FXXC.js → comments-TPU7QRBL.js} +4 -4
- package/dist/cli-bundle/chunks/{comments-VBQ345ND.js → comments-Z4KV6FU3.js} +4 -4
- package/dist/cli-bundle/chunks/{create-GG4RBCGC.js → create-EIFBIVNQ.js} +6 -6
- package/dist/cli-bundle/chunks/{create-JBC5KUW6.js → create-RGSPN5GD.js} +6 -6
- package/dist/cli-bundle/chunks/{deps-FI4O6NQS.js → deps-UH5EQQUY.js} +3 -3
- package/dist/cli-bundle/chunks/{deps-DELOO4TL.js → deps-YG7UF3VR.js} +3 -3
- package/dist/cli-bundle/chunks/{docs-6Z2MP526.js → docs-G32TK6XS.js} +4 -4
- package/dist/cli-bundle/chunks/{docs-2UYBP5D6.js → docs-JWWNRC27.js} +4 -4
- package/dist/cli-bundle/chunks/{files-HKEAN5MA.js → files-3H32UQ4K.js} +4 -4
- package/dist/cli-bundle/chunks/{files-HEUGSJZL.js → files-4ALMCSKB.js} +4 -4
- package/dist/cli-bundle/chunks/{history-compact-7NDVP3PT.js → history-compact-77BT2LUB.js} +3 -4
- package/dist/cli-bundle/chunks/{history-compact-65YVN5DB.js → history-compact-VCKGTX64.js} +3 -4
- package/dist/cli-bundle/chunks/{history-redact-7YBL4XSG.js → history-redact-LJPZPNFA.js} +2 -3
- package/dist/cli-bundle/chunks/{history-redact-IZGNJBU4.js → history-redact-PWTTJ3XS.js} +2 -3
- package/dist/cli-bundle/chunks/{history-repair-7M4PYQUH.js → history-repair-I7CBNCV3.js} +5 -6
- package/dist/cli-bundle/chunks/{history-repair-GNGETJLI.js → history-repair-YTDZRK5E.js} +5 -6
- package/dist/cli-bundle/chunks/{learnings-3YXDRWFT.js → learnings-CYNOICKR.js} +4 -4
- package/dist/cli-bundle/chunks/{learnings-ALC6IMNL.js → learnings-KTYW3AL7.js} +4 -4
- package/dist/cli-bundle/chunks/{notes-7JMLQ447.js → notes-FGO63FXL.js} +4 -4
- package/dist/cli-bundle/chunks/{notes-JFCLTC5C.js → notes-ZERD3KB2.js} +4 -4
- package/dist/cli-bundle/chunks/{plan-T3XB3WTK.js → plan-OFJUBIM5.js} +7 -7
- package/dist/cli-bundle/chunks/{plan-KOIPWVOZ.js → plan-SGCDDTYR.js} +7 -7
- package/dist/cli-bundle/chunks/{profile-OILJHKUW.js → profile-3THJPIQY.js} +4 -5
- package/dist/cli-bundle/chunks/{profile-RJXA7PGS.js → profile-XEZAMMBW.js} +4 -5
- package/dist/cli-bundle/chunks/{register-list-query-CFVG75P3.js → register-list-query-BR435Q2H.js} +19 -16
- package/dist/cli-bundle/chunks/register-list-query-BR435Q2H.js.map +1 -0
- package/dist/cli-bundle/chunks/{register-list-query-E7UVY4TN.js → register-list-query-RWTWLOBE.js} +18 -15
- package/dist/cli-bundle/chunks/register-list-query-RWTWLOBE.js.map +7 -0
- package/dist/cli-bundle/chunks/{register-mutation-IQ55E25C.js → register-mutation-HLWC2WNP.js} +167 -31
- package/dist/cli-bundle/chunks/register-mutation-HLWC2WNP.js.map +1 -0
- package/dist/cli-bundle/chunks/{register-mutation-4VJTXBZW.js → register-mutation-ZJX5AZM5.js} +173 -30
- package/dist/cli-bundle/chunks/register-mutation-ZJX5AZM5.js.map +7 -0
- package/dist/cli-bundle/chunks/{register-operations-AR7RMX4W.js → register-operations-744YBJMV.js} +23 -25
- package/dist/cli-bundle/chunks/{register-operations-AR7RMX4W.js.map → register-operations-744YBJMV.js.map} +1 -1
- package/dist/cli-bundle/chunks/{register-operations-NOLZGORC.js → register-operations-UEBJH6AN.js} +21 -23
- package/dist/cli-bundle/chunks/{register-operations-NOLZGORC.js.map → register-operations-UEBJH6AN.js.map} +1 -1
- package/dist/cli-bundle/chunks/{register-setup-EYZ7VTPZ.js → register-setup-5DA4UZXI.js} +7 -8
- package/dist/cli-bundle/chunks/{register-setup-EYZ7VTPZ.js.map → register-setup-5DA4UZXI.js.map} +1 -1
- package/dist/cli-bundle/chunks/{register-setup-BB77VUEE.js → register-setup-6H25M3BH.js} +9 -10
- package/dist/cli-bundle/chunks/{register-setup-BB77VUEE.js.map → register-setup-6H25M3BH.js.map} +1 -1
- package/dist/cli-bundle/chunks/{restore-ZH65GZFB.js → restore-6TDVSATY.js} +4 -5
- package/dist/cli-bundle/chunks/{restore-L6IAGOD7.js → restore-RSL7C7LO.js} +4 -5
- package/dist/cli-bundle/chunks/{schema-4ERWM5QJ.js → schema-HFXFSGPG.js} +2 -3
- package/dist/cli-bundle/chunks/{schema-55DL3Q52.js → schema-TPVTIJPO.js} +2 -3
- package/dist/cli-bundle/chunks/{update-FW6FKDOP.js → update-LJZCL5SI.js} +7 -7
- package/dist/cli-bundle/chunks/{update-TJHSPRPT.js → update-SIA7LH5Y.js} +7 -7
- package/dist/cli-bundle/chunks/{update-many-PWXX5WHO.js → update-many-BDBZVGQL.js} +11 -12
- package/dist/cli-bundle/chunks/{update-many-NNALYQCN.js → update-many-MOY3A5B3.js} +11 -12
- package/dist/cli-bundle/main.js +60 -85
- package/dist/cli-bundle/main.js.map +2 -2
- package/dist/cli-bundle/sdk-runtime.js +37 -37
- package/dist/cli-bundle/sdk.js +126 -291
- package/dist/cli-bundle/sdk.js.map +4 -4
- package/dist/core/item/parse.d.ts +1 -1
- package/dist/core/item/parse.js +13 -7
- package/dist/core/item/parse.js.map +1 -1
- package/dist/core/output/mutation-projection.d.ts +2 -0
- package/dist/core/output/mutation-projection.js +33 -2
- package/dist/core/output/mutation-projection.js.map +1 -1
- package/dist/core/output/output.d.ts +4 -0
- package/dist/core/output/output.js +18 -13
- package/dist/core/output/output.js.map +1 -1
- package/dist/core/shared/command-types.d.ts +4 -0
- package/dist/core/shared/command-types.js +2 -2
- package/dist/core/shared/command-types.js.map +1 -1
- package/dist/mcp/server.js +39 -4
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tool-definitions.js +45 -4
- package/dist/mcp/tool-definitions.js.map +1 -1
- package/dist/sdk/cli-bootstrap.d.ts +2 -0
- package/dist/sdk/cli-bootstrap.js +20 -27
- package/dist/sdk/cli-bootstrap.js.map +1 -1
- package/dist/sdk/cli-contracts/enum-contracts.d.ts +2 -2
- package/dist/sdk/cli-contracts/enum-contracts.js +7 -2
- package/dist/sdk/cli-contracts/enum-contracts.js.map +1 -1
- package/dist/sdk/cli-contracts/flag-contracts.d.ts +2 -0
- package/dist/sdk/cli-contracts/flag-contracts.js +21 -2
- package/dist/sdk/cli-contracts/flag-contracts.js.map +1 -1
- package/dist/sdk/cli-contracts/tool-option-contracts.js +10 -2
- package/dist/sdk/cli-contracts/tool-option-contracts.js.map +1 -1
- package/dist/sdk/cli-contracts/tool-parameter-tables.js +16 -3
- package/dist/sdk/cli-contracts/tool-parameter-tables.js.map +1 -1
- package/dist/sdk/cli-contracts/tool-schema.js +6 -2
- package/dist/sdk/cli-contracts/tool-schema.js.map +1 -1
- package/dist/sdk/cli-contracts.d.ts +1 -1
- package/dist/sdk/cli-contracts.js +3 -3
- package/dist/sdk/cli-contracts.js.map +1 -1
- package/dist/sdk/cli-program.js +4 -2
- package/dist/sdk/cli-program.js.map +1 -1
- package/dist/sdk/dependencies.d.ts +3 -2
- package/dist/sdk/dependencies.js +13 -12
- package/dist/sdk/dependencies.js.map +1 -1
- package/dist/sdk/error-runtime.d.ts +10 -0
- package/dist/sdk/error-runtime.js +59 -0
- package/dist/sdk/error-runtime.js.map +1 -0
- package/dist/sdk/graph/analytics.d.ts +9 -0
- package/dist/sdk/graph/analytics.js +24 -13
- package/dist/sdk/graph/analytics.js.map +1 -1
- package/dist/sdk/graph/assembly.d.ts +39 -0
- package/dist/sdk/graph/assembly.js +92 -12
- package/dist/sdk/graph/assembly.js.map +1 -1
- package/dist/sdk/graph/cache.d.ts +4 -1
- package/dist/sdk/graph/cache.js +17 -4
- package/dist/sdk/graph/cache.js.map +1 -1
- package/dist/sdk/graph/centrality.d.ts +82 -0
- package/dist/sdk/graph/centrality.js +259 -0
- package/dist/sdk/graph/centrality.js.map +1 -0
- package/dist/sdk/graph/durable-cache.d.ts +71 -0
- package/dist/sdk/graph/durable-cache.js +205 -0
- package/dist/sdk/graph/durable-cache.js.map +1 -0
- package/dist/sdk/graph/governance.d.ts +69 -1
- package/dist/sdk/graph/governance.js +143 -35
- package/dist/sdk/graph/governance.js.map +1 -1
- package/dist/sdk/graph/index.d.ts +7 -4
- package/dist/sdk/graph/index.js +8 -5
- package/dist/sdk/graph/index.js.map +1 -1
- package/dist/sdk/graph/mutation-advisory.d.ts +35 -4
- package/dist/sdk/graph/mutation-advisory.js +124 -35
- package/dist/sdk/graph/mutation-advisory.js.map +1 -1
- package/dist/sdk/graph/remediation.js +12 -2
- package/dist/sdk/graph/remediation.js.map +1 -1
- package/dist/sdk/graph/run.d.ts +133 -2
- package/dist/sdk/graph/run.js +217 -8
- package/dist/sdk/graph/run.js.map +1 -1
- package/dist/sdk/graph/scheduling.d.ts +59 -0
- package/dist/sdk/graph/scheduling.js +125 -0
- package/dist/sdk/graph/scheduling.js.map +1 -0
- package/dist/sdk/graph/traversal.d.ts +11 -0
- package/dist/sdk/graph/traversal.js +16 -2
- package/dist/sdk/graph/traversal.js.map +1 -1
- package/dist/sdk/index.d.ts +4 -0
- package/dist/sdk/index.js +6 -2
- package/dist/sdk/index.js.map +1 -1
- package/dist/sdk/runtime-input.js +7 -2
- package/dist/sdk/runtime-input.js.map +1 -1
- package/dist/sdk/runtime-primitives.d.ts +10 -0
- package/dist/sdk/runtime-primitives.js +14 -0
- package/dist/sdk/runtime-primitives.js.map +1 -0
- package/dist/sdk/runtime.d.ts +15 -0
- package/dist/sdk/runtime.js +16 -3
- package/dist/sdk/runtime.js.map +1 -1
- package/dist/sdk/structured-mutations.d.ts +21 -0
- package/dist/sdk/structured-mutations.js +375 -0
- package/dist/sdk/structured-mutations.js.map +1 -0
- package/docs/COMMANDS.md +85 -5
- package/docs/RELATIONSHIP_GRAPH.md +48 -7
- package/docs/SDK.md +78 -3
- package/marketplace.json +2 -2
- package/package.json +1 -1
- package/packages/pm-beads/package.json +1 -1
- package/packages/pm-calendar/package.json +1 -1
- package/packages/pm-command-kit/package.json +1 -1
- package/packages/pm-governance-audit/package.json +1 -1
- package/packages/pm-guide-shell/package.json +1 -1
- package/packages/pm-kanban/package.json +1 -1
- package/packages/pm-lifecycle-hooks/package.json +1 -1
- package/packages/pm-linked-test-adapters/package.json +1 -1
- package/packages/pm-search-advanced/package.json +1 -1
- package/packages/pm-templates/package.json +1 -1
- package/packages/pm-todos/package.json +1 -1
- package/packages/pm-vcs/package.json +1 -1
- package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
- package/plugins/pm-claude/README.md +5 -3
- package/plugins/pm-claude/commands/pm-init.md +1 -1
- package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
- package/dist/cli-bundle/chunks/chunk-3AO5VSDR.js.map +0 -7
- package/dist/cli-bundle/chunks/chunk-3LPFHE3P.js.map +0 -1
- package/dist/cli-bundle/chunks/chunk-3VY6MIEO.js.map +0 -7
- package/dist/cli-bundle/chunks/chunk-4F6BWJ4J.js.map +0 -7
- package/dist/cli-bundle/chunks/chunk-5IZJWSDB.js.map +0 -1
- package/dist/cli-bundle/chunks/chunk-5UX66S6U.js.map +0 -1
- package/dist/cli-bundle/chunks/chunk-6C2ZZX5Z.js +0 -286
- package/dist/cli-bundle/chunks/chunk-6C2ZZX5Z.js.map +0 -1
- package/dist/cli-bundle/chunks/chunk-6VIJ7UA5.js.map +0 -7
- package/dist/cli-bundle/chunks/chunk-6XMUURSS.js.map +0 -1
- package/dist/cli-bundle/chunks/chunk-C3KD7U3T.js.map +0 -7
- package/dist/cli-bundle/chunks/chunk-CFHAODHQ.js +0 -334
- package/dist/cli-bundle/chunks/chunk-CFHAODHQ.js.map +0 -1
- package/dist/cli-bundle/chunks/chunk-DAQ45GCS.js.map +0 -7
- package/dist/cli-bundle/chunks/chunk-E6LEMURX.js +0 -249
- package/dist/cli-bundle/chunks/chunk-E6LEMURX.js.map +0 -7
- package/dist/cli-bundle/chunks/chunk-G3COA6GS.js.map +0 -1
- package/dist/cli-bundle/chunks/chunk-GSJSM5BJ.js.map +0 -1
- package/dist/cli-bundle/chunks/chunk-GVI6FAFG.js.map +0 -7
- package/dist/cli-bundle/chunks/chunk-GVT754PX.js.map +0 -7
- package/dist/cli-bundle/chunks/chunk-HEWM6CEN.js.map +0 -7
- package/dist/cli-bundle/chunks/chunk-IJNK6WW2.js.map +0 -7
- package/dist/cli-bundle/chunks/chunk-INSTSQNZ.js.map +0 -7
- package/dist/cli-bundle/chunks/chunk-J4FBIFU6.js.map +0 -1
- package/dist/cli-bundle/chunks/chunk-KZEANWJS.js.map +0 -7
- package/dist/cli-bundle/chunks/chunk-LPEYZRUR.js.map +0 -1
- package/dist/cli-bundle/chunks/chunk-NHT3F4WZ.js.map +0 -1
- package/dist/cli-bundle/chunks/chunk-OV7MSW3I.js.map +0 -1
- package/dist/cli-bundle/chunks/chunk-P7JTWYLH.js +0 -246
- package/dist/cli-bundle/chunks/chunk-P7JTWYLH.js.map +0 -1
- package/dist/cli-bundle/chunks/chunk-R5U2VZ3K.js +0 -344
- package/dist/cli-bundle/chunks/chunk-R5U2VZ3K.js.map +0 -7
- package/dist/cli-bundle/chunks/chunk-SG74J6MV.js.map +0 -1
- package/dist/cli-bundle/chunks/chunk-T44FMVRL.js.map +0 -7
- package/dist/cli-bundle/chunks/chunk-THAOZG4U.js.map +0 -1
- package/dist/cli-bundle/chunks/chunk-UQLXSG72.js +0 -289
- package/dist/cli-bundle/chunks/chunk-UQLXSG72.js.map +0 -7
- package/dist/cli-bundle/chunks/chunk-UZDQXNUB.js.map +0 -1
- package/dist/cli-bundle/chunks/chunk-WVA4DVNV.js.map +0 -7
- package/dist/cli-bundle/chunks/register-list-query-CFVG75P3.js.map +0 -1
- package/dist/cli-bundle/chunks/register-list-query-E7UVY4TN.js.map +0 -7
- package/dist/cli-bundle/chunks/register-mutation-4VJTXBZW.js.map +0 -7
- package/dist/cli-bundle/chunks/register-mutation-IQ55E25C.js.map +0 -1
- /package/dist/cli-bundle/chunks/{append-E6V54WOQ.js.map → append-7OVFYR3C.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{append-NNJ3EFIT.js.map → append-DFETHAZL.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{chunk-QJKL5PHS.js.map → chunk-7656MAUL.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{chunk-X6K5XTTN.js.map → chunk-7W2UAXXT.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{chunk-5OK4UGFV.js.map → chunk-BXNDKOR2.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{chunk-KSPWRNVP.js.map → chunk-CPX275C6.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{chunk-JH2ZSLO3.js.map → chunk-DZRV2OPA.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{chunk-L7BH7IWU.js.map → chunk-E5XMKOSB.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{chunk-CQTQKUUV.js.map → chunk-EWJWSNRU.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{chunk-3MCTMJXI.js.map → chunk-FPBMLYPI.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{chunk-DVDBQA3B.js.map → chunk-GTK4DTEW.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{chunk-GIQSU6S2.js.map → chunk-KBLZ3AA5.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{chunk-XJF7CDNH.js.map → chunk-KH44WBAT.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{chunk-GV2262OF.js.map → chunk-LAV26CQG.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{chunk-VDO2JMD4.js.map → chunk-LVC72YRD.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{chunk-ZYPFOO3L.js.map → chunk-MATHDCGG.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{chunk-AOEOYL3S.js.map → chunk-O4BCOJTZ.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{chunk-RJO3YAFM.js.map → chunk-OCUBRA6D.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{chunk-I577UHC7.js.map → chunk-OR42SO64.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{chunk-ZOJ3LR7O.js.map → chunk-OY7DMU5P.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{chunk-ZV2NTVMJ.js.map → chunk-PONKZPCJ.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{chunk-UX6SSO2I.js.map → chunk-PZC2FUOQ.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{chunk-VQIHNHMJ.js.map → chunk-QIH77CAG.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{chunk-R3EWPG4A.js.map → chunk-QJIKE24M.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{chunk-HB3P4M52.js.map → chunk-TRSHFPRZ.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{chunk-HRW7GF6T.js.map → chunk-UCBDQI5E.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{chunk-4U4Z6AHI.js.map → chunk-V22ZPOKN.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{chunk-JM6YAXTR.js.map → chunk-VHXHVTUU.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{chunk-D6NXDNP3.js.map → chunk-VLRGDBLY.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{chunk-JAPE62Y5.js.map → chunk-VNTCM3VW.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{chunk-FDHPAVVB.js.map → chunk-VXOOB6YX.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{chunk-HQ3SB4CN.js.map → chunk-X3W65QK3.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{chunk-RZJLNXFG.js.map → chunk-XJTABV3X.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{chunk-TYHJDFYW.js.map → chunk-ZVZGXPQ5.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{close-many-SDA426VT.js.map → close-many-5RZXVN4J.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{close-many-UTZANOCQ.js.map → close-many-RZVTUGBG.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{comments-LZN6FXXC.js.map → comments-TPU7QRBL.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{comments-VBQ345ND.js.map → comments-Z4KV6FU3.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{create-GG4RBCGC.js.map → create-EIFBIVNQ.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{create-JBC5KUW6.js.map → create-RGSPN5GD.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{deps-FI4O6NQS.js.map → deps-UH5EQQUY.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{deps-DELOO4TL.js.map → deps-YG7UF3VR.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{docs-6Z2MP526.js.map → docs-G32TK6XS.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{docs-2UYBP5D6.js.map → docs-JWWNRC27.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{files-HKEAN5MA.js.map → files-3H32UQ4K.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{files-HEUGSJZL.js.map → files-4ALMCSKB.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{history-compact-7NDVP3PT.js.map → history-compact-77BT2LUB.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{history-compact-65YVN5DB.js.map → history-compact-VCKGTX64.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{history-redact-7YBL4XSG.js.map → history-redact-LJPZPNFA.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{history-redact-IZGNJBU4.js.map → history-redact-PWTTJ3XS.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{history-repair-7M4PYQUH.js.map → history-repair-I7CBNCV3.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{history-repair-GNGETJLI.js.map → history-repair-YTDZRK5E.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{learnings-3YXDRWFT.js.map → learnings-CYNOICKR.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{learnings-ALC6IMNL.js.map → learnings-KTYW3AL7.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{notes-7JMLQ447.js.map → notes-FGO63FXL.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{notes-JFCLTC5C.js.map → notes-ZERD3KB2.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{plan-T3XB3WTK.js.map → plan-OFJUBIM5.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{plan-KOIPWVOZ.js.map → plan-SGCDDTYR.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{profile-OILJHKUW.js.map → profile-3THJPIQY.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{profile-RJXA7PGS.js.map → profile-XEZAMMBW.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{restore-ZH65GZFB.js.map → restore-6TDVSATY.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{restore-L6IAGOD7.js.map → restore-RSL7C7LO.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{schema-4ERWM5QJ.js.map → schema-HFXFSGPG.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{schema-55DL3Q52.js.map → schema-TPVTIJPO.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{update-FW6FKDOP.js.map → update-LJZCL5SI.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{update-TJHSPRPT.js.map → update-SIA7LH5Y.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{update-many-PWXX5WHO.js.map → update-many-BDBZVGQL.js.map} +0 -0
- /package/dist/cli-bundle/chunks/{update-many-NNALYQCN.js.map → update-many-MOY3A5B3.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.js","sources":["sdk/graph/cache.ts"],"sourceRoot":"/","sourcesContent":["/**\n * @module sdk/graph/cache\n *\n * Incremental workspace graph cache: fingerprint-keyed reuse of the assembled\n * relationship graph plus memoized analytics results, so long-lived processes\n * (the MCP server, SDK consumers, packages) stop paying the full-workspace\n * assembly and analysis cost on every bounded query. The fingerprint is a\n * deterministic digest of every relationship-relevant item field — id, title,\n * lifecycle status and its terminal classification, hierarchy parent, legacy\n * blocker, and structured dependency edges — so any mutation that could change\n * the graph or its projections invalidates the entry, while unrelated payload\n * churn (bodies, comments) does not enter the digest at all. Every lookup\n * reports hit/miss observability so envelopes can explain their freshness.\n */\nimport { createHash } from \"node:crypto\";\nimport type { Dependency, ItemStatus } from \"../../types/index.js\";\nimport type {\n WorkspaceRelationshipAssembly,\n WorkspaceRelationshipItem,\n} from \"./assembly.js\";\n\n/** Default memoized query results retained per workspace entry. */\nconst DEFAULT_MAX_RESULTS_PER_WORKSPACE = 64;\n/** Default workspace entries retained by one cache instance. */\nconst DEFAULT_MAX_WORKSPACES = 8;\n\n/** Cache observability attached to graph result envelopes. */\nexport interface GraphCacheMetadata {\n /** Truncated workspace graph fingerprint identifying the snapshot answered from. */\n fingerprint: string;\n /** Whether the workspace assembly was reused from cache. */\n assembly: \"hit\" | \"miss\";\n /** Whether the full query result was reused from cache. */\n result: \"hit\" | \"miss\";\n}\n\n/** Digest one item's relationship-relevant fields into a deterministic line. */\nfunction fingerprintLine(\n item: WorkspaceRelationshipItem,\n isTerminal: (status: ItemStatus) => boolean,\n): string {\n const dependencies = (item.dependencies ?? [])\n .map((rawDependency) => {\n // Public SDK callers may supply legacy or JSON-decoded payloads; mirror\n // assembly's defensive normalization so the digest tracks what assembly\n // would actually index.\n if (typeof rawDependency !== \"object\" || rawDependency === null)\n return \"\";\n const dependency = rawDependency as Partial<Dependency>;\n return `${typeof dependency.kind === \"string\" ? dependency.kind : \"related\"}:${\n typeof dependency.id === \"string\" ? dependency.id : \"\"\n }`;\n })\n .sort()\n .join(\",\");\n return [\n item.id,\n item.title,\n item.status,\n isTerminal(item.status) ? \"1\" : \"0\",\n item.parent ?? \"\",\n item.blocked_by ?? \"\",\n dependencies,\n ].join(\"\\u0000\");\n}\n\n/**\n * Compute the deterministic workspace graph fingerprint for one item snapshot.\n * Items are digested in sorted order so storage enumeration order never\n * changes the fingerprint, and the terminal classification of each status is\n * folded in so schema changes to lifecycle semantics invalidate cached\n * assemblies even when raw statuses are unchanged.\n */\nexport function computeWorkspaceGraphFingerprint(\n items: readonly WorkspaceRelationshipItem[],\n isTerminal: (status: ItemStatus) => boolean = (status) =>\n status === \"closed\" || status === \"canceled\",\n): string {\n const lines = items\n .filter((item) => typeof item?.id === \"string\" && item.id.trim().length > 0)\n .map((item) => fingerprintLine(item, isTerminal))\n .sort();\n // Separate fields and records with control characters no stored field can\n // contain unescaped, so adjacent free-text fields can never collide.\n return createHash(\"sha256\").update(lines.join(\"\\u0001\")).digest(\"hex\");\n}\n\n/** One cached workspace snapshot: its assembly plus memoized query results. */\ninterface WorkspaceGraphCacheEntry {\n /** Fingerprint of the item snapshot this entry answers for. */\n fingerprint: string;\n /** Assembled relationship graph reused across queries. */\n assembly: WorkspaceRelationshipAssembly;\n /** Bounded insertion-ordered memo of deterministic query results. */\n results: Map<string, unknown>;\n}\n\n/** One resolved cache lookup bound to a workspace snapshot. */\nexport interface WorkspaceGraphCacheLookup {\n /** The (possibly reused) workspace assembly. */\n assembly: WorkspaceRelationshipAssembly;\n /** Whether the assembly came from cache instead of being rebuilt. */\n assemblyReused: boolean;\n /** Full fingerprint of the snapshot this lookup is bound to. */\n fingerprint: string;\n /**\n * Memoize one deterministic query result under this snapshot. On a miss the\n * freshly computed value is returned as-is while an independent clone is\n * stored, so mutating a miss result never corrupts the cache; every hit\n * returns a fresh clone of the stored entry. Callers must not rely on the\n * miss and hit paths returning the same object identity.\n */\n memoize<T>(queryKey: string, compute: () => T): { value: T; reused: boolean };\n}\n\n/** Validate one optional positive-integer cache bound, applying its default. */\nfunction requirePositiveCacheBound(\n name: string,\n value: number | undefined,\n fallback: number,\n): number {\n const bound = value ?? fallback;\n if (!Number.isInteger(bound) || bound < 1)\n throw new TypeError(`Invalid ${name} bound: ${String(value)}`);\n return bound;\n}\n\n/**\n * Fingerprint-keyed cache of workspace relationship assemblies and memoized\n * deterministic query results. One entry is retained per workspace key; a\n * changed fingerprint atomically replaces the entry and drops its memoized\n * results. Memoized values are structured-cloned on store and on read so\n * neither the producer nor any consumer can mutate cached state.\n */\nexport class WorkspaceGraphCache {\n readonly #entries = new Map<string, WorkspaceGraphCacheEntry>();\n readonly #maxResultsPerWorkspace: number;\n readonly #maxWorkspaces: number;\n\n /** Create a cache, optionally bounding workspaces and memoized results. */\n public constructor(\n options: { maxResultsPerWorkspace?: number; maxWorkspaces?: number } = {},\n ) {\n this.#maxResultsPerWorkspace = requirePositiveCacheBound(\n \"maxResultsPerWorkspace\",\n options.maxResultsPerWorkspace,\n DEFAULT_MAX_RESULTS_PER_WORKSPACE,\n );\n this.#maxWorkspaces = requirePositiveCacheBound(\n \"maxWorkspaces\",\n options.maxWorkspaces,\n DEFAULT_MAX_WORKSPACES,\n );\n }\n\n /**\n * Resolve the cached assembly for one workspace key and fingerprint,\n * building and storing it on miss. The returned lookup exposes snapshot-\n * scoped memoization for query results derived from the same assembly.\n */\n public lookup(\n key: string,\n fingerprint: string,\n build: () => WorkspaceRelationshipAssembly,\n ): WorkspaceGraphCacheLookup {\n let entry = this.#entries.get(key);\n let assemblyReused = true;\n if (entry === undefined || entry.fingerprint !== fingerprint) {\n entry = { fingerprint, assembly: build(), results: new Map() };\n assemblyReused = false;\n if (\n !this.#entries.has(key) &&\n this.#entries.size >= this.#maxWorkspaces\n ) {\n // Evict the least recently used workspace; every lookup refreshes\n // recency below, so insertion order tracks use order.\n this.#entries.delete(this.#entries.keys().next().value!);\n }\n }\n // Delete-and-reinsert keeps Map insertion order equal to recency order,\n // making workspace eviction genuinely least-recently-used.\n this.#entries.delete(key);\n this.#entries.set(key, entry);\n const resolved = entry;\n const maxResults = this.#maxResultsPerWorkspace;\n return {\n assembly: resolved.assembly,\n assemblyReused,\n fingerprint,\n memoize<T>(\n queryKey: string,\n compute: () => T,\n ): { value: T; reused: boolean } {\n const cached = resolved.results.get(queryKey);\n if (cached !== undefined || resolved.results.has(queryKey)) {\n // Refresh recency so eviction below is least-recently-used rather\n // than first-inserted.\n resolved.results.delete(queryKey);\n resolved.results.set(queryKey, cached);\n return { value: structuredClone(cached) as T, reused: true };\n }\n const value = compute();\n if (resolved.results.size >= maxResults) {\n // Evict the oldest memoized result; Map preserves insertion order.\n resolved.results.delete(resolved.results.keys().next().value!);\n }\n resolved.results.set(queryKey, structuredClone(value));\n return { value, reused: false };\n },\n };\n }\n\n /** Drop every cached workspace entry and memoized result. */\n public clear(): void {\n this.#entries.clear();\n }\n}\n\n/** Process-wide shared cache used by the CLI/MCP graph and deps adapters. */\nconst sharedCache = new WorkspaceGraphCache();\n\n/** Return the process-wide shared workspace graph cache. */\nexport function workspaceGraphCache(): WorkspaceGraphCache {\n return sharedCache;\n}\n\n/** Reset the process-wide shared workspace graph cache (tests and long-lived hosts). */\nexport function resetWorkspaceGraphCache(): void {\n sharedCache.clear();\n}\n"],"names":[],"mappings":"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","debugId":"6c46fa7f-4566-5a15-ac76-1666770dc806"}
|
|
1
|
+
{"version":3,"file":"cache.js","sources":["sdk/graph/cache.ts"],"sourceRoot":"/","sourcesContent":["/**\n * @module sdk/graph/cache\n *\n * Incremental workspace graph cache: fingerprint-keyed reuse of the assembled\n * relationship graph plus memoized analytics results, so long-lived processes\n * (the MCP server, SDK consumers, packages) stop paying the full-workspace\n * assembly and analysis cost on every bounded query. The fingerprint is a\n * deterministic digest of every relationship-relevant item field — id, title,\n * lifecycle status and its terminal classification, hierarchy parent, legacy\n * blocker, and structured dependency edges — so any mutation that could change\n * the graph or its projections invalidates the entry, while unrelated payload\n * churn (bodies, comments) does not enter the digest at all. Every lookup\n * reports hit/miss observability so envelopes can explain their freshness.\n */\nimport { createHash } from \"node:crypto\";\nimport { stableStringify } from \"../../core/shared/serialization.js\";\nimport type { Dependency, ItemStatus } from \"../../types/index.js\";\nimport {\n createRelationshipKindRegistry,\n type RelationshipKindRegistry,\n} from \"../relationships.js\";\nimport type {\n WorkspaceRelationshipAssembly,\n WorkspaceRelationshipItem,\n} from \"./assembly.js\";\n\n/** Default memoized query results retained per workspace entry. */\nconst DEFAULT_MAX_RESULTS_PER_WORKSPACE = 64;\n/** Default workspace entries retained by one cache instance. */\nconst DEFAULT_MAX_WORKSPACES = 8;\n\n/** Cache observability attached to graph result envelopes. */\nexport interface GraphCacheMetadata {\n /** Truncated workspace graph fingerprint identifying the snapshot answered from. */\n fingerprint: string;\n /** Whether the workspace assembly was reused from cache. */\n assembly: \"hit\" | \"miss\";\n /** Whether the full query result was reused from cache. */\n result: \"hit\" | \"miss\";\n /** Durable cross-process index disposition: answered from it, missed it, or persistence disabled for this workspace. Absent on surfaces that never consult the durable index. */\n durable?: \"hit\" | \"miss\" | \"off\";\n}\n\n/** Digest one item's relationship-relevant fields into a deterministic line. */\nfunction fingerprintLine(\n item: WorkspaceRelationshipItem,\n isTerminal: (status: ItemStatus) => boolean,\n): string {\n const dependencies = (item.dependencies ?? [])\n .map((rawDependency) => {\n // Public SDK callers may supply legacy or JSON-decoded payloads; mirror\n // assembly's defensive normalization so the digest tracks what assembly\n // would actually index.\n if (typeof rawDependency !== \"object\" || rawDependency === null)\n return \"\";\n const dependency = rawDependency as Partial<Dependency>;\n return `${typeof dependency.kind === \"string\" ? dependency.kind : \"related\"}:${\n typeof dependency.id === \"string\" ? dependency.id : \"\"\n }`;\n })\n .sort()\n .join(\",\");\n return [\n item.id,\n item.title,\n item.status,\n isTerminal(item.status) ? \"1\" : \"0\",\n // Per-type coverage profiles make the item type a projection input, so a\n // retype must invalidate cached assemblies and memoized audit results.\n typeof item.type === \"string\" ? item.type.trim() : \"\",\n item.parent ?? \"\",\n item.blocked_by ?? \"\",\n dependencies,\n ].join(\"\\u0000\");\n}\n\n/**\n * Compute the deterministic workspace graph fingerprint for one item snapshot.\n * Items are digested in sorted order so storage enumeration order never\n * changes the fingerprint, and the terminal classification of each status is\n * folded in so schema changes to lifecycle semantics invalidate cached\n * assemblies even when raw statuses are unchanged.\n */\nexport function computeWorkspaceGraphFingerprint(\n items: readonly WorkspaceRelationshipItem[],\n isTerminal: (status: ItemStatus) => boolean = (status) =>\n status === \"closed\" || status === \"canceled\",\n registry: RelationshipKindRegistry = createRelationshipKindRegistry(),\n): string {\n const lines = items\n .filter((item) => typeof item?.id === \"string\" && item.id.trim().length > 0)\n .map((item) => fingerprintLine(item, isTerminal))\n .sort();\n const relationshipSemantics = registry\n .list()\n .map((definition) => stableStringify(definition))\n .sort();\n // Separate fields and records with control characters no stored field can\n // contain unescaped, so adjacent free-text fields can never collide.\n return createHash(\"sha256\")\n .update(lines.join(\"\\u0001\"))\n .update(\"\\u0002\")\n .update(relationshipSemantics.join(\"\\u0001\"))\n .digest(\"hex\");\n}\n\n/** One cached workspace snapshot: its assembly plus memoized query results. */\ninterface WorkspaceGraphCacheEntry {\n /** Fingerprint of the item snapshot this entry answers for. */\n fingerprint: string;\n /** Assembled relationship graph reused across queries. */\n assembly: WorkspaceRelationshipAssembly;\n /** Bounded insertion-ordered memo of deterministic query results. */\n results: Map<string, unknown>;\n}\n\n/** One resolved cache lookup bound to a workspace snapshot. */\nexport interface WorkspaceGraphCacheLookup {\n /** The (possibly reused) workspace assembly. */\n assembly: WorkspaceRelationshipAssembly;\n /** Whether the assembly came from cache instead of being rebuilt. */\n assemblyReused: boolean;\n /** Full fingerprint of the snapshot this lookup is bound to. */\n fingerprint: string;\n /**\n * Memoize one deterministic query result under this snapshot. On a miss the\n * freshly computed value is returned as-is while an independent clone is\n * stored, so mutating a miss result never corrupts the cache; every hit\n * returns a fresh clone of the stored entry. Callers must not rely on the\n * miss and hit paths returning the same object identity.\n */\n memoize<T>(queryKey: string, compute: () => T): { value: T; reused: boolean };\n}\n\n/** Validate one optional positive-integer cache bound, applying its default. */\nfunction requirePositiveCacheBound(\n name: string,\n value: number | undefined,\n fallback: number,\n): number {\n const bound = value ?? fallback;\n if (!Number.isInteger(bound) || bound < 1)\n throw new TypeError(`Invalid ${name} bound: ${String(value)}`);\n return bound;\n}\n\n/**\n * Fingerprint-keyed cache of workspace relationship assemblies and memoized\n * deterministic query results. One entry is retained per workspace key; a\n * changed fingerprint atomically replaces the entry and drops its memoized\n * results. Memoized values are structured-cloned on store and on read so\n * neither the producer nor any consumer can mutate cached state.\n */\nexport class WorkspaceGraphCache {\n readonly #entries = new Map<string, WorkspaceGraphCacheEntry>();\n readonly #maxResultsPerWorkspace: number;\n readonly #maxWorkspaces: number;\n\n /** Create a cache, optionally bounding workspaces and memoized results. */\n public constructor(\n options: { maxResultsPerWorkspace?: number; maxWorkspaces?: number } = {},\n ) {\n this.#maxResultsPerWorkspace = requirePositiveCacheBound(\n \"maxResultsPerWorkspace\",\n options.maxResultsPerWorkspace,\n DEFAULT_MAX_RESULTS_PER_WORKSPACE,\n );\n this.#maxWorkspaces = requirePositiveCacheBound(\n \"maxWorkspaces\",\n options.maxWorkspaces,\n DEFAULT_MAX_WORKSPACES,\n );\n }\n\n /**\n * Resolve the cached assembly for one workspace key and fingerprint,\n * building and storing it on miss. The returned lookup exposes snapshot-\n * scoped memoization for query results derived from the same assembly.\n */\n public lookup(\n key: string,\n fingerprint: string,\n build: () => WorkspaceRelationshipAssembly,\n ): WorkspaceGraphCacheLookup {\n let entry = this.#entries.get(key);\n let assemblyReused = true;\n if (entry === undefined || entry.fingerprint !== fingerprint) {\n entry = { fingerprint, assembly: build(), results: new Map() };\n assemblyReused = false;\n if (\n !this.#entries.has(key) &&\n this.#entries.size >= this.#maxWorkspaces\n ) {\n // Evict the least recently used workspace; every lookup refreshes\n // recency below, so insertion order tracks use order.\n this.#entries.delete(this.#entries.keys().next().value!);\n }\n }\n // Delete-and-reinsert keeps Map insertion order equal to recency order,\n // making workspace eviction genuinely least-recently-used.\n this.#entries.delete(key);\n this.#entries.set(key, entry);\n const resolved = entry;\n const maxResults = this.#maxResultsPerWorkspace;\n return {\n assembly: resolved.assembly,\n assemblyReused,\n fingerprint,\n memoize<T>(\n queryKey: string,\n compute: () => T,\n ): { value: T; reused: boolean } {\n const cached = resolved.results.get(queryKey);\n if (cached !== undefined || resolved.results.has(queryKey)) {\n // Refresh recency so eviction below is least-recently-used rather\n // than first-inserted.\n resolved.results.delete(queryKey);\n resolved.results.set(queryKey, cached);\n return { value: structuredClone(cached) as T, reused: true };\n }\n const value = compute();\n if (resolved.results.size >= maxResults) {\n // Evict the oldest memoized result; Map preserves insertion order.\n resolved.results.delete(resolved.results.keys().next().value!);\n }\n resolved.results.set(queryKey, structuredClone(value));\n return { value, reused: false };\n },\n };\n }\n\n /** Drop every cached workspace entry and memoized result. */\n public clear(): void {\n this.#entries.clear();\n }\n}\n\n/** Process-wide shared cache used by the CLI/MCP graph and deps adapters. */\nconst sharedCache = new WorkspaceGraphCache();\n\n/** Return the process-wide shared workspace graph cache. */\nexport function workspaceGraphCache(): WorkspaceGraphCache {\n return sharedCache;\n}\n\n/** Reset the process-wide shared workspace graph cache (tests and long-lived hosts). */\nexport function resetWorkspaceGraphCache(): void {\n sharedCache.clear();\n}\n"],"names":[],"mappings":"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","debugId":"2131cf48-7b5e-5b17-bb53-65e8bf485100"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module sdk/graph/centrality
|
|
3
|
+
*
|
|
4
|
+
* Structural-importance and fragility analytics over the undirected knowledge
|
|
5
|
+
* graph: exact shortest-path betweenness (Brandes), Wasserman–Faust closeness,
|
|
6
|
+
* undirected degree, and directed fan-in/fan-out per node, plus articulation
|
|
7
|
+
* points and bridges (single points of failure) via an iterative Tarjan
|
|
8
|
+
* low-link search. Every analysis is deterministic, cancellable, exact on the
|
|
9
|
+
* bounded workspace, and reports the visited-node and inspected-edge cost so
|
|
10
|
+
* agents can budget follow-up reads. Adjacency is the simple undirected graph
|
|
11
|
+
* (parallel edges and self-loops collapsed) so a pair connected by any kind
|
|
12
|
+
* counts once.
|
|
13
|
+
*/
|
|
14
|
+
import { RelationshipGraph, type RelationshipQueryResult } from "../relationships.js";
|
|
15
|
+
/** One node ranked by structural centrality with directed fan-in/fan-out. */
|
|
16
|
+
export interface RelationshipCentralityRow {
|
|
17
|
+
/** Node id. */
|
|
18
|
+
id: string;
|
|
19
|
+
/** Undirected unique-neighbor degree over selected kinds. */
|
|
20
|
+
degree: number;
|
|
21
|
+
/** Distinct ordering predecessors that gate this node (dependency fan-in). */
|
|
22
|
+
inDegree: number;
|
|
23
|
+
/** Distinct ordering successors this node gates (dependency fan-out). */
|
|
24
|
+
outDegree: number;
|
|
25
|
+
/** Shortest-path betweenness centrality, rounded to six decimals. */
|
|
26
|
+
betweenness: number;
|
|
27
|
+
/** Wasserman–Faust closeness centrality, rounded to six decimals. */
|
|
28
|
+
closeness: number;
|
|
29
|
+
}
|
|
30
|
+
/** Bounded centrality-analysis controls. */
|
|
31
|
+
export interface GraphCentralityOptions {
|
|
32
|
+
/** Registered relationship kinds whose edges define adjacency. */
|
|
33
|
+
kinds?: readonly string[];
|
|
34
|
+
/** Abort signal checked once per source sweep. */
|
|
35
|
+
signal?: AbortSignal;
|
|
36
|
+
}
|
|
37
|
+
/** Deterministic centrality ranking over the undirected knowledge graph. */
|
|
38
|
+
export interface RelationshipCentralityAnalysis {
|
|
39
|
+
/** Total indexed graph nodes. */
|
|
40
|
+
nodeCount: number;
|
|
41
|
+
/** Simple undirected edge count over selected kinds. */
|
|
42
|
+
edgeCount: number;
|
|
43
|
+
/** Node rows ranked by betweenness, then degree, then id. */
|
|
44
|
+
rows: RelationshipCentralityRow[];
|
|
45
|
+
}
|
|
46
|
+
/** One undirected bridge edge whose removal disconnects its endpoints. */
|
|
47
|
+
export interface RelationshipBridge {
|
|
48
|
+
/** Lexicographically smaller endpoint. */
|
|
49
|
+
source: string;
|
|
50
|
+
/** Lexicographically larger endpoint. */
|
|
51
|
+
target: string;
|
|
52
|
+
}
|
|
53
|
+
/** Bounded cut-structure controls. */
|
|
54
|
+
export interface GraphCutStructureOptions {
|
|
55
|
+
/** Registered relationship kinds whose edges define adjacency. */
|
|
56
|
+
kinds?: readonly string[];
|
|
57
|
+
/** Abort signal checked once per depth-first step. */
|
|
58
|
+
signal?: AbortSignal;
|
|
59
|
+
}
|
|
60
|
+
/** Articulation points and bridges of the undirected knowledge graph. */
|
|
61
|
+
export interface RelationshipCutStructure {
|
|
62
|
+
/** Sorted articulation-point ids — nodes whose removal adds components. */
|
|
63
|
+
articulationPoints: string[];
|
|
64
|
+
/** Bridge edges sorted by source then target. */
|
|
65
|
+
bridges: RelationshipBridge[];
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Compute exact shortest-path betweenness (Brandes), Wasserman–Faust closeness,
|
|
69
|
+
* undirected degree, and directed fan-in/fan-out for every node over the simple
|
|
70
|
+
* undirected graph induced by the selected kinds. Rows rank by betweenness, then
|
|
71
|
+
* degree, then id. The result is exact on the bounded workspace; callers bound
|
|
72
|
+
* the returned rows for token-aware projection.
|
|
73
|
+
*/
|
|
74
|
+
export declare function computeRelationshipCentrality(graph: RelationshipGraph, options?: GraphCentralityOptions): RelationshipQueryResult<RelationshipCentralityAnalysis>;
|
|
75
|
+
/**
|
|
76
|
+
* Find articulation points and bridges of the undirected knowledge graph with
|
|
77
|
+
* an iterative Tarjan low-link search over the simple adjacency. An articulation
|
|
78
|
+
* point is a node whose removal increases the connected-component count; a bridge
|
|
79
|
+
* is an edge whose removal does the same — both are single points of failure in
|
|
80
|
+
* the relationship graph. Results are deterministic and exact.
|
|
81
|
+
*/
|
|
82
|
+
export declare function findRelationshipCutStructure(graph: RelationshipGraph, options?: GraphCutStructureOptions): RelationshipQueryResult<RelationshipCutStructure>;
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="0b226d67-72f3-57f4-8428-d3af70ecf306")}catch(e){}}();
|
|
3
|
+
import { forEachMatchedRelationshipEdge } from "./analytics.js";
|
|
4
|
+
import { orientOrderingEdge } from "./traversal.js";
|
|
5
|
+
/** Round a centrality score to six decimals to suppress floating-point noise. */
|
|
6
|
+
function roundScore(value) {
|
|
7
|
+
return Math.round(value * 1e6) / 1e6;
|
|
8
|
+
}
|
|
9
|
+
/** Add one value to a set-valued map entry, creating the set on demand. */
|
|
10
|
+
function addToSetMap(map, key, value) {
|
|
11
|
+
const set = map.get(key);
|
|
12
|
+
if (set === undefined)
|
|
13
|
+
map.set(key, new Set([value]));
|
|
14
|
+
else
|
|
15
|
+
set.add(value);
|
|
16
|
+
}
|
|
17
|
+
/** Build the deterministic simple undirected adjacency over every node. */
|
|
18
|
+
function buildSimpleUndirectedAdjacency(graph, kinds) {
|
|
19
|
+
const sets = new Map(graph.nodes().map((id) => [id, new Set()]));
|
|
20
|
+
forEachMatchedRelationshipEdge(graph, kinds, (edge) => {
|
|
21
|
+
sets.get(edge.source).add(edge.target);
|
|
22
|
+
sets.get(edge.target).add(edge.source);
|
|
23
|
+
});
|
|
24
|
+
const adjacency = new Map();
|
|
25
|
+
for (const [id, neighbors] of sets)
|
|
26
|
+
adjacency.set(id, [...neighbors].sort());
|
|
27
|
+
return adjacency;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Collect distinct ordering predecessors and successors per node, oriented by
|
|
31
|
+
* each kind's declared precedence so fan-in/fan-out match the SDK's
|
|
32
|
+
* predecessor/successor traversal semantics rather than the stored edge
|
|
33
|
+
* spelling (a `blocked_by` edge counts its target as the predecessor).
|
|
34
|
+
*/
|
|
35
|
+
function collectOrderingDegrees(graph, kinds) {
|
|
36
|
+
const incoming = new Map();
|
|
37
|
+
const outgoing = new Map();
|
|
38
|
+
forEachMatchedRelationshipEdge(graph, kinds, (edge, definition) => {
|
|
39
|
+
if (!definition.ordering)
|
|
40
|
+
return;
|
|
41
|
+
const { predecessor, successor } = orientOrderingEdge(edge, definition);
|
|
42
|
+
// The predecessor gates the successor: predecessor -> successor.
|
|
43
|
+
addToSetMap(outgoing, predecessor, successor);
|
|
44
|
+
addToSetMap(incoming, successor, predecessor);
|
|
45
|
+
});
|
|
46
|
+
return { incoming, outgoing };
|
|
47
|
+
}
|
|
48
|
+
/** Run one breadth-first single-source shortest-path sweep for Brandes accumulation. */
|
|
49
|
+
function brandesShortestPaths(source, adjacency, cost) {
|
|
50
|
+
const distance = new Map([[source, 0]]);
|
|
51
|
+
const sigma = new Map([[source, 1]]);
|
|
52
|
+
const predecessors = new Map();
|
|
53
|
+
const stack = [];
|
|
54
|
+
const queue = [source];
|
|
55
|
+
let reachableCount = 0;
|
|
56
|
+
let distanceSum = 0;
|
|
57
|
+
for (let index = 0; index < queue.length; index += 1) {
|
|
58
|
+
const node = queue[index];
|
|
59
|
+
cost.visitedNodes += 1;
|
|
60
|
+
stack.push(node);
|
|
61
|
+
const nodeDistance = distance.get(node);
|
|
62
|
+
if (node !== source) {
|
|
63
|
+
reachableCount += 1;
|
|
64
|
+
distanceSum += nodeDistance;
|
|
65
|
+
}
|
|
66
|
+
for (const neighbor of adjacency.get(node)) {
|
|
67
|
+
cost.inspectedEdges += 1;
|
|
68
|
+
if (!distance.has(neighbor)) {
|
|
69
|
+
distance.set(neighbor, nodeDistance + 1);
|
|
70
|
+
queue.push(neighbor);
|
|
71
|
+
}
|
|
72
|
+
if (distance.get(neighbor) === nodeDistance + 1) {
|
|
73
|
+
sigma.set(neighbor, (sigma.get(neighbor) ?? 0) + sigma.get(node));
|
|
74
|
+
const predecessorList = predecessors.get(neighbor);
|
|
75
|
+
if (predecessorList === undefined)
|
|
76
|
+
predecessors.set(neighbor, [node]);
|
|
77
|
+
else
|
|
78
|
+
predecessorList.push(node);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return { stack, sigma, predecessors, reachableCount, distanceSum };
|
|
83
|
+
}
|
|
84
|
+
/** Fold one completed sweep's dependencies back into the running betweenness totals. */
|
|
85
|
+
function accumulateBetweenness(sweep, source, betweenness) {
|
|
86
|
+
const dependency = new Map();
|
|
87
|
+
for (let index = sweep.stack.length - 1; index >= 0; index -= 1) {
|
|
88
|
+
const node = sweep.stack[index];
|
|
89
|
+
const share = (1 + (dependency.get(node) ?? 0)) / sweep.sigma.get(node);
|
|
90
|
+
for (const predecessor of sweep.predecessors.get(node) ?? []) {
|
|
91
|
+
dependency.set(predecessor, (dependency.get(predecessor) ?? 0) + sweep.sigma.get(predecessor) * share);
|
|
92
|
+
}
|
|
93
|
+
if (node !== source)
|
|
94
|
+
betweenness.set(node, (betweenness.get(node) ?? 0) + (dependency.get(node) ?? 0));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Wasserman–Faust closeness normalizes reachability so disconnected graphs
|
|
99
|
+
* compare fairly. A reachable count above zero implies a positive distance sum
|
|
100
|
+
* (every shortest distance is at least one), so those two guards suffice.
|
|
101
|
+
*/
|
|
102
|
+
function wassermanFaustCloseness(sweep, nodeCount) {
|
|
103
|
+
if (nodeCount <= 1 || sweep.reachableCount === 0)
|
|
104
|
+
return 0;
|
|
105
|
+
return ((sweep.reachableCount / (nodeCount - 1)) *
|
|
106
|
+
(sweep.reachableCount / sweep.distanceSum));
|
|
107
|
+
}
|
|
108
|
+
/** Assemble one ranked centrality row from the accumulated scores. */
|
|
109
|
+
function centralityRow(id, adjacency, directed, betweenness, closeness) {
|
|
110
|
+
return {
|
|
111
|
+
id,
|
|
112
|
+
degree: adjacency.get(id).length,
|
|
113
|
+
inDegree: directed.incoming.get(id)?.size ?? 0,
|
|
114
|
+
outDegree: directed.outgoing.get(id)?.size ?? 0,
|
|
115
|
+
// Undirected betweenness double-counts each ordered pair; halve it.
|
|
116
|
+
betweenness: roundScore((betweenness.get(id) ?? 0) / 2),
|
|
117
|
+
// Every node is a sweep source, so closeness is always populated.
|
|
118
|
+
closeness: roundScore(closeness.get(id)),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Compute exact shortest-path betweenness (Brandes), Wasserman–Faust closeness,
|
|
123
|
+
* undirected degree, and directed fan-in/fan-out for every node over the simple
|
|
124
|
+
* undirected graph induced by the selected kinds. Rows rank by betweenness, then
|
|
125
|
+
* degree, then id. The result is exact on the bounded workspace; callers bound
|
|
126
|
+
* the returned rows for token-aware projection.
|
|
127
|
+
*/
|
|
128
|
+
export function computeRelationshipCentrality(graph, options = {}) {
|
|
129
|
+
const adjacency = buildSimpleUndirectedAdjacency(graph, options.kinds);
|
|
130
|
+
const directed = collectOrderingDegrees(graph, options.kinds);
|
|
131
|
+
const nodes = graph.nodes();
|
|
132
|
+
const betweenness = new Map();
|
|
133
|
+
const closeness = new Map();
|
|
134
|
+
const cost = { visitedNodes: 0, inspectedEdges: 0 };
|
|
135
|
+
for (const source of nodes) {
|
|
136
|
+
options.signal?.throwIfAborted();
|
|
137
|
+
const sweep = brandesShortestPaths(source, adjacency, cost);
|
|
138
|
+
closeness.set(source, wassermanFaustCloseness(sweep, nodes.length));
|
|
139
|
+
accumulateBetweenness(sweep, source, betweenness);
|
|
140
|
+
}
|
|
141
|
+
let degreeSum = 0;
|
|
142
|
+
for (const neighbors of adjacency.values())
|
|
143
|
+
degreeSum += neighbors.length;
|
|
144
|
+
const rows = nodes
|
|
145
|
+
.map((id) => centralityRow(id, adjacency, directed, betweenness, closeness))
|
|
146
|
+
.sort((left, right) => right.betweenness - left.betweenness ||
|
|
147
|
+
right.degree - left.degree ||
|
|
148
|
+
left.id.localeCompare(right.id));
|
|
149
|
+
return {
|
|
150
|
+
value: { nodeCount: nodes.length, edgeCount: degreeSum / 2, rows },
|
|
151
|
+
meta: {
|
|
152
|
+
visitedNodes: cost.visitedNodes,
|
|
153
|
+
inspectedEdges: cost.inspectedEdges,
|
|
154
|
+
truncated: false,
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/** Order one bridge endpoint pair deterministically. */
|
|
159
|
+
function orderedBridge(left, right) {
|
|
160
|
+
return left <= right
|
|
161
|
+
? { source: left, target: right }
|
|
162
|
+
: { source: right, target: left };
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Advance the top frame by one neighbor: descend into an undiscovered neighbor,
|
|
166
|
+
* or relax the low-link against a back edge. Returns the discovered child frame
|
|
167
|
+
* to push, or undefined when the step consumed a back edge or the tree parent.
|
|
168
|
+
*/
|
|
169
|
+
function advanceCutFrame(frame, adjacency, state) {
|
|
170
|
+
const neighbors = adjacency.get(frame.node);
|
|
171
|
+
const neighbor = neighbors[frame.neighborIndex];
|
|
172
|
+
frame.neighborIndex += 1;
|
|
173
|
+
state.inspectedEdges += 1;
|
|
174
|
+
if (neighbor === frame.parent)
|
|
175
|
+
return undefined;
|
|
176
|
+
if (state.discovery.has(neighbor)) {
|
|
177
|
+
state.low.set(frame.node, Math.min(state.low.get(frame.node), state.discovery.get(neighbor)));
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
frame.children += 1;
|
|
181
|
+
state.discovery.set(neighbor, state.timer);
|
|
182
|
+
state.low.set(neighbor, state.timer);
|
|
183
|
+
state.timer += 1;
|
|
184
|
+
return { node: neighbor, parent: frame.node, neighborIndex: 0, children: 0 };
|
|
185
|
+
}
|
|
186
|
+
/** Retreat from a finished frame: fold its low-link into its parent and flag cuts. */
|
|
187
|
+
function retreatCutFrame(frame, root, state) {
|
|
188
|
+
if (frame.parent === null) {
|
|
189
|
+
if (frame.children > 1)
|
|
190
|
+
state.articulation.add(frame.node);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const parent = frame.parent;
|
|
194
|
+
const childLow = state.low.get(frame.node);
|
|
195
|
+
state.low.set(parent, Math.min(state.low.get(parent), childLow));
|
|
196
|
+
if (parent !== root && childLow >= state.discovery.get(parent))
|
|
197
|
+
state.articulation.add(parent);
|
|
198
|
+
if (childLow > state.discovery.get(parent))
|
|
199
|
+
state.bridges.push(orderedBridge(parent, frame.node));
|
|
200
|
+
}
|
|
201
|
+
/** Walk one connected component's depth-first tree, updating low-links and cuts. */
|
|
202
|
+
function searchComponentCuts(root, adjacency, state, signal) {
|
|
203
|
+
state.discovery.set(root, state.timer);
|
|
204
|
+
state.low.set(root, state.timer);
|
|
205
|
+
state.timer += 1;
|
|
206
|
+
const stack = [
|
|
207
|
+
{ node: root, parent: null, neighborIndex: 0, children: 0 },
|
|
208
|
+
];
|
|
209
|
+
while (stack.length > 0) {
|
|
210
|
+
signal?.throwIfAborted();
|
|
211
|
+
const frame = stack.at(-1);
|
|
212
|
+
if (frame.neighborIndex < adjacency.get(frame.node).length) {
|
|
213
|
+
const child = advanceCutFrame(frame, adjacency, state);
|
|
214
|
+
if (child !== undefined)
|
|
215
|
+
stack.push(child);
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
retreatCutFrame(frame, root, state);
|
|
219
|
+
stack.pop();
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Find articulation points and bridges of the undirected knowledge graph with
|
|
225
|
+
* an iterative Tarjan low-link search over the simple adjacency. An articulation
|
|
226
|
+
* point is a node whose removal increases the connected-component count; a bridge
|
|
227
|
+
* is an edge whose removal does the same — both are single points of failure in
|
|
228
|
+
* the relationship graph. Results are deterministic and exact.
|
|
229
|
+
*/
|
|
230
|
+
export function findRelationshipCutStructure(graph, options = {}) {
|
|
231
|
+
const adjacency = buildSimpleUndirectedAdjacency(graph, options.kinds);
|
|
232
|
+
const state = {
|
|
233
|
+
discovery: new Map(),
|
|
234
|
+
low: new Map(),
|
|
235
|
+
timer: 0,
|
|
236
|
+
articulation: new Set(),
|
|
237
|
+
bridges: [],
|
|
238
|
+
inspectedEdges: 0,
|
|
239
|
+
};
|
|
240
|
+
for (const root of graph.nodes()) {
|
|
241
|
+
if (state.discovery.has(root))
|
|
242
|
+
continue;
|
|
243
|
+
searchComponentCuts(root, adjacency, state, options.signal);
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
value: {
|
|
247
|
+
articulationPoints: [...state.articulation].sort(),
|
|
248
|
+
bridges: state.bridges.sort((left, right) => left.source.localeCompare(right.source) ||
|
|
249
|
+
left.target.localeCompare(right.target)),
|
|
250
|
+
},
|
|
251
|
+
meta: {
|
|
252
|
+
visitedNodes: state.discovery.size,
|
|
253
|
+
inspectedEdges: state.inspectedEdges,
|
|
254
|
+
truncated: false,
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
//# sourceMappingURL=centrality.js.map
|
|
259
|
+
//# debugId=0b226d67-72f3-57f4-8428-d3af70ecf306
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"centrality.js","sources":["sdk/graph/centrality.ts"],"sourceRoot":"/","sourcesContent":["/**\n * @module sdk/graph/centrality\n *\n * Structural-importance and fragility analytics over the undirected knowledge\n * graph: exact shortest-path betweenness (Brandes), Wasserman–Faust closeness,\n * undirected degree, and directed fan-in/fan-out per node, plus articulation\n * points and bridges (single points of failure) via an iterative Tarjan\n * low-link search. Every analysis is deterministic, cancellable, exact on the\n * bounded workspace, and reports the visited-node and inspected-edge cost so\n * agents can budget follow-up reads. Adjacency is the simple undirected graph\n * (parallel edges and self-loops collapsed) so a pair connected by any kind\n * counts once.\n */\nimport {\n RelationshipGraph,\n type RelationshipQueryResult,\n} from \"../relationships.js\";\nimport { forEachMatchedRelationshipEdge } from \"./analytics.js\";\nimport { orientOrderingEdge } from \"./traversal.js\";\n\n/** One node ranked by structural centrality with directed fan-in/fan-out. */\nexport interface RelationshipCentralityRow {\n /** Node id. */\n id: string;\n /** Undirected unique-neighbor degree over selected kinds. */\n degree: number;\n /** Distinct ordering predecessors that gate this node (dependency fan-in). */\n inDegree: number;\n /** Distinct ordering successors this node gates (dependency fan-out). */\n outDegree: number;\n /** Shortest-path betweenness centrality, rounded to six decimals. */\n betweenness: number;\n /** Wasserman–Faust closeness centrality, rounded to six decimals. */\n closeness: number;\n}\n\n/** Bounded centrality-analysis controls. */\nexport interface GraphCentralityOptions {\n /** Registered relationship kinds whose edges define adjacency. */\n kinds?: readonly string[];\n /** Abort signal checked once per source sweep. */\n signal?: AbortSignal;\n}\n\n/** Deterministic centrality ranking over the undirected knowledge graph. */\nexport interface RelationshipCentralityAnalysis {\n /** Total indexed graph nodes. */\n nodeCount: number;\n /** Simple undirected edge count over selected kinds. */\n edgeCount: number;\n /** Node rows ranked by betweenness, then degree, then id. */\n rows: RelationshipCentralityRow[];\n}\n\n/** One undirected bridge edge whose removal disconnects its endpoints. */\nexport interface RelationshipBridge {\n /** Lexicographically smaller endpoint. */\n source: string;\n /** Lexicographically larger endpoint. */\n target: string;\n}\n\n/** Bounded cut-structure controls. */\nexport interface GraphCutStructureOptions {\n /** Registered relationship kinds whose edges define adjacency. */\n kinds?: readonly string[];\n /** Abort signal checked once per depth-first step. */\n signal?: AbortSignal;\n}\n\n/** Articulation points and bridges of the undirected knowledge graph. */\nexport interface RelationshipCutStructure {\n /** Sorted articulation-point ids — nodes whose removal adds components. */\n articulationPoints: string[];\n /** Bridge edges sorted by source then target. */\n bridges: RelationshipBridge[];\n}\n\n/** Round a centrality score to six decimals to suppress floating-point noise. */\nfunction roundScore(value: number): number {\n return Math.round(value * 1e6) / 1e6;\n}\n\n/** Add one value to a set-valued map entry, creating the set on demand. */\nfunction addToSetMap(\n map: Map<string, Set<string>>,\n key: string,\n value: string,\n): void {\n const set = map.get(key);\n if (set === undefined) map.set(key, new Set([value]));\n else set.add(value);\n}\n\n/** Build the deterministic simple undirected adjacency over every node. */\nfunction buildSimpleUndirectedAdjacency(\n graph: RelationshipGraph,\n kinds: readonly string[] | undefined,\n): Map<string, string[]> {\n const sets = new Map(graph.nodes().map((id) => [id, new Set<string>()]));\n forEachMatchedRelationshipEdge(graph, kinds, (edge) => {\n sets.get(edge.source)!.add(edge.target);\n sets.get(edge.target)!.add(edge.source);\n });\n const adjacency = new Map<string, string[]>();\n for (const [id, neighbors] of sets) adjacency.set(id, [...neighbors].sort());\n return adjacency;\n}\n\n/**\n * Collect distinct ordering predecessors and successors per node, oriented by\n * each kind's declared precedence so fan-in/fan-out match the SDK's\n * predecessor/successor traversal semantics rather than the stored edge\n * spelling (a `blocked_by` edge counts its target as the predecessor).\n */\nfunction collectOrderingDegrees(\n graph: RelationshipGraph,\n kinds: readonly string[] | undefined,\n): { incoming: Map<string, Set<string>>; outgoing: Map<string, Set<string>> } {\n const incoming = new Map<string, Set<string>>();\n const outgoing = new Map<string, Set<string>>();\n forEachMatchedRelationshipEdge(graph, kinds, (edge, definition) => {\n if (!definition.ordering) return;\n const { predecessor, successor } = orientOrderingEdge(edge, definition);\n // The predecessor gates the successor: predecessor -> successor.\n addToSetMap(outgoing, predecessor, successor);\n addToSetMap(incoming, successor, predecessor);\n });\n return { incoming, outgoing };\n}\n\n/** Single-source shortest-path state accumulated by one Brandes sweep. */\ninterface BrandesSweep {\n /** Nodes in nondecreasing distance (breadth-first dequeue) order. */\n stack: string[];\n /** Number of shortest paths from the source to each node. */\n sigma: Map<string, number>;\n /** Shortest-path predecessors of each node. */\n predecessors: Map<string, string[]>;\n /** Reachable node count excluding the source. */\n reachableCount: number;\n /** Sum of shortest distances to every reachable node. */\n distanceSum: number;\n}\n\n/** Run one breadth-first single-source shortest-path sweep for Brandes accumulation. */\nfunction brandesShortestPaths(\n source: string,\n adjacency: ReadonlyMap<string, readonly string[]>,\n cost: { visitedNodes: number; inspectedEdges: number },\n): BrandesSweep {\n const distance = new Map<string, number>([[source, 0]]);\n const sigma = new Map<string, number>([[source, 1]]);\n const predecessors = new Map<string, string[]>();\n const stack: string[] = [];\n const queue = [source];\n let reachableCount = 0;\n let distanceSum = 0;\n for (let index = 0; index < queue.length; index += 1) {\n const node = queue[index]!;\n cost.visitedNodes += 1;\n stack.push(node);\n const nodeDistance = distance.get(node)!;\n if (node !== source) {\n reachableCount += 1;\n distanceSum += nodeDistance;\n }\n for (const neighbor of adjacency.get(node)!) {\n cost.inspectedEdges += 1;\n if (!distance.has(neighbor)) {\n distance.set(neighbor, nodeDistance + 1);\n queue.push(neighbor);\n }\n if (distance.get(neighbor) === nodeDistance + 1) {\n sigma.set(neighbor, (sigma.get(neighbor) ?? 0) + sigma.get(node)!);\n const predecessorList = predecessors.get(neighbor);\n if (predecessorList === undefined) predecessors.set(neighbor, [node]);\n else predecessorList.push(node);\n }\n }\n }\n return { stack, sigma, predecessors, reachableCount, distanceSum };\n}\n\n/** Fold one completed sweep's dependencies back into the running betweenness totals. */\nfunction accumulateBetweenness(\n sweep: BrandesSweep,\n source: string,\n betweenness: Map<string, number>,\n): void {\n const dependency = new Map<string, number>();\n for (let index = sweep.stack.length - 1; index >= 0; index -= 1) {\n const node = sweep.stack[index]!;\n const share = (1 + (dependency.get(node) ?? 0)) / sweep.sigma.get(node)!;\n for (const predecessor of sweep.predecessors.get(node) ?? []) {\n dependency.set(\n predecessor,\n (dependency.get(predecessor) ?? 0) + sweep.sigma.get(predecessor)! * share,\n );\n }\n if (node !== source)\n betweenness.set(node, (betweenness.get(node) ?? 0) + (dependency.get(node) ?? 0));\n }\n}\n\n/**\n * Wasserman–Faust closeness normalizes reachability so disconnected graphs\n * compare fairly. A reachable count above zero implies a positive distance sum\n * (every shortest distance is at least one), so those two guards suffice.\n */\nfunction wassermanFaustCloseness(sweep: BrandesSweep, nodeCount: number): number {\n if (nodeCount <= 1 || sweep.reachableCount === 0) return 0;\n return (\n (sweep.reachableCount / (nodeCount - 1)) *\n (sweep.reachableCount / sweep.distanceSum)\n );\n}\n\n/** Assemble one ranked centrality row from the accumulated scores. */\nfunction centralityRow(\n id: string,\n adjacency: ReadonlyMap<string, readonly string[]>,\n directed: ReturnType<typeof collectOrderingDegrees>,\n betweenness: Map<string, number>,\n closeness: Map<string, number>,\n): RelationshipCentralityRow {\n return {\n id,\n degree: adjacency.get(id)!.length,\n inDegree: directed.incoming.get(id)?.size ?? 0,\n outDegree: directed.outgoing.get(id)?.size ?? 0,\n // Undirected betweenness double-counts each ordered pair; halve it.\n betweenness: roundScore((betweenness.get(id) ?? 0) / 2),\n // Every node is a sweep source, so closeness is always populated.\n closeness: roundScore(closeness.get(id)!),\n };\n}\n\n/**\n * Compute exact shortest-path betweenness (Brandes), Wasserman–Faust closeness,\n * undirected degree, and directed fan-in/fan-out for every node over the simple\n * undirected graph induced by the selected kinds. Rows rank by betweenness, then\n * degree, then id. The result is exact on the bounded workspace; callers bound\n * the returned rows for token-aware projection.\n */\nexport function computeRelationshipCentrality(\n graph: RelationshipGraph,\n options: GraphCentralityOptions = {},\n): RelationshipQueryResult<RelationshipCentralityAnalysis> {\n const adjacency = buildSimpleUndirectedAdjacency(graph, options.kinds);\n const directed = collectOrderingDegrees(graph, options.kinds);\n const nodes = graph.nodes();\n const betweenness = new Map<string, number>();\n const closeness = new Map<string, number>();\n const cost = { visitedNodes: 0, inspectedEdges: 0 };\n for (const source of nodes) {\n options.signal?.throwIfAborted();\n const sweep = brandesShortestPaths(source, adjacency, cost);\n closeness.set(source, wassermanFaustCloseness(sweep, nodes.length));\n accumulateBetweenness(sweep, source, betweenness);\n }\n let degreeSum = 0;\n for (const neighbors of adjacency.values()) degreeSum += neighbors.length;\n const rows = nodes\n .map((id) => centralityRow(id, adjacency, directed, betweenness, closeness))\n .sort(\n (left, right) =>\n right.betweenness - left.betweenness ||\n right.degree - left.degree ||\n left.id.localeCompare(right.id),\n );\n return {\n value: { nodeCount: nodes.length, edgeCount: degreeSum / 2, rows },\n meta: {\n visitedNodes: cost.visitedNodes,\n inspectedEdges: cost.inspectedEdges,\n truncated: false,\n },\n };\n}\n\n/** Order one bridge endpoint pair deterministically. */\nfunction orderedBridge(left: string, right: string): RelationshipBridge {\n return left <= right\n ? { source: left, target: right }\n : { source: right, target: left };\n}\n\n/** Mutable depth-first frame for the iterative articulation/bridge search. */\ninterface CutFrame {\n /** Node owned by this frame. */\n node: string;\n /** Depth-first tree parent, or null at a component root. */\n parent: string | null;\n /** Next unexplored neighbor index in the node's adjacency. */\n neighborIndex: number;\n /** Depth-first tree children discovered through this frame. */\n children: number;\n}\n\n/** Shared low-link state threaded through the iterative cut-structure search. */\ninterface CutState {\n /** Discovery index per visited node. */\n discovery: Map<string, number>;\n /** Low-link value per visited node. */\n low: Map<string, number>;\n /** Monotonic discovery counter. */\n timer: number;\n /** Articulation points found so far. */\n articulation: Set<string>;\n /** Bridge edges found so far. */\n bridges: RelationshipBridge[];\n /** Adjacency reads performed. */\n inspectedEdges: number;\n}\n\n/**\n * Advance the top frame by one neighbor: descend into an undiscovered neighbor,\n * or relax the low-link against a back edge. Returns the discovered child frame\n * to push, or undefined when the step consumed a back edge or the tree parent.\n */\nfunction advanceCutFrame(\n frame: CutFrame,\n adjacency: ReadonlyMap<string, readonly string[]>,\n state: CutState,\n): CutFrame | undefined {\n const neighbors = adjacency.get(frame.node)!;\n const neighbor = neighbors[frame.neighborIndex]!;\n frame.neighborIndex += 1;\n state.inspectedEdges += 1;\n if (neighbor === frame.parent) return undefined;\n if (state.discovery.has(neighbor)) {\n state.low.set(\n frame.node,\n Math.min(state.low.get(frame.node)!, state.discovery.get(neighbor)!),\n );\n return undefined;\n }\n frame.children += 1;\n state.discovery.set(neighbor, state.timer);\n state.low.set(neighbor, state.timer);\n state.timer += 1;\n return { node: neighbor, parent: frame.node, neighborIndex: 0, children: 0 };\n}\n\n/** Retreat from a finished frame: fold its low-link into its parent and flag cuts. */\nfunction retreatCutFrame(frame: CutFrame, root: string, state: CutState): void {\n if (frame.parent === null) {\n if (frame.children > 1) state.articulation.add(frame.node);\n return;\n }\n const parent = frame.parent;\n const childLow = state.low.get(frame.node)!;\n state.low.set(parent, Math.min(state.low.get(parent)!, childLow));\n if (parent !== root && childLow >= state.discovery.get(parent)!)\n state.articulation.add(parent);\n if (childLow > state.discovery.get(parent)!)\n state.bridges.push(orderedBridge(parent, frame.node));\n}\n\n/** Walk one connected component's depth-first tree, updating low-links and cuts. */\nfunction searchComponentCuts(\n root: string,\n adjacency: ReadonlyMap<string, readonly string[]>,\n state: CutState,\n signal: AbortSignal | undefined,\n): void {\n state.discovery.set(root, state.timer);\n state.low.set(root, state.timer);\n state.timer += 1;\n const stack: CutFrame[] = [\n { node: root, parent: null, neighborIndex: 0, children: 0 },\n ];\n while (stack.length > 0) {\n signal?.throwIfAborted();\n const frame = stack.at(-1)!;\n if (frame.neighborIndex < adjacency.get(frame.node)!.length) {\n const child = advanceCutFrame(frame, adjacency, state);\n if (child !== undefined) stack.push(child);\n } else {\n retreatCutFrame(frame, root, state);\n stack.pop();\n }\n }\n}\n\n/**\n * Find articulation points and bridges of the undirected knowledge graph with\n * an iterative Tarjan low-link search over the simple adjacency. An articulation\n * point is a node whose removal increases the connected-component count; a bridge\n * is an edge whose removal does the same — both are single points of failure in\n * the relationship graph. Results are deterministic and exact.\n */\nexport function findRelationshipCutStructure(\n graph: RelationshipGraph,\n options: GraphCutStructureOptions = {},\n): RelationshipQueryResult<RelationshipCutStructure> {\n const adjacency = buildSimpleUndirectedAdjacency(graph, options.kinds);\n const state: CutState = {\n discovery: new Map(),\n low: new Map(),\n timer: 0,\n articulation: new Set(),\n bridges: [],\n inspectedEdges: 0,\n };\n for (const root of graph.nodes()) {\n if (state.discovery.has(root)) continue;\n searchComponentCuts(root, adjacency, state, options.signal);\n }\n return {\n value: {\n articulationPoints: [...state.articulation].sort(),\n bridges: state.bridges.sort(\n (left, right) =>\n left.source.localeCompare(right.source) ||\n left.target.localeCompare(right.target),\n ),\n },\n meta: {\n visitedNodes: state.discovery.size,\n inspectedEdges: state.inspectedEdges,\n truncated: false,\n },\n };\n}\n"],"names":[],"mappings":"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","debugId":"0b226d67-72f3-57f4-8428-d3af70ecf306"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { RelationshipAuditSnapshot } from "./governance.js";
|
|
2
|
+
/** Envelope format version; any change invalidates persisted entries. */
|
|
3
|
+
export declare const GRAPH_DURABLE_CACHE_VERSION = 1;
|
|
4
|
+
/** Workspaces below this item count skip implicit persistence. */
|
|
5
|
+
export declare const GRAPH_DURABLE_CACHE_MIN_ITEMS = 500;
|
|
6
|
+
/** Persisted durable graph cache envelope keyed by one workspace fingerprint. */
|
|
7
|
+
export interface DurableGraphCacheEnvelope {
|
|
8
|
+
/** Envelope format version guarding decode compatibility. */
|
|
9
|
+
version: number;
|
|
10
|
+
/** Full workspace graph fingerprint every entry answers for. */
|
|
11
|
+
fingerprint: string;
|
|
12
|
+
/** ISO timestamp of the last persisted write. */
|
|
13
|
+
saved_at: string;
|
|
14
|
+
/** Deterministic query results keyed by their canonical query key. */
|
|
15
|
+
results: Record<string, unknown>;
|
|
16
|
+
}
|
|
17
|
+
/** Compact durable index status projection for observability surfaces. */
|
|
18
|
+
export interface DurableGraphCacheStatus {
|
|
19
|
+
/** Whether an envelope file exists on disk. */
|
|
20
|
+
exists: boolean;
|
|
21
|
+
/** Whether the stored fingerprint matches the current workspace snapshot. */
|
|
22
|
+
fresh: boolean;
|
|
23
|
+
/** Stored entry count, 0 when absent. */
|
|
24
|
+
entry_count: number;
|
|
25
|
+
/** Truncated stored fingerprint, absent when no envelope exists. */
|
|
26
|
+
fingerprint?: string;
|
|
27
|
+
/** ISO timestamp of the last persisted write, absent when no envelope exists. */
|
|
28
|
+
saved_at?: string;
|
|
29
|
+
/** Envelope file size in bytes, absent when no envelope exists. */
|
|
30
|
+
bytes?: number;
|
|
31
|
+
}
|
|
32
|
+
/** Resolve the durable graph cache file path for one workspace root. */
|
|
33
|
+
export declare function durableGraphCachePath(pmRoot: string): string;
|
|
34
|
+
/** Resolve the persisted audit census baseline path for one workspace root. */
|
|
35
|
+
export declare function graphAuditBaselinePath(pmRoot: string): string;
|
|
36
|
+
/** One opened durable index view bound to the current workspace fingerprint. */
|
|
37
|
+
export interface DurableGraphCacheView {
|
|
38
|
+
/** Whether any envelope file exists on disk, fresh or not. */
|
|
39
|
+
exists: boolean;
|
|
40
|
+
/** Whether the stored envelope answers for the current fingerprint. */
|
|
41
|
+
fresh: boolean;
|
|
42
|
+
/** Fingerprint-fresh persisted query results; empty when absent or stale. */
|
|
43
|
+
results: Record<string, unknown>;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Open the persisted envelope against the current workspace fingerprint. A
|
|
47
|
+
* missing, corrupt, version-drifted, or fingerprint-mismatched envelope reads
|
|
48
|
+
* as empty so stale entries can never answer for a mutated workspace.
|
|
49
|
+
*/
|
|
50
|
+
export declare function openDurableGraphCache(pmRoot: string, fingerprint: string): Promise<DurableGraphCacheView>;
|
|
51
|
+
/**
|
|
52
|
+
* Persist one query result into the durable envelope, replacing any envelope
|
|
53
|
+
* recorded for a different fingerprint and trimming the oldest entries beyond
|
|
54
|
+
* the retention bound. A cross-process lock protects the read-merge-write, and
|
|
55
|
+
* the final write is atomic, so concurrent queries retain each other's entries.
|
|
56
|
+
*/
|
|
57
|
+
export declare function persistDurableGraphResult(pmRoot: string, fingerprint: string, view: DurableGraphCacheView, queryKey: string, result: unknown): Promise<void>;
|
|
58
|
+
/** Delete the persisted durable graph cache envelope when present. */
|
|
59
|
+
export declare function clearDurableGraphCache(pmRoot: string): Promise<void>;
|
|
60
|
+
/** Report the durable index status against the current workspace fingerprint. */
|
|
61
|
+
export declare function durableGraphCacheStatus(pmRoot: string, fingerprint: string): Promise<DurableGraphCacheStatus>;
|
|
62
|
+
/**
|
|
63
|
+
* Decide whether implicit persistence applies to this invocation. Small
|
|
64
|
+
* workspaces skip the index unless an envelope already exists — the explicit
|
|
65
|
+
* `pm graph index --rebuild` opt-in creates it and thereby enables writes.
|
|
66
|
+
*/
|
|
67
|
+
export declare function shouldPersistDurableGraphCache(itemCount: number, envelopeExists: boolean): boolean;
|
|
68
|
+
/** Load the persisted audit census baseline when present and decodable. */
|
|
69
|
+
export declare function loadGraphAuditBaseline(pmRoot: string): Promise<RelationshipAuditSnapshot | undefined>;
|
|
70
|
+
/** Atomically persist one audit census snapshot as the comparison baseline. */
|
|
71
|
+
export declare function saveGraphAuditBaseline(pmRoot: string, snapshot: RelationshipAuditSnapshot): Promise<void>;
|