@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
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module sdk/graph/durable-cache
|
|
3
|
+
*
|
|
4
|
+
* Durable, rebuildable graph query index persisted under the workspace
|
|
5
|
+
* `runtime/` cache directory. One-shot CLI invocations lose the in-process
|
|
6
|
+
* {@link WorkspaceGraphCache} memo between commands; this store persists the
|
|
7
|
+
* fingerprint-keyed deterministic query results (and the audit census
|
|
8
|
+
* baseline) across processes so repeated bounded queries on an unchanged
|
|
9
|
+
* workspace are answered without re-running assembly-derived analysis. The
|
|
10
|
+
* index is never authoritative: every entry is rebuildable from item storage,
|
|
11
|
+
* a fingerprint mismatch invalidates the whole envelope, corrupt or
|
|
12
|
+
* version-drifted files are ignored and rewritten, and writes are atomic
|
|
13
|
+
* (temp-file rename) so a crash can never publish a torn index. Persistence
|
|
14
|
+
* is optional for small projects: envelopes are written only above an item
|
|
15
|
+
* threshold or after an explicit `pm graph index --rebuild` opt-in.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
!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]="3c975105-d3de-51f7-939f-1abafd4c3672")}catch(e){}}();
|
|
19
|
+
import path from "node:path";
|
|
20
|
+
import { readFileIfExists, removeFileIfExists, writeFileAtomic, } from "../../core/fs/fs-utils.js";
|
|
21
|
+
import { acquireLock } from "../../core/lock/lock.js";
|
|
22
|
+
/** Envelope format version; any change invalidates persisted entries. */
|
|
23
|
+
export const GRAPH_DURABLE_CACHE_VERSION = 1;
|
|
24
|
+
/** Workspaces below this item count skip implicit persistence. */
|
|
25
|
+
export const GRAPH_DURABLE_CACHE_MIN_ITEMS = 500;
|
|
26
|
+
/** Maximum memoized query results retained in one persisted envelope. */
|
|
27
|
+
const MAX_DURABLE_RESULTS = 64;
|
|
28
|
+
const DURABLE_GRAPH_CACHE_LOCK_ID = "graph-durable-cache";
|
|
29
|
+
/** Serialize cache read-merge-write and clear operations across processes. */
|
|
30
|
+
function acquireDurableGraphCacheLock(pmRoot) {
|
|
31
|
+
return acquireLock(pmRoot, DURABLE_GRAPH_CACHE_LOCK_ID, 30, `graph-cache:${process.pid}`, false, false, 3_000);
|
|
32
|
+
}
|
|
33
|
+
/** Resolve the durable graph cache file path for one workspace root. */
|
|
34
|
+
export function durableGraphCachePath(pmRoot) {
|
|
35
|
+
return path.join(pmRoot, "runtime", "graph-cache.json");
|
|
36
|
+
}
|
|
37
|
+
/** Resolve the persisted audit census baseline path for one workspace root. */
|
|
38
|
+
export function graphAuditBaselinePath(pmRoot) {
|
|
39
|
+
return path.join(pmRoot, "runtime", "graph-audit-baseline.json");
|
|
40
|
+
}
|
|
41
|
+
/** Decode one persisted JSON envelope, returning undefined on any defect. */
|
|
42
|
+
function decodeEnvelope(raw) {
|
|
43
|
+
if (raw === null)
|
|
44
|
+
return undefined;
|
|
45
|
+
try {
|
|
46
|
+
const parsed = JSON.parse(raw);
|
|
47
|
+
if (typeof parsed !== "object" ||
|
|
48
|
+
parsed === null ||
|
|
49
|
+
parsed.version !==
|
|
50
|
+
GRAPH_DURABLE_CACHE_VERSION ||
|
|
51
|
+
typeof parsed.fingerprint !== "string" ||
|
|
52
|
+
typeof parsed.saved_at !== "string" ||
|
|
53
|
+
typeof parsed.results !== "object" ||
|
|
54
|
+
parsed.results === null ||
|
|
55
|
+
Array.isArray(parsed.results))
|
|
56
|
+
return undefined;
|
|
57
|
+
return parsed;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// Corrupt or torn writes must never break queries; the index rebuilds.
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Open the persisted envelope against the current workspace fingerprint. A
|
|
66
|
+
* missing, corrupt, version-drifted, or fingerprint-mismatched envelope reads
|
|
67
|
+
* as empty so stale entries can never answer for a mutated workspace.
|
|
68
|
+
*/
|
|
69
|
+
export async function openDurableGraphCache(pmRoot, fingerprint) {
|
|
70
|
+
const raw = await readFileIfExists(durableGraphCachePath(pmRoot));
|
|
71
|
+
const envelope = decodeEnvelope(raw);
|
|
72
|
+
const fresh = envelope?.fingerprint === fingerprint;
|
|
73
|
+
return {
|
|
74
|
+
exists: raw !== null,
|
|
75
|
+
fresh,
|
|
76
|
+
results: fresh ? envelope.results : {},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Persist one query result into the durable envelope, replacing any envelope
|
|
81
|
+
* recorded for a different fingerprint and trimming the oldest entries beyond
|
|
82
|
+
* the retention bound. A cross-process lock protects the read-merge-write, and
|
|
83
|
+
* the final write is atomic, so concurrent queries retain each other's entries.
|
|
84
|
+
*/
|
|
85
|
+
export async function persistDurableGraphResult(pmRoot, fingerprint, view, queryKey, result) {
|
|
86
|
+
const release = await acquireDurableGraphCacheLock(pmRoot);
|
|
87
|
+
try {
|
|
88
|
+
const envelope = decodeEnvelope(await readFileIfExists(durableGraphCachePath(pmRoot)));
|
|
89
|
+
const results = envelope?.fingerprint === fingerprint ? envelope.results : {};
|
|
90
|
+
delete results[queryKey];
|
|
91
|
+
results[queryKey] = result;
|
|
92
|
+
const keys = Object.keys(results);
|
|
93
|
+
for (const stale of keys.slice(0, Math.max(0, keys.length - MAX_DURABLE_RESULTS)))
|
|
94
|
+
delete results[stale];
|
|
95
|
+
await writeFileAtomic(durableGraphCachePath(pmRoot), JSON.stringify({
|
|
96
|
+
version: GRAPH_DURABLE_CACHE_VERSION,
|
|
97
|
+
fingerprint,
|
|
98
|
+
saved_at: new Date().toISOString(),
|
|
99
|
+
results,
|
|
100
|
+
}));
|
|
101
|
+
view.results = results;
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
await release();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/** Delete the persisted durable graph cache envelope when present. */
|
|
108
|
+
export async function clearDurableGraphCache(pmRoot) {
|
|
109
|
+
const release = await acquireDurableGraphCacheLock(pmRoot);
|
|
110
|
+
try {
|
|
111
|
+
await removeFileIfExists(durableGraphCachePath(pmRoot));
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
await release();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/** Report the durable index status against the current workspace fingerprint. */
|
|
118
|
+
export async function durableGraphCacheStatus(pmRoot, fingerprint) {
|
|
119
|
+
const cachePath = durableGraphCachePath(pmRoot);
|
|
120
|
+
const raw = await readFileIfExists(cachePath);
|
|
121
|
+
const envelope = decodeEnvelope(raw);
|
|
122
|
+
if (envelope === undefined)
|
|
123
|
+
return { exists: raw !== null, fresh: false, entry_count: 0 };
|
|
124
|
+
return {
|
|
125
|
+
exists: true,
|
|
126
|
+
fresh: envelope.fingerprint === fingerprint,
|
|
127
|
+
entry_count: Object.keys(envelope.results).length,
|
|
128
|
+
fingerprint: envelope.fingerprint.slice(0, 12),
|
|
129
|
+
saved_at: envelope.saved_at,
|
|
130
|
+
bytes: Buffer.byteLength(raw, "utf8"),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Decide whether implicit persistence applies to this invocation. Small
|
|
135
|
+
* workspaces skip the index unless an envelope already exists — the explicit
|
|
136
|
+
* `pm graph index --rebuild` opt-in creates it and thereby enables writes.
|
|
137
|
+
*/
|
|
138
|
+
export function shouldPersistDurableGraphCache(itemCount, envelopeExists) {
|
|
139
|
+
return envelopeExists || itemCount >= GRAPH_DURABLE_CACHE_MIN_ITEMS;
|
|
140
|
+
}
|
|
141
|
+
/** Return whether a decoded value is a string-keyed non-negative integer count map. */
|
|
142
|
+
function isNonnegativeCountRecord(value) {
|
|
143
|
+
return (typeof value === "object" &&
|
|
144
|
+
value !== null &&
|
|
145
|
+
!Array.isArray(value) &&
|
|
146
|
+
Object.values(value).every((count) => Number.isInteger(count) && count >= 0));
|
|
147
|
+
}
|
|
148
|
+
/** Return whether every decoded item-type coverage entry contains valid counts. */
|
|
149
|
+
function isNonnegativeCoverageByTypeRecord(value) {
|
|
150
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
151
|
+
return false;
|
|
152
|
+
return Object.values(value).every((entry) => typeof entry === "object" &&
|
|
153
|
+
entry !== null &&
|
|
154
|
+
!Array.isArray(entry) &&
|
|
155
|
+
[entry.active, entry.isolated, entry.degree_leq_one].every((count) => Number.isInteger(count) && count >= 0));
|
|
156
|
+
}
|
|
157
|
+
/** Decode one persisted audit census baseline, undefined on any defect. */
|
|
158
|
+
function decodeBaseline(raw) {
|
|
159
|
+
if (raw === null)
|
|
160
|
+
return undefined;
|
|
161
|
+
try {
|
|
162
|
+
const parsed = JSON.parse(raw);
|
|
163
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
164
|
+
return undefined;
|
|
165
|
+
const snapshot = parsed;
|
|
166
|
+
const profile = snapshot.profile;
|
|
167
|
+
if (typeof snapshot.saved_at !== "string" ||
|
|
168
|
+
typeof snapshot.fingerprint !== "string" ||
|
|
169
|
+
!isNonnegativeCountRecord(snapshot.affected_subjects_by_code) ||
|
|
170
|
+
typeof profile !== "object" ||
|
|
171
|
+
profile === null ||
|
|
172
|
+
![
|
|
173
|
+
profile.nodes,
|
|
174
|
+
profile.edges,
|
|
175
|
+
profile.active_nodes,
|
|
176
|
+
profile.missing_nodes,
|
|
177
|
+
profile.isolated_active_nodes,
|
|
178
|
+
profile.degree_leq_one_active_nodes,
|
|
179
|
+
].every((value) => Number.isInteger(value) && value >= 0) ||
|
|
180
|
+
!isNonnegativeCountRecord(profile.edges_by_kind) ||
|
|
181
|
+
(profile.coverage_by_type !== undefined &&
|
|
182
|
+
!isNonnegativeCoverageByTypeRecord(profile.coverage_by_type)))
|
|
183
|
+
return undefined;
|
|
184
|
+
return {
|
|
185
|
+
...snapshot,
|
|
186
|
+
profile: {
|
|
187
|
+
...profile,
|
|
188
|
+
coverage_by_type: profile.coverage_by_type ?? {},
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
return undefined;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/** Load the persisted audit census baseline when present and decodable. */
|
|
197
|
+
export async function loadGraphAuditBaseline(pmRoot) {
|
|
198
|
+
return decodeBaseline(await readFileIfExists(graphAuditBaselinePath(pmRoot)));
|
|
199
|
+
}
|
|
200
|
+
/** Atomically persist one audit census snapshot as the comparison baseline. */
|
|
201
|
+
export async function saveGraphAuditBaseline(pmRoot, snapshot) {
|
|
202
|
+
await writeFileAtomic(graphAuditBaselinePath(pmRoot), JSON.stringify(snapshot));
|
|
203
|
+
}
|
|
204
|
+
//# sourceMappingURL=durable-cache.js.map
|
|
205
|
+
//# debugId=3c975105-d3de-51f7-939f-1abafd4c3672
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"durable-cache.js","sources":["sdk/graph/durable-cache.ts"],"sourceRoot":"/","sourcesContent":["/**\n * @module sdk/graph/durable-cache\n *\n * Durable, rebuildable graph query index persisted under the workspace\n * `runtime/` cache directory. One-shot CLI invocations lose the in-process\n * {@link WorkspaceGraphCache} memo between commands; this store persists the\n * fingerprint-keyed deterministic query results (and the audit census\n * baseline) across processes so repeated bounded queries on an unchanged\n * workspace are answered without re-running assembly-derived analysis. The\n * index is never authoritative: every entry is rebuildable from item storage,\n * a fingerprint mismatch invalidates the whole envelope, corrupt or\n * version-drifted files are ignored and rewritten, and writes are atomic\n * (temp-file rename) so a crash can never publish a torn index. Persistence\n * is optional for small projects: envelopes are written only above an item\n * threshold or after an explicit `pm graph index --rebuild` opt-in.\n */\nimport path from \"node:path\";\nimport {\n readFileIfExists,\n removeFileIfExists,\n writeFileAtomic,\n} from \"../../core/fs/fs-utils.js\";\nimport { acquireLock } from \"../../core/lock/lock.js\";\nimport type { RelationshipAuditSnapshot } from \"./governance.js\";\n\n/** Envelope format version; any change invalidates persisted entries. */\nexport const GRAPH_DURABLE_CACHE_VERSION = 1;\n\n/** Workspaces below this item count skip implicit persistence. */\nexport const GRAPH_DURABLE_CACHE_MIN_ITEMS = 500;\n\n/** Maximum memoized query results retained in one persisted envelope. */\nconst MAX_DURABLE_RESULTS = 64;\nconst DURABLE_GRAPH_CACHE_LOCK_ID = \"graph-durable-cache\";\n\n/** Serialize cache read-merge-write and clear operations across processes. */\nfunction acquireDurableGraphCacheLock(\n pmRoot: string,\n): Promise<() => Promise<void>> {\n return acquireLock(\n pmRoot,\n DURABLE_GRAPH_CACHE_LOCK_ID,\n 30,\n `graph-cache:${process.pid}`,\n false,\n false,\n 3_000,\n );\n}\n\n/** Persisted durable graph cache envelope keyed by one workspace fingerprint. */\nexport interface DurableGraphCacheEnvelope {\n /** Envelope format version guarding decode compatibility. */\n version: number;\n /** Full workspace graph fingerprint every entry answers for. */\n fingerprint: string;\n /** ISO timestamp of the last persisted write. */\n saved_at: string;\n /** Deterministic query results keyed by their canonical query key. */\n results: Record<string, unknown>;\n}\n\n/** Compact durable index status projection for observability surfaces. */\nexport interface DurableGraphCacheStatus {\n /** Whether an envelope file exists on disk. */\n exists: boolean;\n /** Whether the stored fingerprint matches the current workspace snapshot. */\n fresh: boolean;\n /** Stored entry count, 0 when absent. */\n entry_count: number;\n /** Truncated stored fingerprint, absent when no envelope exists. */\n fingerprint?: string;\n /** ISO timestamp of the last persisted write, absent when no envelope exists. */\n saved_at?: string;\n /** Envelope file size in bytes, absent when no envelope exists. */\n bytes?: number;\n}\n\n/** Resolve the durable graph cache file path for one workspace root. */\nexport function durableGraphCachePath(pmRoot: string): string {\n return path.join(pmRoot, \"runtime\", \"graph-cache.json\");\n}\n\n/** Resolve the persisted audit census baseline path for one workspace root. */\nexport function graphAuditBaselinePath(pmRoot: string): string {\n return path.join(pmRoot, \"runtime\", \"graph-audit-baseline.json\");\n}\n\n/** Decode one persisted JSON envelope, returning undefined on any defect. */\nfunction decodeEnvelope(\n raw: string | null,\n): DurableGraphCacheEnvelope | undefined {\n if (raw === null) return undefined;\n try {\n const parsed: unknown = JSON.parse(raw);\n if (\n typeof parsed !== \"object\" ||\n parsed === null ||\n (parsed as DurableGraphCacheEnvelope).version !==\n GRAPH_DURABLE_CACHE_VERSION ||\n typeof (parsed as DurableGraphCacheEnvelope).fingerprint !== \"string\" ||\n typeof (parsed as DurableGraphCacheEnvelope).saved_at !== \"string\" ||\n typeof (parsed as DurableGraphCacheEnvelope).results !== \"object\" ||\n (parsed as DurableGraphCacheEnvelope).results === null ||\n Array.isArray((parsed as DurableGraphCacheEnvelope).results)\n )\n return undefined;\n return parsed as DurableGraphCacheEnvelope;\n } catch {\n // Corrupt or torn writes must never break queries; the index rebuilds.\n return undefined;\n }\n}\n\n/** One opened durable index view bound to the current workspace fingerprint. */\nexport interface DurableGraphCacheView {\n /** Whether any envelope file exists on disk, fresh or not. */\n exists: boolean;\n /** Whether the stored envelope answers for the current fingerprint. */\n fresh: boolean;\n /** Fingerprint-fresh persisted query results; empty when absent or stale. */\n results: Record<string, unknown>;\n}\n\n/**\n * Open the persisted envelope against the current workspace fingerprint. A\n * missing, corrupt, version-drifted, or fingerprint-mismatched envelope reads\n * as empty so stale entries can never answer for a mutated workspace.\n */\nexport async function openDurableGraphCache(\n pmRoot: string,\n fingerprint: string,\n): Promise<DurableGraphCacheView> {\n const raw = await readFileIfExists(durableGraphCachePath(pmRoot));\n const envelope = decodeEnvelope(raw);\n const fresh = envelope?.fingerprint === fingerprint;\n return {\n exists: raw !== null,\n fresh,\n results: fresh ? envelope!.results : {},\n };\n}\n\n/**\n * Persist one query result into the durable envelope, replacing any envelope\n * recorded for a different fingerprint and trimming the oldest entries beyond\n * the retention bound. A cross-process lock protects the read-merge-write, and\n * the final write is atomic, so concurrent queries retain each other's entries.\n */\nexport async function persistDurableGraphResult(\n pmRoot: string,\n fingerprint: string,\n view: DurableGraphCacheView,\n queryKey: string,\n result: unknown,\n): Promise<void> {\n const release = await acquireDurableGraphCacheLock(pmRoot);\n try {\n const envelope = decodeEnvelope(\n await readFileIfExists(durableGraphCachePath(pmRoot)),\n );\n const results =\n envelope?.fingerprint === fingerprint ? envelope.results : {};\n delete results[queryKey];\n results[queryKey] = result;\n const keys = Object.keys(results);\n for (const stale of keys.slice(\n 0,\n Math.max(0, keys.length - MAX_DURABLE_RESULTS),\n ))\n delete results[stale];\n await writeFileAtomic(\n durableGraphCachePath(pmRoot),\n JSON.stringify({\n version: GRAPH_DURABLE_CACHE_VERSION,\n fingerprint,\n saved_at: new Date().toISOString(),\n results,\n }),\n );\n view.results = results;\n } finally {\n await release();\n }\n}\n\n/** Delete the persisted durable graph cache envelope when present. */\nexport async function clearDurableGraphCache(pmRoot: string): Promise<void> {\n const release = await acquireDurableGraphCacheLock(pmRoot);\n try {\n await removeFileIfExists(durableGraphCachePath(pmRoot));\n } finally {\n await release();\n }\n}\n\n/** Report the durable index status against the current workspace fingerprint. */\nexport async function durableGraphCacheStatus(\n pmRoot: string,\n fingerprint: string,\n): Promise<DurableGraphCacheStatus> {\n const cachePath = durableGraphCachePath(pmRoot);\n const raw = await readFileIfExists(cachePath);\n const envelope = decodeEnvelope(raw);\n if (envelope === undefined)\n return { exists: raw !== null, fresh: false, entry_count: 0 };\n return {\n exists: true,\n fresh: envelope.fingerprint === fingerprint,\n entry_count: Object.keys(envelope.results).length,\n fingerprint: envelope.fingerprint.slice(0, 12),\n saved_at: envelope.saved_at,\n bytes: Buffer.byteLength(raw!, \"utf8\"),\n };\n}\n\n/**\n * Decide whether implicit persistence applies to this invocation. Small\n * workspaces skip the index unless an envelope already exists — the explicit\n * `pm graph index --rebuild` opt-in creates it and thereby enables writes.\n */\nexport function shouldPersistDurableGraphCache(\n itemCount: number,\n envelopeExists: boolean,\n): boolean {\n return envelopeExists || itemCount >= GRAPH_DURABLE_CACHE_MIN_ITEMS;\n}\n\n/** Return whether a decoded value is a string-keyed non-negative integer count map. */\nfunction isNonnegativeCountRecord(\n value: unknown,\n): value is Record<string, number> {\n return (\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value) &&\n Object.values(value).every((count) => Number.isInteger(count) && count >= 0)\n );\n}\n\n/** Return whether every decoded item-type coverage entry contains valid counts. */\nfunction isNonnegativeCoverageByTypeRecord(value: unknown): boolean {\n if (typeof value !== \"object\" || value === null || Array.isArray(value))\n return false;\n return Object.values(value).every(\n (entry) =>\n typeof entry === \"object\" &&\n entry !== null &&\n !Array.isArray(entry) &&\n [entry.active, entry.isolated, entry.degree_leq_one].every(\n (count) => Number.isInteger(count) && count >= 0,\n ),\n );\n}\n\n/** Decode one persisted audit census baseline, undefined on any defect. */\nfunction decodeBaseline(\n raw: string | null,\n): RelationshipAuditSnapshot | undefined {\n if (raw === null) return undefined;\n try {\n const parsed: unknown = JSON.parse(raw);\n if (typeof parsed !== \"object\" || parsed === null) return undefined;\n const snapshot = parsed as Partial<RelationshipAuditSnapshot>;\n const profile = snapshot.profile;\n if (\n typeof snapshot.saved_at !== \"string\" ||\n typeof snapshot.fingerprint !== \"string\" ||\n !isNonnegativeCountRecord(snapshot.affected_subjects_by_code) ||\n typeof profile !== \"object\" ||\n profile === null ||\n ![\n profile.nodes,\n profile.edges,\n profile.active_nodes,\n profile.missing_nodes,\n profile.isolated_active_nodes,\n profile.degree_leq_one_active_nodes,\n ].every((value) => Number.isInteger(value) && value >= 0) ||\n !isNonnegativeCountRecord(profile.edges_by_kind) ||\n (profile.coverage_by_type !== undefined &&\n !isNonnegativeCoverageByTypeRecord(profile.coverage_by_type))\n )\n return undefined;\n return {\n ...snapshot,\n profile: {\n ...profile,\n coverage_by_type: profile.coverage_by_type ?? {},\n },\n } as RelationshipAuditSnapshot;\n } catch {\n return undefined;\n }\n}\n\n/** Load the persisted audit census baseline when present and decodable. */\nexport async function loadGraphAuditBaseline(\n pmRoot: string,\n): Promise<RelationshipAuditSnapshot | undefined> {\n return decodeBaseline(await readFileIfExists(graphAuditBaselinePath(pmRoot)));\n}\n\n/** Atomically persist one audit census snapshot as the comparison baseline. */\nexport async function saveGraphAuditBaseline(\n pmRoot: string,\n snapshot: RelationshipAuditSnapshot,\n): Promise<void> {\n await writeFileAtomic(\n graphAuditBaselinePath(pmRoot),\n JSON.stringify(snapshot),\n );\n}\n"],"names":[],"mappings":"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","debugId":"3c975105-d3de-51f7-939f-1abafd4c3672"}
|
|
@@ -17,7 +17,7 @@ import type { WorkspaceRelationshipAssembly } from "./assembly.js";
|
|
|
17
17
|
/** Severity ladder shared by every relationship audit finding. */
|
|
18
18
|
export type RelationshipAuditSeverity = "error" | "warning" | "info";
|
|
19
19
|
/** Machine-readable identifier for one relationship audit finding family. */
|
|
20
|
-
export type RelationshipAuditFindingCode = "missing_reference_active" | "missing_reference_terminal" | "legacy_no_blocker_sentinel" | "ordering_cycle" | "legacy_ordering_cycle" | "duplicate_edge" | "legacy_duplicate_edge" | "stale_lifecycle_block" | "isolated_active_node" | "sparse_active_node";
|
|
20
|
+
export type RelationshipAuditFindingCode = "missing_reference_active" | "missing_reference_terminal" | "legacy_no_blocker_sentinel" | "ordering_cycle" | "legacy_ordering_cycle" | "duplicate_edge" | "legacy_duplicate_edge" | "duplicate_dependency_row" | "legacy_duplicate_dependency_row" | "stale_lifecycle_block" | "isolated_active_node" | "sparse_active_node";
|
|
21
21
|
/** One policy-aware relationship audit finding with bounded evidence. */
|
|
22
22
|
export interface RelationshipAuditFinding {
|
|
23
23
|
/** Machine-readable finding family identifier. */
|
|
@@ -37,6 +37,15 @@ export interface RelationshipAuditFinding {
|
|
|
37
37
|
/** Safe machine-actionable next step; audits never invent or auto-apply edges. */
|
|
38
38
|
remediation: string;
|
|
39
39
|
}
|
|
40
|
+
/** Per-item-type connectivity coverage inside a coverage profile. */
|
|
41
|
+
export interface RelationshipCoverageTypeProfile {
|
|
42
|
+
/** Active nodes declaring this item type. */
|
|
43
|
+
active: number;
|
|
44
|
+
/** Active nodes of this type with no incident edges. */
|
|
45
|
+
isolated: number;
|
|
46
|
+
/** Active nodes of this type with at most one incident edge, isolates included. */
|
|
47
|
+
degree_leq_one: number;
|
|
48
|
+
}
|
|
40
49
|
/** Aggregate structural coverage metrics for one assembled workspace graph. */
|
|
41
50
|
export interface RelationshipCoverageProfile {
|
|
42
51
|
/** Total indexed nodes including materialized missing placeholders. */
|
|
@@ -53,6 +62,8 @@ export interface RelationshipCoverageProfile {
|
|
|
53
62
|
isolated_active_nodes: number;
|
|
54
63
|
/** Active nodes with at most one incident edge, isolates included. */
|
|
55
64
|
degree_leq_one_active_nodes: number;
|
|
65
|
+
/** Deterministic per-type connectivity coverage; untyped nodes bucket under `(untyped)`. */
|
|
66
|
+
coverage_by_type: Record<string, RelationshipCoverageTypeProfile>;
|
|
56
67
|
}
|
|
57
68
|
/** Tuning and policy inputs accepted by the relationship audit. */
|
|
58
69
|
export interface RelationshipAuditOptions {
|
|
@@ -62,6 +73,8 @@ export interface RelationshipAuditOptions {
|
|
|
62
73
|
isBlocked?: (status: string) => boolean;
|
|
63
74
|
/** Node identifiers that are explicitly valid isolates (roots, archives, scratch work). */
|
|
64
75
|
exemptIsolates?: readonly string[];
|
|
76
|
+
/** Item types whose active nodes are policy-valid isolates or sparse nodes (case-insensitive); they stay in the coverage profile but raise no coverage finding. */
|
|
77
|
+
isolateExemptTypes?: readonly string[];
|
|
65
78
|
/** Maximum sample entries retained per finding; defaults to 25. */
|
|
66
79
|
maxSampleSize?: number;
|
|
67
80
|
/** Abort signal checked between finding families. */
|
|
@@ -74,6 +87,15 @@ export interface RelationshipAuditReport {
|
|
|
74
87
|
/** Structural coverage metrics computed during the audit. */
|
|
75
88
|
profile: RelationshipCoverageProfile;
|
|
76
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Enumerate strongly connected components with more than one member (or a
|
|
92
|
+
* self-loop) over an ordering-only successor digraph using an iterative Tarjan
|
|
93
|
+
* traversal. Only order-bearing kinds should participate: associative kinds
|
|
94
|
+
* such as `related` cannot create execution-order contradictions and stay out
|
|
95
|
+
* of cycle analysis by policy. Exported so incremental consumers (mutation
|
|
96
|
+
* advisories) share one cycle semantics with the workspace audit.
|
|
97
|
+
*/
|
|
98
|
+
export declare function collectOrderingCycles(successors: Map<string, string[]>): string[][];
|
|
77
99
|
/** One duplicate-edge group in semantic orientation with its stored spellings. */
|
|
78
100
|
export interface DuplicateRelationshipEdgeGroup {
|
|
79
101
|
/** Semantic tail node shared by every stored spelling. */
|
|
@@ -106,3 +128,49 @@ export declare function collectDuplicateRelationshipEdgeGroups(assembly: Workspa
|
|
|
106
128
|
* members so callers can decide which edge to retype, remove, or waive.
|
|
107
129
|
*/
|
|
108
130
|
export declare function auditWorkspaceRelationshipGraph(assembly: WorkspaceRelationshipAssembly, options?: RelationshipAuditOptions): RelationshipAuditReport;
|
|
131
|
+
/** One persisted point-in-time audit census used for temporal comparison. */
|
|
132
|
+
export interface RelationshipAuditSnapshot {
|
|
133
|
+
/** ISO timestamp the snapshot was taken at. */
|
|
134
|
+
saved_at: string;
|
|
135
|
+
/** Workspace graph fingerprint of the audited item snapshot. */
|
|
136
|
+
fingerprint: string;
|
|
137
|
+
/** Affected-subject counts keyed by finding code at snapshot time. */
|
|
138
|
+
affected_subjects_by_code: Record<string, number>;
|
|
139
|
+
/** Structural coverage profile at snapshot time. */
|
|
140
|
+
profile: RelationshipCoverageProfile;
|
|
141
|
+
}
|
|
142
|
+
/** Signed change between two audit snapshots; zero-change keys are dropped. */
|
|
143
|
+
export interface RelationshipAuditDelta {
|
|
144
|
+
/** ISO timestamp of the baseline snapshot the delta is measured against. */
|
|
145
|
+
baseline_saved_at: string;
|
|
146
|
+
/** Whether both snapshots audited the identical workspace fingerprint. */
|
|
147
|
+
same_snapshot: boolean;
|
|
148
|
+
/** Signed affected-subject deltas keyed by finding code. */
|
|
149
|
+
affected_subjects_by_code: Record<string, number>;
|
|
150
|
+
/** Signed structural profile deltas. */
|
|
151
|
+
profile: {
|
|
152
|
+
/** Node-count change including missing placeholders. */
|
|
153
|
+
nodes: number;
|
|
154
|
+
/** Deduplicated edge-count change. */
|
|
155
|
+
edges: number;
|
|
156
|
+
/** Active-node-count change. */
|
|
157
|
+
active_nodes: number;
|
|
158
|
+
/** Missing-placeholder-count change. */
|
|
159
|
+
missing_nodes: number;
|
|
160
|
+
/** Isolated-active-node-count change. */
|
|
161
|
+
isolated_active_nodes: number;
|
|
162
|
+
/** Degree-at-most-one active-node-count change. */
|
|
163
|
+
degree_leq_one_active_nodes: number;
|
|
164
|
+
/** Signed per-kind edge-count deltas; unchanged kinds are dropped. */
|
|
165
|
+
edges_by_kind: Record<string, number>;
|
|
166
|
+
/** Signed per-type coverage deltas; unchanged types are dropped. */
|
|
167
|
+
coverage_by_type: Record<string, RelationshipCoverageTypeProfile>;
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Compare two point-in-time audit snapshots and return the signed change in
|
|
172
|
+
* findings and structural coverage. This is the temporal primitive behind
|
|
173
|
+
* change-since-baseline audit reporting: agents track census drift across
|
|
174
|
+
* sessions without re-deriving or storing full finding collections.
|
|
175
|
+
*/
|
|
176
|
+
export declare function diffRelationshipAuditSnapshots(baseline: RelationshipAuditSnapshot, current: RelationshipAuditSnapshot): RelationshipAuditDelta;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
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]="
|
|
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]="4b36a9d4-2621-5560-831f-7fd026072cf1")}catch(e){}}();
|
|
3
3
|
import { isTransitiveKind, orientTransitiveEdge, transitiveFamilyKey, } from "./analytics.js";
|
|
4
4
|
/** Default bounded sample size applied to every finding family. */
|
|
5
5
|
const DEFAULT_MAX_SAMPLE_SIZE = 25;
|
|
@@ -93,12 +93,14 @@ function completeTarjanFrame(frame, frames, successors, state) {
|
|
|
93
93
|
state.components.push(component.sort((left, right) => left.localeCompare(right)));
|
|
94
94
|
}
|
|
95
95
|
/**
|
|
96
|
-
* Enumerate strongly connected components with more than one member
|
|
97
|
-
* ordering-only digraph using an iterative Tarjan
|
|
98
|
-
* kinds participate: associative kinds
|
|
99
|
-
* execution-order contradictions and stay out
|
|
96
|
+
* Enumerate strongly connected components with more than one member (or a
|
|
97
|
+
* self-loop) over an ordering-only successor digraph using an iterative Tarjan
|
|
98
|
+
* traversal. Only order-bearing kinds should participate: associative kinds
|
|
99
|
+
* such as `related` cannot create execution-order contradictions and stay out
|
|
100
|
+
* of cycle analysis by policy. Exported so incremental consumers (mutation
|
|
101
|
+
* advisories) share one cycle semantics with the workspace audit.
|
|
100
102
|
*/
|
|
101
|
-
function collectOrderingCycles(successors) {
|
|
103
|
+
export function collectOrderingCycles(successors) {
|
|
102
104
|
const state = {
|
|
103
105
|
indexes: new Map(),
|
|
104
106
|
lowLinks: new Map(),
|
|
@@ -258,39 +260,75 @@ function collectDuplicateEdgeFindings(assembly, nodeStates, maxSampleSize) {
|
|
|
258
260
|
appendFinding(findings, legacy, maxSampleSize, "legacy_duplicate_edge", "info", "Duplicated spellings between exclusively terminal items are historical noise with no scheduling effect.", (count) => `${count} duplicated relationship(s) between terminal endpoints`, "Leave as history debt or clean up in a dedicated changelog-safe closed-item batch.");
|
|
259
261
|
return findings;
|
|
260
262
|
}
|
|
261
|
-
/**
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
263
|
+
/**
|
|
264
|
+
* Collect storage-integrity findings for raw dependency rows whose exact
|
|
265
|
+
* identity is stored more than once on one holder. Graph edge-identity dedup
|
|
266
|
+
* hides these from every assembled-graph surface (duplicate_edge only covers
|
|
267
|
+
* cross-spelling parallels), so the audit reports them from the pre-assembly
|
|
268
|
+
* scan carried on the assembly.
|
|
269
|
+
*/
|
|
270
|
+
function collectDuplicateRowFindings(assembly, maxSampleSize) {
|
|
271
|
+
const active = [];
|
|
272
|
+
const legacy = [];
|
|
273
|
+
// Public/JSON-parsed assemblies produced before duplicate-row governance may omit this field.
|
|
274
|
+
for (const row of assembly.duplicateRows ?? []) {
|
|
275
|
+
(row.legacy_terminal ? legacy : active).push(`${row.holder_id} -> ${row.target_id} (${row.kind} x${row.occurrences})`);
|
|
276
|
+
}
|
|
277
|
+
const findings = [];
|
|
278
|
+
appendFinding(findings, active, maxSampleSize, "duplicate_dependency_row", "warning", "One dependency row must be stored once per holder; identical repeated rows are a storage-layer defect invisible to the assembled graph.", (count) => `${count} exactly duplicated stored dependency row(s) on active holder(s)`, "Remove the repeated rows so the dependency is stored exactly once; graph semantics are unchanged.");
|
|
279
|
+
appendFinding(findings, legacy, maxSampleSize, "legacy_duplicate_dependency_row", "info", "Identical repeated dependency rows on terminal holders are historical storage noise with no scheduling effect.", (count) => `${count} exactly duplicated stored dependency row(s) on terminal holder(s)`, "Leave as history debt or clean up in a dedicated changelog-safe closed-item batch.");
|
|
280
|
+
return findings;
|
|
281
|
+
}
|
|
282
|
+
/** Fold one active node's degree into the aggregate and per-type coverage tallies. */
|
|
283
|
+
function tallyActiveCoverageNode(tallies, state, degree, exemptIsolates, isolateExemptTypes) {
|
|
284
|
+
tallies.active += 1;
|
|
285
|
+
const typeKey = state.type ?? "(untyped)";
|
|
286
|
+
let typeProfile = tallies.byType.get(typeKey);
|
|
287
|
+
if (!typeProfile) {
|
|
288
|
+
typeProfile = { active: 0, isolated: 0, degree_leq_one: 0 };
|
|
289
|
+
tallies.byType.set(typeKey, typeProfile);
|
|
290
|
+
}
|
|
291
|
+
typeProfile.active += 1;
|
|
292
|
+
if (degree > 1)
|
|
293
|
+
return;
|
|
294
|
+
tallies.degreeLeqOne += 1;
|
|
295
|
+
typeProfile.degree_leq_one += 1;
|
|
296
|
+
if (degree === 0) {
|
|
297
|
+
tallies.isolated += 1;
|
|
298
|
+
typeProfile.isolated += 1;
|
|
299
|
+
}
|
|
300
|
+
if (exemptIsolates.has(state.id.toLowerCase()) ||
|
|
301
|
+
isolateExemptTypes.has(typeKey.toLowerCase()))
|
|
302
|
+
return;
|
|
303
|
+
if (degree === 0)
|
|
304
|
+
tallies.isolates.push(state.id);
|
|
305
|
+
else
|
|
306
|
+
tallies.sparse.push(state.id);
|
|
307
|
+
}
|
|
308
|
+
function collectCoverageReport(assembly, nodeStates, exemptIsolates, isolateExemptTypes, maxSampleSize, edgesByKind) {
|
|
309
|
+
const tallies = {
|
|
310
|
+
active: 0,
|
|
311
|
+
missing: 0,
|
|
312
|
+
isolated: 0,
|
|
313
|
+
degreeLeqOne: 0,
|
|
314
|
+
isolates: [],
|
|
315
|
+
sparse: [],
|
|
316
|
+
byType: new Map(),
|
|
317
|
+
};
|
|
269
318
|
for (const id of assembly.graph.nodes()) {
|
|
270
319
|
const state = nodeStates.get(id);
|
|
271
320
|
/* c8 ignore next -- every graph node originates from assembly details by construction */
|
|
272
321
|
if (!state)
|
|
273
322
|
continue;
|
|
274
323
|
if (state.missing) {
|
|
275
|
-
|
|
324
|
+
tallies.missing += 1;
|
|
276
325
|
continue;
|
|
277
326
|
}
|
|
278
327
|
if (state.terminal)
|
|
279
328
|
continue;
|
|
280
|
-
|
|
281
|
-
const degree = assembly.graph.incidentEdges(id).length;
|
|
282
|
-
if (degree > 1)
|
|
283
|
-
continue;
|
|
284
|
-
degreeLeqOneActive += 1;
|
|
285
|
-
if (degree === 0)
|
|
286
|
-
isolatedActiveNodes += 1;
|
|
287
|
-
if (exemptIsolates.has(id.toLowerCase()))
|
|
288
|
-
continue;
|
|
289
|
-
if (degree === 0)
|
|
290
|
-
isolates.push(id);
|
|
291
|
-
else
|
|
292
|
-
sparse.push(id);
|
|
329
|
+
tallyActiveCoverageNode(tallies, state, assembly.graph.incidentEdges(id).length, exemptIsolates, isolateExemptTypes);
|
|
293
330
|
}
|
|
331
|
+
const { isolates, sparse } = tallies;
|
|
294
332
|
const findings = [];
|
|
295
333
|
appendFinding(findings, isolates, maxSampleSize, "isolated_active_node", "warning", "Active work should be reachable from the graph unless explicitly declared a valid isolate.", (count) => `${count} active item(s) with no relationships`, "Link the item to its parent, prerequisite, or origin — or register it as an exempt isolate.");
|
|
296
334
|
appendFinding(findings, sparse, maxSampleSize, "sparse_active_node", "info", "Active items with a single edge often miss an honest semantic partner; sparse is not automatically defective.", (count) => `${count} active item(s) with exactly one relationship`, "Investigate for an honest second edge (story->task, defect family, cross-epic complement); do not fabricate one.");
|
|
@@ -300,10 +338,11 @@ function collectCoverageReport(assembly, nodeStates, exemptIsolates, maxSampleSi
|
|
|
300
338
|
nodes: assembly.graph.nodes().length,
|
|
301
339
|
edges: assembly.graph.edges().length,
|
|
302
340
|
edges_by_kind: Object.fromEntries(Object.entries(edgesByKind).sort(([left], [right]) => left.localeCompare(right))),
|
|
303
|
-
active_nodes:
|
|
304
|
-
missing_nodes:
|
|
305
|
-
isolated_active_nodes:
|
|
306
|
-
degree_leq_one_active_nodes:
|
|
341
|
+
active_nodes: tallies.active,
|
|
342
|
+
missing_nodes: tallies.missing,
|
|
343
|
+
isolated_active_nodes: tallies.isolated,
|
|
344
|
+
degree_leq_one_active_nodes: tallies.degreeLeqOne,
|
|
345
|
+
coverage_by_type: Object.fromEntries([...tallies.byType.entries()].sort(([left], [right]) => left < right ? -1 : 1)),
|
|
307
346
|
},
|
|
308
347
|
};
|
|
309
348
|
}
|
|
@@ -327,6 +366,9 @@ export function auditWorkspaceRelationshipGraph(assembly, options = {}) {
|
|
|
327
366
|
const exemptIsolates = new Set((options.exemptIsolates ?? [])
|
|
328
367
|
.filter((id) => typeof id === "string")
|
|
329
368
|
.map((id) => id.trim().toLowerCase()));
|
|
369
|
+
const isolateExemptTypes = new Set((options.isolateExemptTypes ?? [])
|
|
370
|
+
.filter((type) => typeof type === "string")
|
|
371
|
+
.map((type) => type.trim().toLowerCase()));
|
|
330
372
|
const nodeStates = new Map(assembly.details.map((detail) => [
|
|
331
373
|
detail.id,
|
|
332
374
|
{
|
|
@@ -334,6 +376,7 @@ export function auditWorkspaceRelationshipGraph(assembly, options = {}) {
|
|
|
334
376
|
status: detail.status,
|
|
335
377
|
missing: assembly.missingIdSet.has(detail.id.toLowerCase()),
|
|
336
378
|
terminal: isTerminal(detail.status),
|
|
379
|
+
...(detail.type === undefined ? {} : { type: detail.type }),
|
|
337
380
|
},
|
|
338
381
|
]));
|
|
339
382
|
options.signal?.throwIfAborted();
|
|
@@ -344,9 +387,9 @@ export function auditWorkspaceRelationshipGraph(assembly, options = {}) {
|
|
|
344
387
|
options.signal?.throwIfAborted();
|
|
345
388
|
findings.push(...collectOrderingFindings(assembly, nodeStates, isBlocked, maxSampleSize));
|
|
346
389
|
options.signal?.throwIfAborted();
|
|
347
|
-
findings.push(...collectDuplicateEdgeFindings(assembly, nodeStates, maxSampleSize));
|
|
390
|
+
findings.push(...collectDuplicateEdgeFindings(assembly, nodeStates, maxSampleSize), ...collectDuplicateRowFindings(assembly, maxSampleSize));
|
|
348
391
|
options.signal?.throwIfAborted();
|
|
349
|
-
const coverage = collectCoverageReport(assembly, nodeStates, exemptIsolates, maxSampleSize, edgesByKind);
|
|
392
|
+
const coverage = collectCoverageReport(assembly, nodeStates, exemptIsolates, isolateExemptTypes, maxSampleSize, edgesByKind);
|
|
350
393
|
findings.push(...coverage.findings);
|
|
351
394
|
findings.sort((left, right) => SEVERITY_RANK[left.severity] - SEVERITY_RANK[right.severity] ||
|
|
352
395
|
left.code.localeCompare(right.code) ||
|
|
@@ -356,5 +399,70 @@ export function auditWorkspaceRelationshipGraph(assembly, options = {}) {
|
|
|
356
399
|
profile: coverage.profile,
|
|
357
400
|
};
|
|
358
401
|
}
|
|
402
|
+
/** Subtract two count records, keeping only non-zero signed deltas in sorted key order. */
|
|
403
|
+
function diffCountRecords(baseline, current) {
|
|
404
|
+
const keys = [
|
|
405
|
+
...new Set([...Object.keys(baseline), ...Object.keys(current)]),
|
|
406
|
+
].sort();
|
|
407
|
+
const deltas = {};
|
|
408
|
+
for (const key of keys) {
|
|
409
|
+
const delta = (current[key] ?? 0) - (baseline[key] ?? 0);
|
|
410
|
+
if (delta !== 0)
|
|
411
|
+
deltas[key] = delta;
|
|
412
|
+
}
|
|
413
|
+
return deltas;
|
|
414
|
+
}
|
|
415
|
+
/** Read per-type coverage defensively from current or legacy runtime snapshots. */
|
|
416
|
+
function readSnapshotCoverageByType(snapshot) {
|
|
417
|
+
return snapshot.profile.coverage_by_type ?? {};
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Compare two point-in-time audit snapshots and return the signed change in
|
|
421
|
+
* findings and structural coverage. This is the temporal primitive behind
|
|
422
|
+
* change-since-baseline audit reporting: agents track census drift across
|
|
423
|
+
* sessions without re-deriving or storing full finding collections.
|
|
424
|
+
*/
|
|
425
|
+
export function diffRelationshipAuditSnapshots(baseline, current) {
|
|
426
|
+
const coverageByType = {};
|
|
427
|
+
// Public SDK callers may compare legacy or JSON-decoded snapshots created
|
|
428
|
+
// before per-type coverage was added, even though current typed snapshots
|
|
429
|
+
// always carry this field.
|
|
430
|
+
const baselineCoverageByType = readSnapshotCoverageByType(baseline);
|
|
431
|
+
const currentCoverageByType = readSnapshotCoverageByType(current);
|
|
432
|
+
const typeKeys = [
|
|
433
|
+
...new Set([
|
|
434
|
+
...Object.keys(baselineCoverageByType),
|
|
435
|
+
...Object.keys(currentCoverageByType),
|
|
436
|
+
]),
|
|
437
|
+
].sort();
|
|
438
|
+
for (const type of typeKeys) {
|
|
439
|
+
const before = baselineCoverageByType[type];
|
|
440
|
+
const after = currentCoverageByType[type];
|
|
441
|
+
const delta = {
|
|
442
|
+
active: (after?.active ?? 0) - (before?.active ?? 0),
|
|
443
|
+
isolated: (after?.isolated ?? 0) - (before?.isolated ?? 0),
|
|
444
|
+
degree_leq_one: (after?.degree_leq_one ?? 0) - (before?.degree_leq_one ?? 0),
|
|
445
|
+
};
|
|
446
|
+
if (Object.values(delta).some((value) => value !== 0))
|
|
447
|
+
coverageByType[type] = delta;
|
|
448
|
+
}
|
|
449
|
+
return {
|
|
450
|
+
baseline_saved_at: baseline.saved_at,
|
|
451
|
+
same_snapshot: baseline.fingerprint === current.fingerprint,
|
|
452
|
+
affected_subjects_by_code: diffCountRecords(baseline.affected_subjects_by_code, current.affected_subjects_by_code),
|
|
453
|
+
profile: {
|
|
454
|
+
nodes: current.profile.nodes - baseline.profile.nodes,
|
|
455
|
+
edges: current.profile.edges - baseline.profile.edges,
|
|
456
|
+
active_nodes: current.profile.active_nodes - baseline.profile.active_nodes,
|
|
457
|
+
missing_nodes: current.profile.missing_nodes - baseline.profile.missing_nodes,
|
|
458
|
+
isolated_active_nodes: current.profile.isolated_active_nodes -
|
|
459
|
+
baseline.profile.isolated_active_nodes,
|
|
460
|
+
degree_leq_one_active_nodes: current.profile.degree_leq_one_active_nodes -
|
|
461
|
+
baseline.profile.degree_leq_one_active_nodes,
|
|
462
|
+
edges_by_kind: diffCountRecords(baseline.profile.edges_by_kind, current.profile.edges_by_kind),
|
|
463
|
+
coverage_by_type: coverageByType,
|
|
464
|
+
},
|
|
465
|
+
};
|
|
466
|
+
}
|
|
359
467
|
//# sourceMappingURL=governance.js.map
|
|
360
|
-
//# debugId=
|
|
468
|
+
//# debugId=4b36a9d4-2621-5560-831f-7fd026072cf1
|