gitnexus 1.6.5-rc.9 → 1.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_shared/scope-resolution/finalize-algorithm.js +10 -2
- package/dist/_shared/scope-resolution/finalize-algorithm.js.map +1 -1
- package/dist/_shared/scope-resolution/method-dispatch-index.d.ts +26 -0
- package/dist/_shared/scope-resolution/method-dispatch-index.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/method-dispatch-index.js +22 -3
- package/dist/_shared/scope-resolution/method-dispatch-index.js.map +1 -1
- package/dist/_shared/scope-resolution/registries/lookup-core.js +18 -1
- package/dist/_shared/scope-resolution/registries/lookup-core.js.map +1 -1
- package/dist/_shared/scope-resolution/symbol-definition.d.ts +2 -0
- package/dist/_shared/scope-resolution/symbol-definition.d.ts.map +1 -1
- package/dist/cli/ai-context.d.ts +1 -0
- package/dist/cli/ai-context.js +70 -16
- package/dist/cli/analyze.d.ts +30 -2
- package/dist/cli/analyze.js +45 -5
- package/dist/cli/index.js +8 -1
- package/dist/cli/setup.js +18 -0
- package/dist/cli/wiki.d.ts +2 -0
- package/dist/cli/wiki.js +11 -0
- package/dist/core/augmentation/engine.js +24 -4
- package/dist/core/incremental/shadow-candidates.d.ts +44 -0
- package/dist/core/incremental/shadow-candidates.js +74 -0
- package/dist/core/incremental/subgraph-extract.d.ts +64 -0
- package/dist/core/incremental/subgraph-extract.js +111 -0
- package/dist/core/ingestion/call-processor.d.ts +12 -1
- package/dist/core/ingestion/call-processor.js +183 -47
- package/dist/core/ingestion/class-extractors/configs/c-cpp.js +38 -0
- package/dist/core/ingestion/class-extractors/generic.js +8 -0
- package/dist/core/ingestion/class-types.d.ts +15 -0
- package/dist/core/ingestion/community-processor.js +18 -0
- package/dist/core/ingestion/import-resolvers/standard.d.ts +5 -0
- package/dist/core/ingestion/import-resolvers/standard.js +31 -1
- package/dist/core/ingestion/import-resolvers/utils.js +4 -0
- package/dist/core/ingestion/languages/c-cpp.js +20 -2
- package/dist/core/ingestion/languages/cpp/adl.d.ts +141 -0
- package/dist/core/ingestion/languages/cpp/adl.js +478 -0
- package/dist/core/ingestion/languages/cpp/arity-metadata.d.ts +18 -0
- package/dist/core/ingestion/languages/cpp/arity-metadata.js +170 -0
- package/dist/core/ingestion/languages/cpp/arity.d.ts +17 -0
- package/dist/core/ingestion/languages/cpp/arity.js +31 -0
- package/dist/core/ingestion/languages/cpp/captures.d.ts +2 -0
- package/dist/core/ingestion/languages/cpp/captures.js +1131 -0
- package/dist/core/ingestion/languages/cpp/conversion-rank.d.ts +31 -0
- package/dist/core/ingestion/languages/cpp/conversion-rank.js +47 -0
- package/dist/core/ingestion/languages/cpp/file-local-linkage.d.ts +118 -0
- package/dist/core/ingestion/languages/cpp/file-local-linkage.js +266 -0
- package/dist/core/ingestion/languages/cpp/header-scan.d.ts +8 -0
- package/dist/core/ingestion/languages/cpp/header-scan.js +50 -0
- package/dist/core/ingestion/languages/cpp/import-decomposer.d.ts +19 -0
- package/dist/core/ingestion/languages/cpp/import-decomposer.js +111 -0
- package/dist/core/ingestion/languages/cpp/import-target.d.ts +10 -0
- package/dist/core/ingestion/languages/cpp/import-target.js +13 -0
- package/dist/core/ingestion/languages/cpp/index.d.ts +11 -0
- package/dist/core/ingestion/languages/cpp/index.js +11 -0
- package/dist/core/ingestion/languages/cpp/inline-namespaces.d.ts +67 -0
- package/dist/core/ingestion/languages/cpp/inline-namespaces.js +176 -0
- package/dist/core/ingestion/languages/cpp/interpret.d.ts +35 -0
- package/dist/core/ingestion/languages/cpp/interpret.js +113 -0
- package/dist/core/ingestion/languages/cpp/merge-bindings.d.ts +13 -0
- package/dist/core/ingestion/languages/cpp/merge-bindings.js +29 -0
- package/dist/core/ingestion/languages/cpp/query.d.ts +3 -0
- package/dist/core/ingestion/languages/cpp/query.js +508 -0
- package/dist/core/ingestion/languages/cpp/range-bindings.d.ts +20 -0
- package/dist/core/ingestion/languages/cpp/range-bindings.js +212 -0
- package/dist/core/ingestion/languages/cpp/scope-resolver.d.ts +17 -0
- package/dist/core/ingestion/languages/cpp/scope-resolver.js +246 -0
- package/dist/core/ingestion/languages/cpp/simple-hooks.d.ts +24 -0
- package/dist/core/ingestion/languages/cpp/simple-hooks.js +62 -0
- package/dist/core/ingestion/languages/cpp/two-phase-lookup.d.ts +76 -0
- package/dist/core/ingestion/languages/cpp/two-phase-lookup.js +195 -0
- package/dist/core/ingestion/languages/java/arity-metadata.d.ts +18 -0
- package/dist/core/ingestion/languages/java/arity-metadata.js +40 -0
- package/dist/core/ingestion/languages/java/arity.d.ts +10 -0
- package/dist/core/ingestion/languages/java/arity.js +24 -0
- package/dist/core/ingestion/languages/java/cache-stats.d.ts +15 -0
- package/dist/core/ingestion/languages/java/cache-stats.js +26 -0
- package/dist/core/ingestion/languages/java/captures.d.ts +17 -0
- package/dist/core/ingestion/languages/java/captures.js +187 -0
- package/dist/core/ingestion/languages/java/import-decomposer.d.ts +18 -0
- package/dist/core/ingestion/languages/java/import-decomposer.js +85 -0
- package/dist/core/ingestion/languages/java/import-target.d.ts +17 -0
- package/dist/core/ingestion/languages/java/import-target.js +100 -0
- package/dist/core/ingestion/languages/java/index.d.ts +29 -0
- package/dist/core/ingestion/languages/java/index.js +29 -0
- package/dist/core/ingestion/languages/java/interpret.d.ts +13 -0
- package/dist/core/ingestion/languages/java/interpret.js +131 -0
- package/dist/core/ingestion/languages/java/merge-bindings.d.ts +12 -0
- package/dist/core/ingestion/languages/java/merge-bindings.js +40 -0
- package/dist/core/ingestion/languages/java/query.d.ts +30 -0
- package/dist/core/ingestion/languages/java/query.js +192 -0
- package/dist/core/ingestion/languages/java/receiver-binding.d.ts +11 -0
- package/dist/core/ingestion/languages/java/receiver-binding.js +95 -0
- package/dist/core/ingestion/languages/java/scope-resolver.d.ts +50 -0
- package/dist/core/ingestion/languages/java/scope-resolver.js +74 -0
- package/dist/core/ingestion/languages/java/simple-hooks.d.ts +13 -0
- package/dist/core/ingestion/languages/java/simple-hooks.js +34 -0
- package/dist/core/ingestion/languages/java.js +11 -0
- package/dist/core/ingestion/languages/php/arity-metadata.d.ts +28 -0
- package/dist/core/ingestion/languages/php/arity-metadata.js +63 -0
- package/dist/core/ingestion/languages/php/arity.d.ts +25 -0
- package/dist/core/ingestion/languages/php/arity.js +40 -0
- package/dist/core/ingestion/languages/php/cache-stats.d.ts +15 -0
- package/dist/core/ingestion/languages/php/cache-stats.js +26 -0
- package/dist/core/ingestion/languages/php/captures.d.ts +34 -0
- package/dist/core/ingestion/languages/php/captures.js +739 -0
- package/dist/core/ingestion/languages/php/import-decomposer.d.ts +28 -0
- package/dist/core/ingestion/languages/php/import-decomposer.js +265 -0
- package/dist/core/ingestion/languages/php/import-target.d.ts +47 -0
- package/dist/core/ingestion/languages/php/import-target.js +100 -0
- package/dist/core/ingestion/languages/php/index.d.ts +68 -0
- package/dist/core/ingestion/languages/php/index.js +72 -0
- package/dist/core/ingestion/languages/php/interpret.d.ts +36 -0
- package/dist/core/ingestion/languages/php/interpret.js +241 -0
- package/dist/core/ingestion/languages/php/merge-bindings.d.ts +19 -0
- package/dist/core/ingestion/languages/php/merge-bindings.js +47 -0
- package/dist/core/ingestion/languages/php/namespace-siblings.d.ts +51 -0
- package/dist/core/ingestion/languages/php/namespace-siblings.js +288 -0
- package/dist/core/ingestion/languages/php/query.d.ts +32 -0
- package/dist/core/ingestion/languages/php/query.js +326 -0
- package/dist/core/ingestion/languages/php/receiver-binding.d.ts +36 -0
- package/dist/core/ingestion/languages/php/receiver-binding.js +128 -0
- package/dist/core/ingestion/languages/php/scope-resolver.d.ts +23 -0
- package/dist/core/ingestion/languages/php/scope-resolver.js +358 -0
- package/dist/core/ingestion/languages/php/simple-hooks.d.ts +42 -0
- package/dist/core/ingestion/languages/php/simple-hooks.js +111 -0
- package/dist/core/ingestion/languages/php.d.ts +0 -7
- package/dist/core/ingestion/languages/php.js +15 -0
- package/dist/core/ingestion/markdown-processor.js +6 -1
- package/dist/core/ingestion/model/symbol-table.d.ts +1 -0
- package/dist/core/ingestion/model/symbol-table.js +3 -0
- package/dist/core/ingestion/parsing-processor.d.ts +22 -2
- package/dist/core/ingestion/parsing-processor.js +123 -50
- package/dist/core/ingestion/pipeline-phases/cross-file-impl.js +54 -2
- package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +5 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +119 -11
- package/dist/core/ingestion/pipeline-phases/parse.d.ts +14 -0
- package/dist/core/ingestion/pipeline.d.ts +13 -0
- package/dist/core/ingestion/registry-primary-flag.js +2 -0
- package/dist/core/ingestion/scope-extractor.js +4 -0
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +167 -0
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.js +3 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/ids.d.ts +1 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +12 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/method-dispatch.d.ts +1 -1
- package/dist/core/ingestion/scope-resolution/graph-bridge/method-dispatch.js +12 -2
- package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +16 -0
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +36 -0
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +204 -13
- package/dist/core/ingestion/scope-resolution/passes/imported-return-types.js +1 -1
- package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.d.ts +55 -4
- package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.js +186 -5
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +10 -1
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +207 -9
- package/dist/core/ingestion/scope-resolution/pipeline/phase.js +13 -1
- package/dist/core/ingestion/scope-resolution/pipeline/registry.js +6 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +17 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +86 -8
- package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +30 -0
- package/dist/core/ingestion/scope-resolution/scope/walkers.js +90 -8
- package/dist/core/ingestion/tree-sitter-queries.d.ts +2 -2
- package/dist/core/ingestion/tree-sitter-queries.js +23 -0
- package/dist/core/ingestion/utils/ast-helpers.js +6 -1
- package/dist/core/ingestion/utils/template-arguments.d.ts +11 -0
- package/dist/core/ingestion/utils/template-arguments.js +65 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +3 -0
- package/dist/core/ingestion/workers/parse-worker.js +42 -1
- package/dist/core/lbug/lbug-adapter.d.ts +39 -0
- package/dist/core/lbug/lbug-adapter.js +348 -48
- package/dist/core/run-analyze.d.ts +2 -0
- package/dist/core/run-analyze.js +323 -29
- package/dist/core/search/hybrid-search.d.ts +3 -0
- package/dist/core/search/hybrid-search.js +23 -6
- package/dist/core/wiki/generator.js +4 -3
- package/dist/core/wiki/html-viewer.js +2 -1
- package/dist/core/wiki/llm-client.d.ts +4 -0
- package/dist/core/wiki/llm-client.js +3 -3
- package/dist/core/wiki/mermaid-sanitizer.d.ts +2 -0
- package/dist/core/wiki/mermaid-sanitizer.js +100 -0
- package/dist/mcp/local/local-backend.js +20 -7
- package/dist/storage/file-hash.d.ts +47 -0
- package/dist/storage/file-hash.js +86 -0
- package/dist/storage/parse-cache.d.ts +73 -0
- package/dist/storage/parse-cache.js +253 -0
- package/dist/storage/repo-manager.d.ts +41 -1
- package/dist/storage/repo-manager.js +61 -6
- package/hooks/claude/gitnexus-hook.cjs +42 -4
- package/hooks/claude/hook-db-lock-probe.cjs +238 -0
- package/hooks/claude/hook-lock.cjs +119 -0
- package/hooks/claude/win-rm-list-json.ps1 +76 -0
- package/package.json +1 -1
- package/scripts/build.js +6 -2
- package/vendor/node_modules/node-addon-api/node_addon_api.target.mk +16 -16
- package/vendor/node_modules/node-addon-api/node_addon_api_except.target.mk +16 -16
- package/vendor/node_modules/node-addon-api/node_addon_api_except_all.target.mk +16 -16
- package/vendor/node_modules/node-addon-api/node_addon_api_maybe.target.mk +16 -16
- package/web/assets/{agent-BpHQleHE.js → agent-D2kLt6Dl.js} +91 -91
- package/web/assets/architecture-7EHR7CIX-6QZW5X65-BETj2x4g.js +1 -0
- package/web/assets/architectureDiagram-UL44E2DR-DvIkEGkp.js +36 -0
- package/web/assets/blockDiagram-7IZFK4PR-B4niSLvR.js +132 -0
- package/web/assets/{c4Diagram-DFAF54RM-DeJV1rCn.js → c4Diagram-Y2BXMSZH-DIzEXalR.js} +6 -6
- package/web/assets/chunk-2T2R6R2M-n6s9JZqv.js +4 -0
- package/web/assets/chunk-2UTLFMKG-CMBB1TMN.js +1 -0
- package/web/assets/{chunk-7RZVMHOQ-BdIU-RGO.js → chunk-3SSMPTDK-BzGeeheI.js} +1 -1
- package/web/assets/chunk-4R4BOZG6-mKhL59ul.js +159 -0
- package/web/assets/chunk-5IMINLNL-CnTLsXIV.js +1 -0
- package/web/assets/chunk-5VCL7Z4A-w1BCx98Z.js +1 -0
- package/web/assets/chunk-6764PJDD-ChwMM2z4.js +1 -0
- package/web/assets/chunk-67TQ5CYL-CChC2cAv.js +124 -0
- package/web/assets/chunk-7J6CGLKN-DT-b53FT.js +10 -0
- package/web/assets/chunk-7W6UQGC5-CgFyfZ6k.js +1 -0
- package/web/assets/chunk-AQ6EADP3-CCFje6lL.js +1 -0
- package/web/assets/{chunk-KSICW3F5-C2tZmXwv.js → chunk-AZZRMDJM-5E9ARdDQ.js} +1 -1
- package/web/assets/chunk-C62D2QBJ-CDAWj26E.js +1 -0
- package/web/assets/chunk-CEXFNPSA-D68Tk6ls.js +1 -0
- package/web/assets/chunk-INKRHTLW-ELpfuk9h.js +70 -0
- package/web/assets/chunk-J5EP6P6S-DxWW0yvu.js +1 -0
- package/web/assets/{chunk-AEOMTBSW-D7qjBMHW.js → chunk-JQRUD6KW-Dcq_Qnyy.js} +1 -1
- package/web/assets/chunk-KGFNY3KK-W-VR57xb.js +62 -0
- package/web/assets/chunk-KGYTTC2M-C6PHeuay.js +1 -0
- package/web/assets/chunk-KNLZD3CH-BuZBOcso.js +1 -0
- package/web/assets/chunk-KRXBNO2N-DH2NPM7I.js +1 -0
- package/web/assets/chunk-LCXTWHL2-CqJ0oo1x.js +231 -0
- package/web/assets/chunk-LII3EMHJ-3X33tCpU.js +1 -0
- package/web/assets/chunk-QA3QBVWF-D5Ha3GgB.js +2 -0
- package/web/assets/chunk-RERM46MO-DQNbXtfw.js +1 -0
- package/web/assets/chunk-RG4AUYOV-D0M6vdq9.js +206 -0
- package/web/assets/chunk-RKZBBQEN-BmTPLSyv.js +1 -0
- package/web/assets/chunk-RLI5ZMPA-DExu2DOK.js +1 -0
- package/web/assets/chunk-T2UQINTJ-kGYG7ppP.js +1 -0
- package/web/assets/chunk-T5OCTHI4--9wWpVws.js +1 -0
- package/web/assets/chunk-UP6H54XL-DsKdC6jC.js +1 -0
- package/web/assets/chunk-UXSXWOXI-DR81EqLr.js +1 -0
- package/web/assets/chunk-UY5QBCOK-Du-c56gs.js +1 -0
- package/web/assets/chunk-W44A43WB-lrCpuupA.js +1 -0
- package/web/assets/chunk-ZXARS5L4-CSZRiyOf.js +1 -0
- package/web/assets/classDiagram-KGZ6W3CR-BB-saqC1.js +1 -0
- package/web/assets/classDiagram-v2-72OJOZXJ-Bp2bAlS5.js +1 -0
- package/web/assets/cose-bilkent-UX7MHV2Q-BHv2bbtl.js +1 -0
- package/web/assets/dagre-ND4H6XIP-DsIr9MOv.js +4 -0
- package/web/assets/diagram-3NCE3AQN-BUxnDPOY.js +43 -0
- package/web/assets/diagram-GF46GFSD-BJTyqqvI.js +24 -0
- package/web/assets/diagram-HNR7UZ2L-fUWreZzx.js +3 -0
- package/web/assets/diagram-QXG6HAR7-DilMHamP.js +24 -0
- package/web/assets/diagram-WEQXMOUZ-8KUrarg1.js +10 -0
- package/web/assets/erDiagram-L5TCEMPS-Doj9mSiW.js +85 -0
- package/web/assets/eventmodeling-FCH6USID-MREXMVOE-B3myHUst.js +1 -0
- package/web/assets/{flowDiagram-OTCZ4VVT-aAZw4c_M.js → flowDiagram-H6V6AXG4-BOSR-i3p.js} +12 -12
- package/web/assets/ganttDiagram-JCBTUEKG-BHQssTz6.js +292 -0
- package/web/assets/gitGraph-WXDBUCRP-R675I2BI-DF3rtf4A.js +1 -0
- package/web/assets/gitGraphDiagram-S2ZK5IYY-CCwVLT__.js +106 -0
- package/web/assets/index-j4-NuleT.js +623 -0
- package/web/assets/info-J43DQDTF-KCYPFFUO-R4XV_akJ.js +1 -0
- package/web/assets/infoDiagram-3YFTVSEB-Ce_TvtaV.js +2 -0
- package/web/assets/{ishikawaDiagram-YMYX4NHK-BF8AtDaj.js → ishikawaDiagram-BNXS4ZKH-4MXIDCSF.js} +6 -6
- package/web/assets/journeyDiagram-M6C3CM3L-DNPYCpvq.js +139 -0
- package/web/assets/kanban-definition-75IXJCU3-yYdfm8Kk.js +89 -0
- package/web/assets/{katex-GD7MH7QM-CJiOjBBJ.js → katex-K3KEBU37-CrVLz8l7.js} +1 -1
- package/web/assets/mindmap-definition-2TDM6QVE-DFNEKM77.js +96 -0
- package/web/assets/packet-YPE3B663-LP52Z2RK-CgQPdNZa.js +1 -0
- package/web/assets/pie-LRSECV5Y-TCRJHUBD-BzWOwc_c.js +1 -0
- package/web/assets/pieDiagram-CU6KROY3-iZAqpvhd.js +30 -0
- package/web/assets/quadrantDiagram-VICAPDV7-bAm6Hi_5.js +7 -0
- package/web/assets/radar-GUYGQ44K-RDLRG3WG-B7KG0FN7.js +1 -0
- package/web/assets/requirementDiagram-JXO7QTGE-CtDijhnf.js +84 -0
- package/web/assets/sankeyDiagram-URQDO5SZ-LFJz53u5.js +40 -0
- package/web/assets/sequenceDiagram-VS2MUI6T-BdjvyCHM.js +162 -0
- package/web/assets/stateDiagram-7D4R322I-e5odN9fG.js +1 -0
- package/web/assets/stateDiagram-v2-36443NZ5-vPsvjR2D.js +1 -0
- package/web/assets/timeline-definition-O6YCAMPW-DPAksNQp.js +120 -0
- package/web/assets/treeView-BLDUP644-QA4HXRO3-CRSxEwLO.js +1 -0
- package/web/assets/treemap-LRROVOQU-LLAWBHMP-a0PfKoVn.js +1 -0
- package/web/assets/vennDiagram-MWXL3ELB-Dtlfdt7J.js +34 -0
- package/web/assets/wardley-L42UT6IY-5TKZOOLJ-BKQ6e5mC.js +1 -0
- package/web/assets/wardleyDiagram-CUQ6CDDI-B23uRMUC.js +78 -0
- package/web/assets/{xychartDiagram-ZHJ5623Y-DZ7fP1_3.js → xychartDiagram-N2JHSOCM-DxwHeksW.js} +6 -6
- package/web/index.html +13 -2
- package/web/assets/architecture-YZFGNWBL-S5CXDPWN-BUzkWgDl.js +0 -1
- package/web/assets/architectureDiagram-EMZXCZ2Q-CXbzok1G.js +0 -36
- package/web/assets/blockDiagram-IGV67L2C-DA1QJ4ZB.js +0 -132
- package/web/assets/chunk-3GS5O3IE-B8hpnia8.js +0 -231
- package/web/assets/chunk-3YCYZ6SJ-B-BVu21C.js +0 -1
- package/web/assets/chunk-6NTNNK5N-DW-jYSDr.js +0 -1
- package/web/assets/chunk-A34GCYZU-BbZl-IKy.js +0 -1
- package/web/assets/chunk-DJ7UZH7F-B5tMfF87.js +0 -1
- package/web/assets/chunk-DKKBVRCY-Djm-6K_e.js +0 -4
- package/web/assets/chunk-DU5LTGQ6-QPoynYcp.js +0 -1
- package/web/assets/chunk-FXACKDTF-jieSmbe2.js +0 -159
- package/web/assets/chunk-H3VCZNTA-8EMHZ3SV.js +0 -1
- package/web/assets/chunk-HN6EAY2L-CgE_agsD.js +0 -1
- package/web/assets/chunk-O5ABG6QK-DEZV1O19.js +0 -1
- package/web/assets/chunk-PK6DOVAG-BXv0JnsJ.js +0 -206
- package/web/assets/chunk-RNJOYNJ4-BiBkF9dN.js +0 -1
- package/web/assets/chunk-RWUO3TPN-DTWzAhQ1.js +0 -1
- package/web/assets/chunk-TBF5ZNIQ-BSl6s9lT.js +0 -1
- package/web/assets/chunk-TU3PZOEN-DE5Qhc0N.js +0 -1
- package/web/assets/chunk-TYMNRAUI-D1NDLIMG.js +0 -1
- package/web/assets/chunk-VELTKBKT-C9dVN39o.js +0 -1
- package/web/assets/chunk-W7ZLLLMY-Bu-cdLzs.js +0 -1
- package/web/assets/chunk-WSB5WSVC-Dor71SOz.js +0 -1
- package/web/assets/chunk-XGPFEOL4-CUonLRle.js +0 -1
- package/web/assets/classDiagram-PPOCWD7C-Cdd6UdVv.js +0 -1
- package/web/assets/classDiagram-v2-23LJLIIU-DNhxY2Ib.js +0 -1
- package/web/assets/cose-bilkent-PNC4W37J-DdZq7Qz_.js +0 -1
- package/web/assets/dagre-E77IOHMT-Cfj_6e17.js +0 -4
- package/web/assets/diagram-H7BISOXX-BNvpsrBv.js +0 -43
- package/web/assets/diagram-JC5VWROH-Cn0rtDbJ.js +0 -24
- package/web/assets/diagram-LXUTUG65-CHfoVKNm.js +0 -10
- package/web/assets/diagram-WEHSV5V5-BLxHksnu.js +0 -24
- package/web/assets/erDiagram-GCSMX5X6-DfdN6iqF.js +0 -85
- package/web/assets/ganttDiagram-MUNLMDZQ-BEfC_7Or.js +0 -292
- package/web/assets/gitGraph-7Q5UKJZL-54BCDZD5-B6ZeRLP0.js +0 -1
- package/web/assets/gitGraphDiagram-3HKGZ4G3-CG0kC8Q5.js +0 -106
- package/web/assets/index-3NUCUW2s.js +0 -886
- package/web/assets/info-OMHHGYJF-BF2H5H6G-BIOsZy0W.js +0 -1
- package/web/assets/infoDiagram-MN7RKWGX-DUoMH-pq.js +0 -2
- package/web/assets/journeyDiagram-SO5T7YLQ-BW8oKa3N.js +0 -139
- package/web/assets/kanban-definition-LJHFXRCJ-DzbOsR-2.js +0 -89
- package/web/assets/mindmap-definition-2EUWGEK5-BIXGQALq.js +0 -96
- package/web/assets/packet-4T2RLAQJ-EV4IVRXR-Bs7ysNMU.js +0 -1
- package/web/assets/pie-ZZUOXDRM-N23DN5KN-Csi8nnbH.js +0 -1
- package/web/assets/pieDiagram-3IATQBI2-QY-u7P6a.js +0 -30
- package/web/assets/quadrantDiagram-E256RVCF-Di4aNiU8.js +0 -7
- package/web/assets/radar-PYXPWWZC-P6TP7ZYP-DJlyXh2-.js +0 -1
- package/web/assets/requirementDiagram-M5DCFWZL-CzUUrhQD.js +0 -84
- package/web/assets/sankeyDiagram-L3NBLAOT-D2fbMiIU.js +0 -10
- package/web/assets/sequenceDiagram-ZOUHS735-BNdkqguh.js +0 -157
- package/web/assets/stateDiagram-MLPALWAM-51_Q2w2L.js +0 -1
- package/web/assets/stateDiagram-v2-B5LQ5ZB2-8nHs2aNE.js +0 -1
- package/web/assets/timeline-definition-5SPVSISX-DkMiGj4Z.js +0 -120
- package/web/assets/treeView-SZITEDCU-5DXDK3XO-Cy6P0_CZ.js +0 -1
- package/web/assets/treemap-W4RFUUIX-WYLRDWKO-C_pp1R7A.js +0 -1
- package/web/assets/vennDiagram-IE5QUKF5-BCxMmf9q.js +0 -34
- package/web/assets/wardley-RL74JXVD-BCRCBASE-CMStJhpn.js +0 -1
- package/web/assets/wardleyDiagram-XU3VSMPF-ijlLF77s.js +0 -20
- /package/web/assets/{context-builder-CqQNhRj1.js → context-builder-_HS0v2ma.js} +0 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
const MERMAID_FENCE_RE = /```mermaid\s*\n([\s\S]*?)```/g;
|
|
2
|
+
const NODE_LABEL_RE = /(\[[^\]\n]*(?:\\n)[^\]\n]*\]|\{[^}\n]*(?:\\n)[^}\n]*\}|\([^)\n]*(?:\\n)[^)\n]*\))/g;
|
|
3
|
+
const EDGE_LABEL_RE = /\|([^|\n]+)\|/g;
|
|
4
|
+
const UNSAFE_EDGE_LABEL_RE = /[()[\]{}<>]/;
|
|
5
|
+
const UNSAFE_NODE_ID_RE = /[^A-Za-z0-9_-]/;
|
|
6
|
+
const NODE_ID_RE = /^[A-Za-z0-9_.:/()-]+$/;
|
|
7
|
+
const LINE_PREFIX_RE = /^(\s*(?:(?:[-A-Za-z0-9_]+)\s*:\s*)?)(.*)$/;
|
|
8
|
+
const EDGE_RE = /(\s*(?:[ox])?(?:--+|==+|\.\.+)(?:[>|ox])?\|[^|\n]*\|(?:[>|ox])?|\s*(?:[ox])?(?:--+|==+|\.\.+)(?:[>|ox])?|\s*<--+>?\s*)/g;
|
|
9
|
+
export function sanitizeMermaidMarkdown(markdown) {
|
|
10
|
+
return markdown.replace(MERMAID_FENCE_RE, (_match, diagram) => {
|
|
11
|
+
return '```mermaid\n' + sanitizeMermaidDiagram(diagram) + '```';
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
export function sanitizeMermaidDiagram(diagram) {
|
|
15
|
+
const aliases = new Map();
|
|
16
|
+
let nextAlias = 1;
|
|
17
|
+
const aliasFor = (id) => {
|
|
18
|
+
const existing = aliases.get(id);
|
|
19
|
+
if (existing)
|
|
20
|
+
return existing;
|
|
21
|
+
const base = id.replace(/[^A-Za-z0-9_-]/g, '_').replace(/^_+|_+$/g, '') || 'node';
|
|
22
|
+
let alias = base;
|
|
23
|
+
while ([...aliases.values()].includes(alias)) {
|
|
24
|
+
nextAlias += 1;
|
|
25
|
+
alias = `${base}_${nextAlias}`;
|
|
26
|
+
}
|
|
27
|
+
aliases.set(id, alias);
|
|
28
|
+
return alias;
|
|
29
|
+
};
|
|
30
|
+
return diagram
|
|
31
|
+
.split('\n')
|
|
32
|
+
.map((line) => sanitizeMermaidLine(line, aliasFor))
|
|
33
|
+
.join('\n');
|
|
34
|
+
}
|
|
35
|
+
function sanitizeMermaidLine(line, aliasFor) {
|
|
36
|
+
let sanitized = replaceLiteralLineBreaksInLabels(line);
|
|
37
|
+
sanitized = quoteUnsafeEdgeLabels(sanitized);
|
|
38
|
+
const prefixMatch = sanitized.match(LINE_PREFIX_RE);
|
|
39
|
+
if (!prefixMatch)
|
|
40
|
+
return sanitized;
|
|
41
|
+
const prefix = prefixMatch[1];
|
|
42
|
+
const body = prefixMatch[2];
|
|
43
|
+
if (isDirectiveLine(body))
|
|
44
|
+
return sanitized;
|
|
45
|
+
const parts = body.split(EDGE_RE);
|
|
46
|
+
if (parts.length === 1)
|
|
47
|
+
return sanitized;
|
|
48
|
+
for (let i = 0; i < parts.length; i += 2) {
|
|
49
|
+
parts[i] = sanitizeNodeReference(parts[i], aliasFor);
|
|
50
|
+
}
|
|
51
|
+
return prefix + parts.join('');
|
|
52
|
+
}
|
|
53
|
+
function replaceLiteralLineBreaksInLabels(line) {
|
|
54
|
+
return line.replace(NODE_LABEL_RE, (label) => label.replace(/\\n/g, '<br/>'));
|
|
55
|
+
}
|
|
56
|
+
function quoteUnsafeEdgeLabels(line) {
|
|
57
|
+
return line.replace(EDGE_LABEL_RE, (match, label) => {
|
|
58
|
+
const trimmed = label.trim();
|
|
59
|
+
if (!UNSAFE_EDGE_LABEL_RE.test(trimmed))
|
|
60
|
+
return match;
|
|
61
|
+
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
62
|
+
(trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
|
63
|
+
return match;
|
|
64
|
+
}
|
|
65
|
+
return `|"${escapeMermaidLabel(trimmed)}"|`;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function sanitizeNodeReference(segment, aliasFor) {
|
|
69
|
+
const match = segment.match(/^(\s*)([A-Za-z0-9_.:/()-]+)(.*?)(\s*)$/);
|
|
70
|
+
if (!match)
|
|
71
|
+
return segment;
|
|
72
|
+
const [, leading, id, suffix, trailing] = match;
|
|
73
|
+
if (!NODE_ID_RE.test(id) || !UNSAFE_NODE_ID_RE.test(id))
|
|
74
|
+
return segment;
|
|
75
|
+
const hasInlineLabel = suffix.trim().startsWith('[') || suffix.trim().startsWith('(') || suffix.trim().startsWith('{');
|
|
76
|
+
if (hasInlineLabel)
|
|
77
|
+
return `${leading}${aliasFor(id)}${suffix}${trailing}`;
|
|
78
|
+
return `${leading}${aliasFor(id)}["${escapeMermaidLabel(id)}"]${suffix}${trailing}`;
|
|
79
|
+
}
|
|
80
|
+
function escapeMermaidLabel(label) {
|
|
81
|
+
return label.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
82
|
+
}
|
|
83
|
+
function isDirectiveLine(line) {
|
|
84
|
+
const trimmed = line.trim();
|
|
85
|
+
return (trimmed === '' ||
|
|
86
|
+
trimmed.startsWith('%%') ||
|
|
87
|
+
trimmed.startsWith('graph ') ||
|
|
88
|
+
trimmed.startsWith('flowchart ') ||
|
|
89
|
+
trimmed.startsWith('sequenceDiagram') ||
|
|
90
|
+
trimmed.startsWith('classDiagram') ||
|
|
91
|
+
trimmed.startsWith('stateDiagram') ||
|
|
92
|
+
trimmed.startsWith('erDiagram') ||
|
|
93
|
+
trimmed.startsWith('journey') ||
|
|
94
|
+
trimmed.startsWith('gantt') ||
|
|
95
|
+
trimmed.startsWith('pie ') ||
|
|
96
|
+
trimmed.startsWith('mindmap') ||
|
|
97
|
+
trimmed.startsWith('timeline') ||
|
|
98
|
+
trimmed.startsWith('subgraph ') ||
|
|
99
|
+
trimmed === 'end');
|
|
100
|
+
}
|
|
@@ -634,8 +634,10 @@ export class LocalBackend {
|
|
|
634
634
|
timer.time('bm25', this.bm25Search(repo, searchQuery, searchLimit)),
|
|
635
635
|
timer.time('vector', this.semanticSearch(repo, searchQuery, searchLimit)),
|
|
636
636
|
]);
|
|
637
|
-
|
|
638
|
-
|
|
637
|
+
// Guard against undefined results (#1489) — when FTS is entirely
|
|
638
|
+
// unavailable the search helper may return an unexpected shape.
|
|
639
|
+
const bm25Results = bm25SearchResult?.results ?? [];
|
|
640
|
+
const ftsUsed = bm25SearchResult?.ftsUsed ?? false;
|
|
639
641
|
// Merge via reciprocal rank fusion
|
|
640
642
|
timer.start('merge');
|
|
641
643
|
const scoreMap = new Map();
|
|
@@ -651,8 +653,9 @@ export class LocalBackend {
|
|
|
651
653
|
scoreMap.set(key, { score: rrfScore, data: result });
|
|
652
654
|
}
|
|
653
655
|
}
|
|
654
|
-
|
|
655
|
-
|
|
656
|
+
const safeSemanticResults = semanticResults ?? [];
|
|
657
|
+
for (let i = 0; i < safeSemanticResults.length; i++) {
|
|
658
|
+
const result = safeSemanticResults[i];
|
|
656
659
|
const key = result.nodeId || result.filePath;
|
|
657
660
|
const rrfScore = 1 / (60 + i);
|
|
658
661
|
const existing = scoreMap.get(key);
|
|
@@ -826,7 +829,15 @@ export class LocalBackend {
|
|
|
826
829
|
* BM25 keyword search helper - uses LadybugDB FTS for always-fresh results
|
|
827
830
|
*/
|
|
828
831
|
async bm25Search(repo, query, limit) {
|
|
829
|
-
|
|
832
|
+
let searchFTSFromLbug;
|
|
833
|
+
try {
|
|
834
|
+
({ searchFTSFromLbug } = await import('../../core/search/bm25-index.js'));
|
|
835
|
+
}
|
|
836
|
+
catch (err) {
|
|
837
|
+
// Module import can fail in sandboxed MCP contexts (#1489)
|
|
838
|
+
logger.warn({ err: err?.message }, 'GitNexus: bm25-index.js import failed — falling back to semantic-only');
|
|
839
|
+
return { results: [], ftsUsed: false };
|
|
840
|
+
}
|
|
830
841
|
let ftsResponse;
|
|
831
842
|
try {
|
|
832
843
|
ftsResponse = await searchFTSFromLbug(query, limit, repo.id);
|
|
@@ -835,8 +846,10 @@ export class LocalBackend {
|
|
|
835
846
|
logger.error({ err: err.message }, 'GitNexus: BM25/FTS search failed (FTS indexes may not exist) -');
|
|
836
847
|
return { results: [], ftsUsed: false };
|
|
837
848
|
}
|
|
838
|
-
|
|
839
|
-
|
|
849
|
+
// Guard against unexpected response shape (#1489) — ftsResponse.results
|
|
850
|
+
// could be undefined when the FTS extension is unavailable in the MCP process.
|
|
851
|
+
const bm25Results = ftsResponse?.results ?? [];
|
|
852
|
+
const ftsUsed = ftsResponse?.ftsAvailable ?? false;
|
|
840
853
|
const results = [];
|
|
841
854
|
for (const bm25Result of bm25Results) {
|
|
842
855
|
const fullPath = bm25Result.filePath;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-file content hashing for incremental DB writeback.
|
|
3
|
+
*
|
|
4
|
+
* On every analyze run we compute SHA-256 of every file's content and
|
|
5
|
+
* store the map in meta.json. The next run compares disk against the
|
|
6
|
+
* stored map and produces:
|
|
7
|
+
* - `changed` — content differs (re-emit DB rows for this file)
|
|
8
|
+
* - `added` — file is new on disk (insert DB rows)
|
|
9
|
+
* - `deleted` — file was in last meta but no longer on disk (drop rows)
|
|
10
|
+
*
|
|
11
|
+
* The pipeline still parses every file (correctness invariant: cross-file
|
|
12
|
+
* resolution needs full data). What this enables is a SELECTIVE DB
|
|
13
|
+
* writeback: instead of wipe-and-reload of the whole graph (~50s of CSV
|
|
14
|
+
* COPY on a 25K-node repo), we only delete-and-rewrite rows for the
|
|
15
|
+
* changed/added/deleted set.
|
|
16
|
+
*
|
|
17
|
+
* See docs/superpowers/specs/2026-05-10-incremental-indexing-design.md
|
|
18
|
+
* (Option B revision).
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Compute SHA-256 of a single file. Returns null when the file can't be
|
|
22
|
+
* read — caller treats that as "no signature, assume changed".
|
|
23
|
+
*/
|
|
24
|
+
export declare const computeFileHash: (absPath: string) => Promise<string | null>;
|
|
25
|
+
/**
|
|
26
|
+
* Compute SHA-256 hashes for many files in parallel batches. Files that
|
|
27
|
+
* fail to read are omitted from the result map.
|
|
28
|
+
*/
|
|
29
|
+
export declare const computeFileHashes: (repoPath: string, relPaths: readonly string[]) => Promise<Map<string, string>>;
|
|
30
|
+
/** Result of comparing the current on-disk hashes against stored ones. */
|
|
31
|
+
export interface FileHashDiff {
|
|
32
|
+
/** Files whose content hash differs from stored. */
|
|
33
|
+
changed: string[];
|
|
34
|
+
/** Files in the current scan that weren't in the stored map. */
|
|
35
|
+
added: string[];
|
|
36
|
+
/** Files in the stored map that aren't in the current scan. */
|
|
37
|
+
deleted: string[];
|
|
38
|
+
/** All files whose DB rows must be replaced (changed ∪ added). */
|
|
39
|
+
toWrite: string[];
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Diff a current hash map against a previously stored one.
|
|
43
|
+
*
|
|
44
|
+
* Sorted output so two runs produce identical diff arrays for the same
|
|
45
|
+
* changes — useful for stable logging / equivalence checks.
|
|
46
|
+
*/
|
|
47
|
+
export declare const diffFileHashes: (current: ReadonlyMap<string, string>, stored: Readonly<Record<string, string>> | undefined) => FileHashDiff;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-file content hashing for incremental DB writeback.
|
|
3
|
+
*
|
|
4
|
+
* On every analyze run we compute SHA-256 of every file's content and
|
|
5
|
+
* store the map in meta.json. The next run compares disk against the
|
|
6
|
+
* stored map and produces:
|
|
7
|
+
* - `changed` — content differs (re-emit DB rows for this file)
|
|
8
|
+
* - `added` — file is new on disk (insert DB rows)
|
|
9
|
+
* - `deleted` — file was in last meta but no longer on disk (drop rows)
|
|
10
|
+
*
|
|
11
|
+
* The pipeline still parses every file (correctness invariant: cross-file
|
|
12
|
+
* resolution needs full data). What this enables is a SELECTIVE DB
|
|
13
|
+
* writeback: instead of wipe-and-reload of the whole graph (~50s of CSV
|
|
14
|
+
* COPY on a 25K-node repo), we only delete-and-rewrite rows for the
|
|
15
|
+
* changed/added/deleted set.
|
|
16
|
+
*
|
|
17
|
+
* See docs/superpowers/specs/2026-05-10-incremental-indexing-design.md
|
|
18
|
+
* (Option B revision).
|
|
19
|
+
*/
|
|
20
|
+
import { createHash } from 'crypto';
|
|
21
|
+
import fs from 'fs/promises';
|
|
22
|
+
import path from 'path';
|
|
23
|
+
/**
|
|
24
|
+
* Compute SHA-256 of a single file. Returns null when the file can't be
|
|
25
|
+
* read — caller treats that as "no signature, assume changed".
|
|
26
|
+
*/
|
|
27
|
+
export const computeFileHash = async (absPath) => {
|
|
28
|
+
try {
|
|
29
|
+
const buf = await fs.readFile(absPath);
|
|
30
|
+
return createHash('sha256').update(buf).digest('hex');
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Compute SHA-256 hashes for many files in parallel batches. Files that
|
|
38
|
+
* fail to read are omitted from the result map.
|
|
39
|
+
*/
|
|
40
|
+
export const computeFileHashes = async (repoPath, relPaths) => {
|
|
41
|
+
const out = new Map();
|
|
42
|
+
const BATCH = 100;
|
|
43
|
+
for (let i = 0; i < relPaths.length; i += BATCH) {
|
|
44
|
+
const batch = relPaths.slice(i, i + BATCH);
|
|
45
|
+
const results = await Promise.all(batch.map(async (rel) => {
|
|
46
|
+
const h = await computeFileHash(path.join(repoPath, rel));
|
|
47
|
+
return h ? [rel, h] : null;
|
|
48
|
+
}));
|
|
49
|
+
for (const r of results)
|
|
50
|
+
if (r)
|
|
51
|
+
out.set(r[0], r[1]);
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Diff a current hash map against a previously stored one.
|
|
57
|
+
*
|
|
58
|
+
* Sorted output so two runs produce identical diff arrays for the same
|
|
59
|
+
* changes — useful for stable logging / equivalence checks.
|
|
60
|
+
*/
|
|
61
|
+
export const diffFileHashes = (current, stored) => {
|
|
62
|
+
const storedMap = new Map(stored ? Object.entries(stored) : []);
|
|
63
|
+
const changed = [];
|
|
64
|
+
const added = [];
|
|
65
|
+
for (const [p, h] of current) {
|
|
66
|
+
const prev = storedMap.get(p);
|
|
67
|
+
if (prev === undefined)
|
|
68
|
+
added.push(p);
|
|
69
|
+
else if (prev !== h)
|
|
70
|
+
changed.push(p);
|
|
71
|
+
}
|
|
72
|
+
const deleted = [];
|
|
73
|
+
for (const p of storedMap.keys()) {
|
|
74
|
+
if (!current.has(p))
|
|
75
|
+
deleted.push(p);
|
|
76
|
+
}
|
|
77
|
+
changed.sort();
|
|
78
|
+
added.sort();
|
|
79
|
+
deleted.sort();
|
|
80
|
+
return {
|
|
81
|
+
changed,
|
|
82
|
+
added,
|
|
83
|
+
deleted,
|
|
84
|
+
toWrite: [...changed, ...added].sort(),
|
|
85
|
+
};
|
|
86
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chunk-level content-addressed parse cache.
|
|
3
|
+
*
|
|
4
|
+
* The pipeline always parses every file (correctness invariant: cross-file
|
|
5
|
+
* resolution and downstream phases need full graph data). What this cache
|
|
6
|
+
* does is skip the tree-sitter worker dispatch when a chunk's contents
|
|
7
|
+
* haven't changed since the last run.
|
|
8
|
+
*
|
|
9
|
+
* Granularity: chunk-level. The parse phase chunks files into ~20MB byte
|
|
10
|
+
* budgets. The cache key is `sha256(joined(filePath:contentHash for each
|
|
11
|
+
* file in the chunk, sorted))`. A change to a single file invalidates only
|
|
12
|
+
* that file's chunk — typically 1 of ~50 chunks on a 1000-file repo.
|
|
13
|
+
*
|
|
14
|
+
* Why not per-file:
|
|
15
|
+
* - Workers process sub-batches and emit aggregated `ParseWorkerResult`s.
|
|
16
|
+
* Splitting back to per-file would require reworking the worker contract.
|
|
17
|
+
* - Chunk-level invalidation gives a useful speedup floor (98% on a single
|
|
18
|
+
* 1-of-50 invalidated chunk) without touching the worker.
|
|
19
|
+
*
|
|
20
|
+
* Survives `--force` because it's content-addressed: the same bytes always
|
|
21
|
+
* produce the same key. `--force` only matters for the LadybugDB writeback;
|
|
22
|
+
* the cache itself is always safe to reuse.
|
|
23
|
+
*/
|
|
24
|
+
import type { ParseWorkerResult } from '../core/ingestion/workers/parse-worker.js';
|
|
25
|
+
export declare const PARSE_CACHE_VERSION: string;
|
|
26
|
+
/** Runtime view: keyed Map for fast lookup; mutated in place during a run. */
|
|
27
|
+
export interface ParseCache {
|
|
28
|
+
version: string;
|
|
29
|
+
entries: Map<string, ParseWorkerResult[]>;
|
|
30
|
+
/**
|
|
31
|
+
* Hashes referenced (hit OR miss-and-stored) by the current run.
|
|
32
|
+
* The parse phase populates this as it processes chunks; the orchestrator
|
|
33
|
+
* uses it as input to `pruneCache` before saving so entries that no
|
|
34
|
+
* longer correspond to any chunk in the current scan are discarded.
|
|
35
|
+
* Transient — never serialized to disk.
|
|
36
|
+
*/
|
|
37
|
+
usedKeys: Set<string>;
|
|
38
|
+
}
|
|
39
|
+
/** Stable hash of a single file's contents — used by callers to compose a chunk hash. */
|
|
40
|
+
export declare const fileContentHash: (content: Buffer | string) => string;
|
|
41
|
+
/**
|
|
42
|
+
* Compute the canonical cache key for a chunk's contents.
|
|
43
|
+
*
|
|
44
|
+
* `entries` is the list of (filePath, file content hash) for every file
|
|
45
|
+
* in the chunk. We sort by filePath before hashing so chunks composed of
|
|
46
|
+
* the same files in different order produce the same key.
|
|
47
|
+
*/
|
|
48
|
+
export declare const computeChunkHash: (entries: Array<{
|
|
49
|
+
filePath: string;
|
|
50
|
+
contentHash: string;
|
|
51
|
+
}>) => string;
|
|
52
|
+
/**
|
|
53
|
+
* Load the parse cache. Returns an empty cache on any failure (missing
|
|
54
|
+
* file, corrupt JSON, version mismatch). Never throws on a normal load.
|
|
55
|
+
*/
|
|
56
|
+
export declare const loadParseCache: (storagePath: string) => Promise<ParseCache>;
|
|
57
|
+
/**
|
|
58
|
+
* Persist the cache to disk using a temp directory + rename.
|
|
59
|
+
*
|
|
60
|
+
* Writes shards under `${cacheDir}.tmp`, then removes the old `cacheDir` and
|
|
61
|
+
* renames the temp directory into place. There is a crash window after
|
|
62
|
+
* `rm(cacheDir)` and before `rename(tmpDir, cacheDir)` where no cache exists;
|
|
63
|
+
* that is acceptable — `loadParseCache` yields empty and the next run
|
|
64
|
+
* reparses. This is not a single atomic swap of the whole tree, but avoids
|
|
65
|
+
* leaving a half-written shard set visible to readers.
|
|
66
|
+
*/
|
|
67
|
+
export declare const saveParseCache: (storagePath: string, cache: ParseCache) => Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* Drop entries whose hashes are not in `usedHashes`. Called at the end
|
|
70
|
+
* of a run so chunks that no longer correspond to any current chunk
|
|
71
|
+
* don't keep their stale entries forever.
|
|
72
|
+
*/
|
|
73
|
+
export declare const pruneCache: (cache: ParseCache, usedHashes: ReadonlySet<string>) => number;
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chunk-level content-addressed parse cache.
|
|
3
|
+
*
|
|
4
|
+
* The pipeline always parses every file (correctness invariant: cross-file
|
|
5
|
+
* resolution and downstream phases need full graph data). What this cache
|
|
6
|
+
* does is skip the tree-sitter worker dispatch when a chunk's contents
|
|
7
|
+
* haven't changed since the last run.
|
|
8
|
+
*
|
|
9
|
+
* Granularity: chunk-level. The parse phase chunks files into ~20MB byte
|
|
10
|
+
* budgets. The cache key is `sha256(joined(filePath:contentHash for each
|
|
11
|
+
* file in the chunk, sorted))`. A change to a single file invalidates only
|
|
12
|
+
* that file's chunk — typically 1 of ~50 chunks on a 1000-file repo.
|
|
13
|
+
*
|
|
14
|
+
* Why not per-file:
|
|
15
|
+
* - Workers process sub-batches and emit aggregated `ParseWorkerResult`s.
|
|
16
|
+
* Splitting back to per-file would require reworking the worker contract.
|
|
17
|
+
* - Chunk-level invalidation gives a useful speedup floor (98% on a single
|
|
18
|
+
* 1-of-50 invalidated chunk) without touching the worker.
|
|
19
|
+
*
|
|
20
|
+
* Survives `--force` because it's content-addressed: the same bytes always
|
|
21
|
+
* produce the same key. `--force` only matters for the LadybugDB writeback;
|
|
22
|
+
* the cache itself is always safe to reuse.
|
|
23
|
+
*/
|
|
24
|
+
import { createHash } from 'crypto';
|
|
25
|
+
import { createRequire } from 'module';
|
|
26
|
+
import fs from 'fs/promises';
|
|
27
|
+
import path from 'path';
|
|
28
|
+
import { fileURLToPath } from 'url';
|
|
29
|
+
/**
|
|
30
|
+
* Cache version composed of:
|
|
31
|
+
* - A schema bump knob (`SCHEMA_BUMP`) for hand-controlled invalidation
|
|
32
|
+
* when ParseWorkerResult shape or upstream parse semantics change.
|
|
33
|
+
* - The current `gitnexus` npm package version, read at module load.
|
|
34
|
+
* Any release that ships an updated tree-sitter grammar or revised
|
|
35
|
+
* extractor logic implies a version bump in package.json, which
|
|
36
|
+
* automatically invalidates the on-disk cache. Without this, a user
|
|
37
|
+
* running `npm i -g gitnexus@latest` after a parser-affecting
|
|
38
|
+
* release would silently replay pre-upgrade ParseWorkerResults
|
|
39
|
+
* against the new graph schema (Bugbot/Claude review on #1479).
|
|
40
|
+
*
|
|
41
|
+
* On version mismatch, `loadParseCache` returns an empty cache and the
|
|
42
|
+
* next save overwrites the on-disk file with the new version baked in.
|
|
43
|
+
*/
|
|
44
|
+
const SCHEMA_BUMP = 1;
|
|
45
|
+
const GITNEXUS_PKG_VERSION = (() => {
|
|
46
|
+
try {
|
|
47
|
+
// package.json sits at gitnexus/package.json — two levels up from
|
|
48
|
+
// gitnexus/src/storage/parse-cache.ts (or its dist/ equivalent).
|
|
49
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
50
|
+
const candidates = [
|
|
51
|
+
path.join(here, '..', '..', 'package.json'), // src/storage → gitnexus/
|
|
52
|
+
path.join(here, '..', '..', '..', 'package.json'), // dist/storage → gitnexus/
|
|
53
|
+
];
|
|
54
|
+
const requireCJS = createRequire(import.meta.url);
|
|
55
|
+
for (const c of candidates) {
|
|
56
|
+
try {
|
|
57
|
+
const pkg = requireCJS(c);
|
|
58
|
+
if (typeof pkg?.version === 'string')
|
|
59
|
+
return pkg.version;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
/* try next candidate */
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
/* fall through to fallback */
|
|
68
|
+
}
|
|
69
|
+
return '0.0.0-unknown';
|
|
70
|
+
})();
|
|
71
|
+
export const PARSE_CACHE_VERSION = `${SCHEMA_BUMP}+${GITNEXUS_PKG_VERSION}`;
|
|
72
|
+
const LEGACY_CACHE_FILENAME = 'parse-cache.json';
|
|
73
|
+
const CACHE_DIRNAME = 'parse-cache';
|
|
74
|
+
const CACHE_INDEX_FILENAME = 'index.json';
|
|
75
|
+
/** Keys on disk always come from `computeChunkHash` — 64-char lowercase hex. */
|
|
76
|
+
const CHUNK_CACHE_KEY_HEX_RE = /^[a-f0-9]{64}$/;
|
|
77
|
+
const isValidChunkCacheKey = (chunkHash) => CHUNK_CACHE_KEY_HEX_RE.test(chunkHash);
|
|
78
|
+
/** SHA-256 hex of a single string or buffer. */
|
|
79
|
+
const sha256Hex = (input) => createHash('sha256')
|
|
80
|
+
.update(typeof input === 'string' ? Buffer.from(input) : input)
|
|
81
|
+
.digest('hex');
|
|
82
|
+
/** Stable hash of a single file's contents — used by callers to compose a chunk hash. */
|
|
83
|
+
export const fileContentHash = (content) => sha256Hex(content);
|
|
84
|
+
/**
|
|
85
|
+
* Compute the canonical cache key for a chunk's contents.
|
|
86
|
+
*
|
|
87
|
+
* `entries` is the list of (filePath, file content hash) for every file
|
|
88
|
+
* in the chunk. We sort by filePath before hashing so chunks composed of
|
|
89
|
+
* the same files in different order produce the same key.
|
|
90
|
+
*/
|
|
91
|
+
export const computeChunkHash = (entries) => {
|
|
92
|
+
const sorted = [...entries].sort((a, b) => (a.filePath < b.filePath ? -1 : 1));
|
|
93
|
+
const joined = sorted.map((e) => `${e.filePath}:${e.contentHash}`).join('\n');
|
|
94
|
+
return sha256Hex(joined);
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* JSON replacer that round-trips Map/Set instances through plain JSON.
|
|
98
|
+
*
|
|
99
|
+
* `ParseWorkerResult.parsedFiles[*].scopes[*].typeBindings` is a
|
|
100
|
+
* `ReadonlyMap<string, TypeRef>`; without this transform it serializes
|
|
101
|
+
* to `{}` and downstream code that iterates / `.get()`s on it crashes
|
|
102
|
+
* with "is not iterable". Applied symmetrically by `mapReviver` on
|
|
103
|
+
* load so the in-memory shape stays Map-typed.
|
|
104
|
+
*/
|
|
105
|
+
const MAP_TAG = '__$mapEntries$__';
|
|
106
|
+
const SET_TAG = '__$setValues$__';
|
|
107
|
+
const mapReplacer = (_key, value) => {
|
|
108
|
+
if (value instanceof Map)
|
|
109
|
+
return { [MAP_TAG]: Array.from(value.entries()) };
|
|
110
|
+
if (value instanceof Set)
|
|
111
|
+
return { [SET_TAG]: Array.from(value.values()) };
|
|
112
|
+
return value;
|
|
113
|
+
};
|
|
114
|
+
const mapReviver = (_key, value) => {
|
|
115
|
+
if (value && typeof value === 'object') {
|
|
116
|
+
const v = value;
|
|
117
|
+
if (Array.isArray(v[MAP_TAG]))
|
|
118
|
+
return new Map(v[MAP_TAG]);
|
|
119
|
+
if (Array.isArray(v[SET_TAG]))
|
|
120
|
+
return new Set(v[SET_TAG]);
|
|
121
|
+
}
|
|
122
|
+
return value;
|
|
123
|
+
};
|
|
124
|
+
const getLegacyCachePath = (storagePath) => path.join(storagePath, LEGACY_CACHE_FILENAME);
|
|
125
|
+
const getCacheDirPath = (storagePath) => path.join(storagePath, CACHE_DIRNAME);
|
|
126
|
+
const getCacheIndexPath = (storagePath) => path.join(getCacheDirPath(storagePath), CACHE_INDEX_FILENAME);
|
|
127
|
+
const getCacheChunkPath = (storagePath, chunkHash) => path.join(getCacheDirPath(storagePath), `${chunkHash}.json`);
|
|
128
|
+
const loadLegacyParseCache = async (storagePath) => {
|
|
129
|
+
const cachePath = getLegacyCachePath(storagePath);
|
|
130
|
+
try {
|
|
131
|
+
const raw = await fs.readFile(cachePath, 'utf-8');
|
|
132
|
+
const data = JSON.parse(raw, mapReviver);
|
|
133
|
+
if (typeof data !== 'object' ||
|
|
134
|
+
data === null ||
|
|
135
|
+
data.version !== PARSE_CACHE_VERSION ||
|
|
136
|
+
typeof data.entries !== 'object' ||
|
|
137
|
+
data.entries === null) {
|
|
138
|
+
return emptyCache();
|
|
139
|
+
}
|
|
140
|
+
const entries = new Map();
|
|
141
|
+
for (const [k, v] of Object.entries(data.entries)) {
|
|
142
|
+
if (Array.isArray(v))
|
|
143
|
+
entries.set(k, v);
|
|
144
|
+
}
|
|
145
|
+
return { version: PARSE_CACHE_VERSION, entries, usedKeys: new Set() };
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return emptyCache();
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
const loadShardedParseCache = async (storagePath) => {
|
|
152
|
+
const indexPath = getCacheIndexPath(storagePath);
|
|
153
|
+
try {
|
|
154
|
+
const raw = await fs.readFile(indexPath, 'utf-8');
|
|
155
|
+
const data = JSON.parse(raw);
|
|
156
|
+
if (typeof data !== 'object' ||
|
|
157
|
+
data === null ||
|
|
158
|
+
data.version !== PARSE_CACHE_VERSION ||
|
|
159
|
+
!Array.isArray(data.keys)) {
|
|
160
|
+
return emptyCache();
|
|
161
|
+
}
|
|
162
|
+
const entries = new Map();
|
|
163
|
+
for (const chunkHash of data.keys) {
|
|
164
|
+
if (typeof chunkHash !== 'string' || !isValidChunkCacheKey(chunkHash))
|
|
165
|
+
continue;
|
|
166
|
+
try {
|
|
167
|
+
const chunkRaw = await fs.readFile(getCacheChunkPath(storagePath, chunkHash), 'utf-8');
|
|
168
|
+
const chunkData = JSON.parse(chunkRaw, mapReviver);
|
|
169
|
+
if (Array.isArray(chunkData))
|
|
170
|
+
entries.set(chunkHash, chunkData);
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
/* skip corrupt or missing shard */
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return { version: PARSE_CACHE_VERSION, entries, usedKeys: new Set() };
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
/**
|
|
183
|
+
* Load the parse cache. Returns an empty cache on any failure (missing
|
|
184
|
+
* file, corrupt JSON, version mismatch). Never throws on a normal load.
|
|
185
|
+
*/
|
|
186
|
+
export const loadParseCache = async (storagePath) => {
|
|
187
|
+
const sharded = await loadShardedParseCache(storagePath);
|
|
188
|
+
if (sharded)
|
|
189
|
+
return sharded;
|
|
190
|
+
return loadLegacyParseCache(storagePath);
|
|
191
|
+
};
|
|
192
|
+
/**
|
|
193
|
+
* Persist the cache to disk using a temp directory + rename.
|
|
194
|
+
*
|
|
195
|
+
* Writes shards under `${cacheDir}.tmp`, then removes the old `cacheDir` and
|
|
196
|
+
* renames the temp directory into place. There is a crash window after
|
|
197
|
+
* `rm(cacheDir)` and before `rename(tmpDir, cacheDir)` where no cache exists;
|
|
198
|
+
* that is acceptable — `loadParseCache` yields empty and the next run
|
|
199
|
+
* reparses. This is not a single atomic swap of the whole tree, but avoids
|
|
200
|
+
* leaving a half-written shard set visible to readers.
|
|
201
|
+
*/
|
|
202
|
+
export const saveParseCache = async (storagePath, cache) => {
|
|
203
|
+
await fs.mkdir(storagePath, { recursive: true });
|
|
204
|
+
const cacheDir = getCacheDirPath(storagePath);
|
|
205
|
+
const tmpDir = `${cacheDir}.tmp`;
|
|
206
|
+
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
207
|
+
await fs.mkdir(tmpDir, { recursive: true });
|
|
208
|
+
const keys = [];
|
|
209
|
+
for (const [chunkHash, chunkResults] of cache.entries) {
|
|
210
|
+
if (!isValidChunkCacheKey(chunkHash))
|
|
211
|
+
continue;
|
|
212
|
+
let payload;
|
|
213
|
+
try {
|
|
214
|
+
payload = JSON.stringify(chunkResults, mapReplacer);
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
// Extremely dense chunks could theoretically exceed string limits; skip
|
|
218
|
+
// rather than failing the entire save (orchestrator catches save errors).
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
keys.push(chunkHash);
|
|
222
|
+
const chunkPath = path.join(tmpDir, `${chunkHash}.json`);
|
|
223
|
+
await fs.writeFile(chunkPath, payload, 'utf-8');
|
|
224
|
+
}
|
|
225
|
+
const index = {
|
|
226
|
+
version: cache.version,
|
|
227
|
+
keys,
|
|
228
|
+
};
|
|
229
|
+
await fs.writeFile(path.join(tmpDir, CACHE_INDEX_FILENAME), JSON.stringify(index), 'utf-8');
|
|
230
|
+
await fs.rm(cacheDir, { recursive: true, force: true });
|
|
231
|
+
await fs.rename(tmpDir, cacheDir);
|
|
232
|
+
await fs.rm(getLegacyCachePath(storagePath), { force: true });
|
|
233
|
+
};
|
|
234
|
+
/**
|
|
235
|
+
* Drop entries whose hashes are not in `usedHashes`. Called at the end
|
|
236
|
+
* of a run so chunks that no longer correspond to any current chunk
|
|
237
|
+
* don't keep their stale entries forever.
|
|
238
|
+
*/
|
|
239
|
+
export const pruneCache = (cache, usedHashes) => {
|
|
240
|
+
let removed = 0;
|
|
241
|
+
for (const k of cache.entries.keys()) {
|
|
242
|
+
if (!usedHashes.has(k)) {
|
|
243
|
+
cache.entries.delete(k);
|
|
244
|
+
removed++;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return removed;
|
|
248
|
+
};
|
|
249
|
+
const emptyCache = () => ({
|
|
250
|
+
version: PARSE_CACHE_VERSION,
|
|
251
|
+
entries: new Map(),
|
|
252
|
+
usedKeys: new Set(),
|
|
253
|
+
});
|
|
@@ -56,7 +56,38 @@ export interface RepoMeta {
|
|
|
56
56
|
processes?: number;
|
|
57
57
|
embeddings?: number;
|
|
58
58
|
};
|
|
59
|
+
/**
|
|
60
|
+
* Bumped whenever incremental-indexing invariants change in an
|
|
61
|
+
* incompatible way (delete-and-rewrite logic, subgraph extraction,
|
|
62
|
+
* graph-wide node handling). On mismatch, runFullAnalysis forces a
|
|
63
|
+
* full rebuild rather than risk an inconsistent incremental update.
|
|
64
|
+
*/
|
|
65
|
+
schemaVersion?: number;
|
|
66
|
+
/**
|
|
67
|
+
* SHA-256 of every file's content at the time of the last successful
|
|
68
|
+
* indexing run. The next run computes current hashes and diffs against
|
|
69
|
+
* this map to determine which files' DB rows must be replaced.
|
|
70
|
+
* Map keys are repo-relative paths.
|
|
71
|
+
*/
|
|
72
|
+
fileHashes?: Record<string, string>;
|
|
73
|
+
/**
|
|
74
|
+
* Crash-recovery dirty flag. Written to meta.json BEFORE any
|
|
75
|
+
* destructive DB mutation in an incremental run; cleared on success
|
|
76
|
+
* by overwriting meta.json. If a run crashes between, the next run
|
|
77
|
+
* sees the flag and forces a full rebuild — the cheapest path back
|
|
78
|
+
* to a known-good index.
|
|
79
|
+
*/
|
|
80
|
+
incrementalInProgress?: {
|
|
81
|
+
/** When the incremental run started (epoch ms). */
|
|
82
|
+
startedAt: number;
|
|
83
|
+
/** Number of files in the writable set, for diagnostic logs. */
|
|
84
|
+
toWriteCount: number;
|
|
85
|
+
};
|
|
59
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Bumped whenever incremental-indexing invariants change incompatibly.
|
|
89
|
+
*/
|
|
90
|
+
export declare const INCREMENTAL_SCHEMA_VERSION = 1;
|
|
60
91
|
export interface IndexedRepo {
|
|
61
92
|
repoPath: string;
|
|
62
93
|
storagePath: string;
|
|
@@ -112,7 +143,16 @@ export declare const cleanupOldKuzuFiles: (storagePath: string) => Promise<{
|
|
|
112
143
|
*/
|
|
113
144
|
export declare const loadMeta: (storagePath: string) => Promise<RepoMeta | null>;
|
|
114
145
|
/**
|
|
115
|
-
* Save metadata to storage
|
|
146
|
+
* Save metadata to storage.
|
|
147
|
+
*
|
|
148
|
+
* Atomic via tmp-file + rename (matches `saveParseCache`'s pattern). The
|
|
149
|
+
* `incrementalInProgress` dirty flag travels through this file — a crash
|
|
150
|
+
* mid-write would leave a corrupt `meta.json` that the next run's
|
|
151
|
+
* `loadMeta` would silently treat as "no prior index", losing the dirty
|
|
152
|
+
* flag and skipping the recovery full-rebuild. Write-and-rename rules
|
|
153
|
+
* that out: the rename is atomic on POSIX and on Windows (`fs.rename`
|
|
154
|
+
* on `node:fs/promises` uses `MoveFileEx(REPLACE_EXISTING)`), so either
|
|
155
|
+
* the old or the new file is observed at every moment.
|
|
116
156
|
*/
|
|
117
157
|
export declare const saveMeta: (storagePath: string, meta: RepoMeta) => Promise<void>;
|
|
118
158
|
/**
|