gitnexus 1.6.5-rc.2 → 1.6.5-rc.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/README.md +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/cli/ai-context.d.ts +1 -0
- package/dist/cli/ai-context.js +30 -12
- package/dist/cli/analyze.d.ts +18 -0
- package/dist/cli/analyze.js +33 -4
- package/dist/cli/augment.d.ts +1 -1
- package/dist/cli/augment.js +1 -1
- package/dist/cli/index.js +6 -1
- package/dist/cli/setup.js +6 -0
- package/dist/core/augmentation/engine.d.ts +2 -2
- package/dist/core/augmentation/engine.js +26 -6
- package/dist/core/embeddings/ast-utils.js +2 -1
- package/dist/core/embeddings/http-client.js +22 -6
- package/dist/core/group/extractors/grpc-extractor.js +2 -1
- package/dist/core/group/extractors/http-patterns/python.js +175 -0
- package/dist/core/group/extractors/http-route-extractor.js +2 -1
- package/dist/core/group/extractors/include-extractor.js +2 -1
- package/dist/core/group/extractors/thrift-extractor.js +2 -1
- package/dist/core/group/extractors/tree-sitter-scanner.js +2 -1
- 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.js +166 -47
- package/dist/core/ingestion/community-processor.js +18 -0
- package/dist/core/ingestion/heritage-processor.js +3 -2
- package/dist/core/ingestion/import-processor.js +2 -1
- package/dist/core/ingestion/import-resolvers/standard.d.ts +5 -0
- package/dist/core/ingestion/import-resolvers/standard.js +27 -1
- package/dist/core/ingestion/import-resolvers/utils.js +4 -0
- package/dist/core/ingestion/languages/c/arity-metadata.d.ts +14 -0
- package/dist/core/ingestion/languages/c/arity-metadata.js +94 -0
- package/dist/core/ingestion/languages/c/arity.d.ts +6 -0
- package/dist/core/ingestion/languages/c/arity.js +18 -0
- package/dist/core/ingestion/languages/c/captures.d.ts +2 -0
- package/dist/core/ingestion/languages/c/captures.js +105 -0
- package/dist/core/ingestion/languages/c/header-scan.d.ts +7 -0
- package/dist/core/ingestion/languages/c/header-scan.js +55 -0
- package/dist/core/ingestion/languages/c/import-decomposer.d.ts +8 -0
- package/dist/core/ingestion/languages/c/import-decomposer.js +50 -0
- package/dist/core/ingestion/languages/c/import-target.d.ts +14 -0
- package/dist/core/ingestion/languages/c/import-target.js +57 -0
- package/dist/core/ingestion/languages/c/index.d.ts +11 -0
- package/dist/core/ingestion/languages/c/index.js +11 -0
- package/dist/core/ingestion/languages/c/interpret.d.ts +14 -0
- package/dist/core/ingestion/languages/c/interpret.js +48 -0
- package/dist/core/ingestion/languages/c/merge-bindings.d.ts +7 -0
- package/dist/core/ingestion/languages/c/merge-bindings.js +23 -0
- package/dist/core/ingestion/languages/c/query.d.ts +3 -0
- package/dist/core/ingestion/languages/c/query.js +161 -0
- package/dist/core/ingestion/languages/c/scope-resolver.d.ts +13 -0
- package/dist/core/ingestion/languages/c/scope-resolver.js +60 -0
- package/dist/core/ingestion/languages/c/simple-hooks.d.ts +14 -0
- package/dist/core/ingestion/languages/c/simple-hooks.js +19 -0
- package/dist/core/ingestion/languages/c/static-linkage.d.ts +13 -0
- package/dist/core/ingestion/languages/c/static-linkage.js +57 -0
- package/dist/core/ingestion/languages/c-cpp.js +10 -0
- package/dist/core/ingestion/languages/csharp/captures.js +2 -1
- package/dist/core/ingestion/languages/csharp/namespace-siblings.js +3 -2
- package/dist/core/ingestion/languages/go/captures.js +2 -1
- package/dist/core/ingestion/languages/go/range-binding.js +2 -1
- 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/languages/python/captures.js +2 -1
- package/dist/core/ingestion/languages/typescript/captures.js +2 -1
- package/dist/core/ingestion/parsing-processor.d.ts +22 -2
- package/dist/core/ingestion/parsing-processor.js +85 -50
- 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-resolution/contract/scope-resolver.d.ts +65 -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 +5 -0
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +25 -1
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +79 -7
- package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.d.ts +7 -3
- package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.js +24 -4
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +21 -2
- 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 +31 -7
- package/dist/core/ingestion/tree-sitter-queries.d.ts +1 -1
- package/dist/core/ingestion/tree-sitter-queries.js +10 -0
- package/dist/core/ingestion/workers/parse-worker.js +2 -1
- package/dist/core/lbug/lbug-adapter.d.ts +26 -0
- package/dist/core/lbug/lbug-adapter.js +174 -40
- package/dist/core/lbug/pool-adapter.js +19 -2
- package/dist/core/run-analyze.d.ts +2 -0
- package/dist/core/run-analyze.js +323 -29
- package/dist/core/tree-sitter/safe-parse.d.ts +6 -0
- package/dist/core/tree-sitter/safe-parse.js +32 -0
- package/dist/server/git-clone.js +10 -13
- package/dist/storage/file-hash.d.ts +47 -0
- package/dist/storage/file-hash.js +86 -0
- package/dist/storage/git.d.ts +17 -6
- package/dist/storage/git.js +46 -12
- package/dist/storage/parse-cache.d.ts +67 -0
- package/dist/storage/parse-cache.js +182 -0
- package/dist/storage/repo-manager.d.ts +41 -1
- package/dist/storage/repo-manager.js +17 -2
- package/hooks/claude/gitnexus-hook.cjs +8 -1
- package/hooks/claude/hook-lock.cjs +119 -0
- package/package.json +1 -1
- package/web/assets/{agent-BpHQleHE.js → agent-BG6RCOzz.js} +1 -1
- package/web/assets/architecture-7EHR7CIX-6QZW5X65-F-tujre-.js +1 -0
- package/web/assets/architectureDiagram-UL44E2DR-2GL7nOvO.js +36 -0
- package/web/assets/blockDiagram-7IZFK4PR-5AjX0pPU.js +132 -0
- package/web/assets/{c4Diagram-DFAF54RM-DeJV1rCn.js → c4Diagram-Y2BXMSZH-hLOeq7xC.js} +2 -2
- package/web/assets/chunk-2T2R6R2M-B_pq_2nf.js +4 -0
- package/web/assets/chunk-2UTLFMKG-CpoohCFN.js +1 -0
- package/web/assets/{chunk-7RZVMHOQ-BdIU-RGO.js → chunk-3SSMPTDK-CfqpR3Nr.js} +1 -1
- package/web/assets/chunk-4R4BOZG6-D3gm7tYj.js +159 -0
- package/web/assets/chunk-6764PJDD-WcI965eO.js +1 -0
- package/web/assets/chunk-AQ6EADP3-BpMj3rQl.js +1 -0
- package/web/assets/{chunk-KSICW3F5-C2tZmXwv.js → chunk-AZZRMDJM-DSoCJlUA.js} +1 -1
- package/web/assets/chunk-C62D2QBJ-CvFF2PnB.js +1 -0
- package/web/assets/chunk-CEXFNPSA-DdNtJtrF.js +1 -0
- package/web/assets/chunk-J5EP6P6S-DtcVaAJI.js +1 -0
- package/web/assets/{chunk-AEOMTBSW-D7qjBMHW.js → chunk-JQRUD6KW-DElaNjc0.js} +1 -1
- package/web/assets/chunk-KGYTTC2M-D0K5or7e.js +1 -0
- package/web/assets/chunk-KRXBNO2N-DpR8iDt8.js +1 -0
- package/web/assets/chunk-LCXTWHL2-DdMObOEk.js +231 -0
- package/web/assets/chunk-LII3EMHJ-I78zXtzi.js +1 -0
- package/web/assets/chunk-RERM46MO-Betu1cSy.js +1 -0
- package/web/assets/chunk-RG4AUYOV-BUCJ2JP5.js +206 -0
- package/web/assets/chunk-RKZBBQEN-Cutpza0t.js +1 -0
- package/web/assets/chunk-RLI5ZMPA-BFUxCvX2.js +1 -0
- package/web/assets/chunk-T5OCTHI4-DKwlkuvB.js +1 -0
- package/web/assets/chunk-UP6H54XL-0YWa0NDg.js +1 -0
- package/web/assets/chunk-UXSXWOXI-CSz0OXpz.js +1 -0
- package/web/assets/chunk-W44A43WB-AOmgoT-X.js +1 -0
- package/web/assets/{chunk-RWUO3TPN-DTWzAhQ1.js → chunk-ZXARS5L4-DIzgxjMw.js} +1 -1
- package/web/assets/classDiagram-KGZ6W3CR-BT-rn7LF.js +1 -0
- package/web/assets/classDiagram-v2-72OJOZXJ-uR3nDadY.js +1 -0
- package/web/assets/{cose-bilkent-PNC4W37J-DdZq7Qz_.js → cose-bilkent-UX7MHV2Q-DCSCD1TK.js} +1 -1
- package/web/assets/dagre-ND4H6XIP-CsciHSij.js +4 -0
- package/web/assets/diagram-3NCE3AQN-BKUT5Rpc.js +43 -0
- package/web/assets/diagram-GF46GFSD-Dxb4hZSo.js +24 -0
- package/web/assets/diagram-HNR7UZ2L-D3qus9Cn.js +3 -0
- package/web/assets/diagram-QXG6HAR7-r9BMLGGP.js +24 -0
- package/web/assets/diagram-WEQXMOUZ-BGXqs7-m.js +10 -0
- package/web/assets/{erDiagram-GCSMX5X6-DfdN6iqF.js → erDiagram-L5TCEMPS-LgSWVrkZ.js} +2 -2
- package/web/assets/eventmodeling-FCH6USID-MREXMVOE-By7r3bla.js +1 -0
- package/web/assets/flowDiagram-H6V6AXG4-Bj_vV5ky.js +162 -0
- package/web/assets/ganttDiagram-JCBTUEKG-B1eVITSh.js +292 -0
- package/web/assets/gitGraph-WXDBUCRP-R675I2BI-ClEP6RdE.js +1 -0
- package/web/assets/gitGraphDiagram-S2ZK5IYY-BP0R-_FD.js +106 -0
- package/web/assets/{index-3NUCUW2s.js → index-oL-ZNYwb.js} +80 -81
- package/web/assets/info-J43DQDTF-KCYPFFUO-bBJRjmP5.js +1 -0
- package/web/assets/infoDiagram-3YFTVSEB-Dmj4OEO3.js +2 -0
- package/web/assets/{ishikawaDiagram-YMYX4NHK-BF8AtDaj.js → ishikawaDiagram-BNXS4ZKH-B3NgQ4lJ.js} +2 -2
- package/web/assets/{journeyDiagram-SO5T7YLQ-BW8oKa3N.js → journeyDiagram-M6C3CM3L-DALmL4_N.js} +3 -3
- package/web/assets/{kanban-definition-LJHFXRCJ-DzbOsR-2.js → kanban-definition-75IXJCU3-AvVbHpnN.js} +4 -4
- package/web/assets/{katex-GD7MH7QM-CJiOjBBJ.js → katex-K3KEBU37-CqF6_ZN-.js} +1 -1
- package/web/assets/{mindmap-definition-2EUWGEK5-BIXGQALq.js → mindmap-definition-2TDM6QVE-B5c9VOzs.js} +28 -28
- package/web/assets/packet-YPE3B663-LP52Z2RK-1ZCldnu3.js +1 -0
- package/web/assets/pie-LRSECV5Y-TCRJHUBD-EhjHC5EE.js +1 -0
- package/web/assets/pieDiagram-CU6KROY3-Po2HSSR4.js +30 -0
- package/web/assets/quadrantDiagram-VICAPDV7-C47UxSZD.js +7 -0
- package/web/assets/radar-GUYGQ44K-RDLRG3WG-CvXjgxlW.js +1 -0
- package/web/assets/{requirementDiagram-M5DCFWZL-CzUUrhQD.js → requirementDiagram-JXO7QTGE-CpECN_Hq.js} +6 -6
- package/web/assets/sankeyDiagram-URQDO5SZ-B3b7cLrS.js +40 -0
- package/web/assets/sequenceDiagram-VS2MUI6T-CRJ95oVP.js +162 -0
- package/web/assets/stateDiagram-7D4R322I-C7U9b3ks.js +1 -0
- package/web/assets/stateDiagram-v2-36443NZ5-BQajbtov.js +1 -0
- package/web/assets/{timeline-definition-5SPVSISX-DkMiGj4Z.js → timeline-definition-O6YCAMPW-rtbk6BBo.js} +4 -4
- package/web/assets/treeView-BLDUP644-QA4HXRO3-R2o5gwfP.js +1 -0
- package/web/assets/treemap-LRROVOQU-LLAWBHMP-Cesmac_u.js +1 -0
- package/web/assets/{vennDiagram-IE5QUKF5-BCxMmf9q.js → vennDiagram-MWXL3ELB-W8Yoaayh.js} +3 -3
- package/web/assets/wardley-L42UT6IY-5TKZOOLJ-B9HYoRgA.js +1 -0
- package/web/assets/wardleyDiagram-CUQ6CDDI-DIZw5Abj.js +78 -0
- package/web/assets/{xychartDiagram-ZHJ5623Y-DZ7fP1_3.js → xychartDiagram-N2JHSOCM-DGRtrj4P.js} +3 -3
- package/web/index.html +2 -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-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/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/flowDiagram-OTCZ4VVT-aAZw4c_M.js +0 -162
- 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/info-OMHHGYJF-BF2H5H6G-BIOsZy0W.js +0 -1
- package/web/assets/infoDiagram-MN7RKWGX-DUoMH-pq.js +0 -2
- 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/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/treeView-SZITEDCU-5DXDK3XO-Cy6P0_CZ.js +0 -1
- package/web/assets/treemap-W4RFUUIX-WYLRDWKO-C_pp1R7A.js +0 -1
- 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-CG-ydtp4.js} +0 -0
package/dist/storage/git.d.ts
CHANGED
|
@@ -108,13 +108,24 @@ export declare const hasGitDir: (dirPath: string) => boolean;
|
|
|
108
108
|
*/
|
|
109
109
|
export declare const getRemoteOriginUrl: (repoPath: string) => string | null;
|
|
110
110
|
/**
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
* `git://`, `ssh://`, and `file://` shapes. Returns `null` for empty /
|
|
114
|
-
* unparseable input.
|
|
111
|
+
* Sanitize a repository name to prevent argument injection and ensure
|
|
112
|
+
* cross-platform filesystem compatibility.
|
|
115
113
|
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
114
|
+
* 1. Strips leading dashes to prevent git command-line argument injection
|
|
115
|
+
* (e.g., --upload-pack=evil).
|
|
116
|
+
* 2. Replaces characters that are unsafe for directory names across
|
|
117
|
+
* platforms (Windows/macOS/Linux) with underscores.
|
|
118
|
+
* 3. Blocks path traversal segments ("." and "..") and Windows reserved
|
|
119
|
+
* names (e.g., CON, NUL) to prevent directory escape.
|
|
120
|
+
*/
|
|
121
|
+
export declare const sanitizeRepoName: (name: string) => string;
|
|
122
|
+
/**
|
|
123
|
+
* Parse a repository name out of a git remote URL. Handles common shapes
|
|
124
|
+
* including SSH (git@host:owner/repo.git) and HTTPS (https://host/owner/repo.git).
|
|
125
|
+
*
|
|
126
|
+
* Returns a sanitized, filesystem-safe name or null if no name could be inferred.
|
|
127
|
+
* Returning null (rather than 'unknown') allows callers to use ?? null-coalescing
|
|
128
|
+
* for fallbacks without risk of registry collisions on 'unknown'.
|
|
118
129
|
*/
|
|
119
130
|
export declare const parseRepoNameFromUrl: (url: string | null | undefined) => string | null;
|
|
120
131
|
/**
|
package/dist/storage/git.js
CHANGED
|
@@ -257,13 +257,36 @@ export const getRemoteOriginUrl = (repoPath) => {
|
|
|
257
257
|
}
|
|
258
258
|
};
|
|
259
259
|
/**
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
* `git://`, `ssh://`, and `file://` shapes. Returns `null` for empty /
|
|
263
|
-
* unparseable input.
|
|
260
|
+
* Sanitize a repository name to prevent argument injection and ensure
|
|
261
|
+
* cross-platform filesystem compatibility.
|
|
264
262
|
*
|
|
265
|
-
*
|
|
266
|
-
*
|
|
263
|
+
* 1. Strips leading dashes to prevent git command-line argument injection
|
|
264
|
+
* (e.g., --upload-pack=evil).
|
|
265
|
+
* 2. Replaces characters that are unsafe for directory names across
|
|
266
|
+
* platforms (Windows/macOS/Linux) with underscores.
|
|
267
|
+
* 3. Blocks path traversal segments ("." and "..") and Windows reserved
|
|
268
|
+
* names (e.g., CON, NUL) to prevent directory escape.
|
|
269
|
+
*/
|
|
270
|
+
export const sanitizeRepoName = (name) => {
|
|
271
|
+
// 1. Prevent argument injection by stripping leading dashes.
|
|
272
|
+
// 2. Remove characters that are not alphanumerics, dots, underscores, or dashes.
|
|
273
|
+
const sanitized = name.replace(/^-+/, '').replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
274
|
+
// 3. Block path traversal segments and Windows reserved names.
|
|
275
|
+
// Windows reserved names like CON, PRN, AUX, NUL, COM1-9, LPT1-9 cannot
|
|
276
|
+
// be used as directory names on Windows even if they have an extension.
|
|
277
|
+
const reserved = /^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(\..*)?$/i;
|
|
278
|
+
if (!sanitized || sanitized === '.' || sanitized === '..' || reserved.test(sanitized)) {
|
|
279
|
+
return 'unknown';
|
|
280
|
+
}
|
|
281
|
+
return sanitized;
|
|
282
|
+
};
|
|
283
|
+
/**
|
|
284
|
+
* Parse a repository name out of a git remote URL. Handles common shapes
|
|
285
|
+
* including SSH (git@host:owner/repo.git) and HTTPS (https://host/owner/repo.git).
|
|
286
|
+
*
|
|
287
|
+
* Returns a sanitized, filesystem-safe name or null if no name could be inferred.
|
|
288
|
+
* Returning null (rather than 'unknown') allows callers to use ?? null-coalescing
|
|
289
|
+
* for fallbacks without risk of registry collisions on 'unknown'.
|
|
267
290
|
*/
|
|
268
291
|
export const parseRepoNameFromUrl = (url) => {
|
|
269
292
|
if (!url)
|
|
@@ -271,12 +294,23 @@ export const parseRepoNameFromUrl = (url) => {
|
|
|
271
294
|
const trimmed = url.trim();
|
|
272
295
|
if (!trimmed)
|
|
273
296
|
return null;
|
|
274
|
-
// Strip
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
297
|
+
// Strip trailing slashes without a regex to avoid polynomial-ReDoS on
|
|
298
|
+
// pathological inputs like `https://x.com/y` + '/'.repeat(1e6).
|
|
299
|
+
let end = trimmed.length;
|
|
300
|
+
while (end > 0 && trimmed.charCodeAt(end - 1) === 47 /* '/' */)
|
|
301
|
+
end--;
|
|
302
|
+
let cleaned = trimmed.slice(0, end);
|
|
303
|
+
// Strip trailing .git (case-insensitive)
|
|
304
|
+
if (cleaned.toLowerCase().endsWith('.git')) {
|
|
305
|
+
cleaned = cleaned.slice(0, -4);
|
|
306
|
+
}
|
|
307
|
+
// Last path segment, handling colons for SSH URLs and path traversal.
|
|
308
|
+
// Split on both / and : to consistently extract the last part.
|
|
309
|
+
const candidate = cleaned.split(/[/:]/).pop() || '';
|
|
310
|
+
if (!candidate)
|
|
311
|
+
return null;
|
|
312
|
+
const safe = sanitizeRepoName(candidate);
|
|
313
|
+
return safe === 'unknown' ? null : safe;
|
|
280
314
|
};
|
|
281
315
|
/**
|
|
282
316
|
* Convenience wrapper: derive a registry-friendly name from the repo's
|
|
@@ -0,0 +1,67 @@
|
|
|
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 atomically (write-and-rename) so a crash
|
|
59
|
+
* mid-write doesn't leave a corrupt file.
|
|
60
|
+
*/
|
|
61
|
+
export declare const saveParseCache: (storagePath: string, cache: ParseCache) => Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Drop entries whose hashes are not in `usedHashes`. Called at the end
|
|
64
|
+
* of a run so chunks that no longer correspond to any current chunk
|
|
65
|
+
* don't keep their stale entries forever.
|
|
66
|
+
*/
|
|
67
|
+
export declare const pruneCache: (cache: ParseCache, usedHashes: ReadonlySet<string>) => number;
|
|
@@ -0,0 +1,182 @@
|
|
|
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 CACHE_FILENAME = 'parse-cache.json';
|
|
73
|
+
/** SHA-256 hex of a single string or buffer. */
|
|
74
|
+
const sha256Hex = (input) => createHash('sha256')
|
|
75
|
+
.update(typeof input === 'string' ? Buffer.from(input) : input)
|
|
76
|
+
.digest('hex');
|
|
77
|
+
/** Stable hash of a single file's contents — used by callers to compose a chunk hash. */
|
|
78
|
+
export const fileContentHash = (content) => sha256Hex(content);
|
|
79
|
+
/**
|
|
80
|
+
* Compute the canonical cache key for a chunk's contents.
|
|
81
|
+
*
|
|
82
|
+
* `entries` is the list of (filePath, file content hash) for every file
|
|
83
|
+
* in the chunk. We sort by filePath before hashing so chunks composed of
|
|
84
|
+
* the same files in different order produce the same key.
|
|
85
|
+
*/
|
|
86
|
+
export const computeChunkHash = (entries) => {
|
|
87
|
+
const sorted = [...entries].sort((a, b) => (a.filePath < b.filePath ? -1 : 1));
|
|
88
|
+
const joined = sorted.map((e) => `${e.filePath}:${e.contentHash}`).join('\n');
|
|
89
|
+
return sha256Hex(joined);
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* JSON replacer that round-trips Map/Set instances through plain JSON.
|
|
93
|
+
*
|
|
94
|
+
* `ParseWorkerResult.parsedFiles[*].scopes[*].typeBindings` is a
|
|
95
|
+
* `ReadonlyMap<string, TypeRef>`; without this transform it serializes
|
|
96
|
+
* to `{}` and downstream code that iterates / `.get()`s on it crashes
|
|
97
|
+
* with "is not iterable". Applied symmetrically by `mapReviver` on
|
|
98
|
+
* load so the in-memory shape stays Map-typed.
|
|
99
|
+
*/
|
|
100
|
+
const MAP_TAG = '__$mapEntries$__';
|
|
101
|
+
const SET_TAG = '__$setValues$__';
|
|
102
|
+
const mapReplacer = (_key, value) => {
|
|
103
|
+
if (value instanceof Map)
|
|
104
|
+
return { [MAP_TAG]: Array.from(value.entries()) };
|
|
105
|
+
if (value instanceof Set)
|
|
106
|
+
return { [SET_TAG]: Array.from(value.values()) };
|
|
107
|
+
return value;
|
|
108
|
+
};
|
|
109
|
+
const mapReviver = (_key, value) => {
|
|
110
|
+
if (value && typeof value === 'object') {
|
|
111
|
+
const v = value;
|
|
112
|
+
if (Array.isArray(v[MAP_TAG]))
|
|
113
|
+
return new Map(v[MAP_TAG]);
|
|
114
|
+
if (Array.isArray(v[SET_TAG]))
|
|
115
|
+
return new Set(v[SET_TAG]);
|
|
116
|
+
}
|
|
117
|
+
return value;
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* Load the parse cache. Returns an empty cache on any failure (missing
|
|
121
|
+
* file, corrupt JSON, version mismatch). Never throws on a normal load.
|
|
122
|
+
*/
|
|
123
|
+
export const loadParseCache = async (storagePath) => {
|
|
124
|
+
const cachePath = path.join(storagePath, CACHE_FILENAME);
|
|
125
|
+
try {
|
|
126
|
+
const raw = await fs.readFile(cachePath, 'utf-8');
|
|
127
|
+
const data = JSON.parse(raw, mapReviver);
|
|
128
|
+
if (typeof data !== 'object' ||
|
|
129
|
+
data === null ||
|
|
130
|
+
data.version !== PARSE_CACHE_VERSION ||
|
|
131
|
+
typeof data.entries !== 'object' ||
|
|
132
|
+
data.entries === null) {
|
|
133
|
+
return emptyCache();
|
|
134
|
+
}
|
|
135
|
+
const entries = new Map();
|
|
136
|
+
for (const [k, v] of Object.entries(data.entries)) {
|
|
137
|
+
if (Array.isArray(v))
|
|
138
|
+
entries.set(k, v);
|
|
139
|
+
}
|
|
140
|
+
return { version: PARSE_CACHE_VERSION, entries, usedKeys: new Set() };
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return emptyCache();
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
/**
|
|
147
|
+
* Persist the cache to disk atomically (write-and-rename) so a crash
|
|
148
|
+
* mid-write doesn't leave a corrupt file.
|
|
149
|
+
*/
|
|
150
|
+
export const saveParseCache = async (storagePath, cache) => {
|
|
151
|
+
await fs.mkdir(storagePath, { recursive: true });
|
|
152
|
+
const cachePath = path.join(storagePath, CACHE_FILENAME);
|
|
153
|
+
const tmpPath = `${cachePath}.tmp`;
|
|
154
|
+
const out = {
|
|
155
|
+
version: cache.version,
|
|
156
|
+
entries: Object.fromEntries(cache.entries),
|
|
157
|
+
};
|
|
158
|
+
// Compact JSON; this file can be tens of MB on a large repo and pretty-
|
|
159
|
+
// printing roughly doubles size for no value.
|
|
160
|
+
await fs.writeFile(tmpPath, JSON.stringify(out, mapReplacer), 'utf-8');
|
|
161
|
+
await fs.rename(tmpPath, cachePath);
|
|
162
|
+
};
|
|
163
|
+
/**
|
|
164
|
+
* Drop entries whose hashes are not in `usedHashes`. Called at the end
|
|
165
|
+
* of a run so chunks that no longer correspond to any current chunk
|
|
166
|
+
* don't keep their stale entries forever.
|
|
167
|
+
*/
|
|
168
|
+
export const pruneCache = (cache, usedHashes) => {
|
|
169
|
+
let removed = 0;
|
|
170
|
+
for (const k of cache.entries.keys()) {
|
|
171
|
+
if (!usedHashes.has(k)) {
|
|
172
|
+
cache.entries.delete(k);
|
|
173
|
+
removed++;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return removed;
|
|
177
|
+
};
|
|
178
|
+
const emptyCache = () => ({
|
|
179
|
+
version: PARSE_CACHE_VERSION,
|
|
180
|
+
entries: new Map(),
|
|
181
|
+
usedKeys: new Set(),
|
|
182
|
+
});
|
|
@@ -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
|
/**
|
|
@@ -49,6 +49,10 @@ export const canonicalizePath = (p) => {
|
|
|
49
49
|
return resolved;
|
|
50
50
|
}
|
|
51
51
|
};
|
|
52
|
+
/**
|
|
53
|
+
* Bumped whenever incremental-indexing invariants change incompatibly.
|
|
54
|
+
*/
|
|
55
|
+
export const INCREMENTAL_SCHEMA_VERSION = 1;
|
|
52
56
|
const GITNEXUS_DIR = '.gitnexus';
|
|
53
57
|
const GITNEXUS_EXCLUDE_ENTRY = `${GITNEXUS_DIR}/`;
|
|
54
58
|
// ─── Local Storage Helpers ─────────────────────────────────────────────
|
|
@@ -137,12 +141,23 @@ export const loadMeta = async (storagePath) => {
|
|
|
137
141
|
}
|
|
138
142
|
};
|
|
139
143
|
/**
|
|
140
|
-
* Save metadata to storage
|
|
144
|
+
* Save metadata to storage.
|
|
145
|
+
*
|
|
146
|
+
* Atomic via tmp-file + rename (matches `saveParseCache`'s pattern). The
|
|
147
|
+
* `incrementalInProgress` dirty flag travels through this file — a crash
|
|
148
|
+
* mid-write would leave a corrupt `meta.json` that the next run's
|
|
149
|
+
* `loadMeta` would silently treat as "no prior index", losing the dirty
|
|
150
|
+
* flag and skipping the recovery full-rebuild. Write-and-rename rules
|
|
151
|
+
* that out: the rename is atomic on POSIX and on Windows (`fs.rename`
|
|
152
|
+
* on `node:fs/promises` uses `MoveFileEx(REPLACE_EXISTING)`), so either
|
|
153
|
+
* the old or the new file is observed at every moment.
|
|
141
154
|
*/
|
|
142
155
|
export const saveMeta = async (storagePath, meta) => {
|
|
143
156
|
await fs.mkdir(storagePath, { recursive: true });
|
|
144
157
|
const metaPath = path.join(storagePath, 'meta.json');
|
|
145
|
-
|
|
158
|
+
const tmpPath = `${metaPath}.tmp`;
|
|
159
|
+
await fs.writeFile(tmpPath, JSON.stringify(meta, null, 2), 'utf-8');
|
|
160
|
+
await fs.rename(tmpPath, metaPath);
|
|
146
161
|
};
|
|
147
162
|
/**
|
|
148
163
|
* Check if a path has a GitNexus index
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
const fs = require('fs');
|
|
15
15
|
const path = require('path');
|
|
16
16
|
const { spawnSync } = require('child_process');
|
|
17
|
+
const { acquireHookSlot } = require('./hook-lock.cjs');
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Read JSON input from stdin synchronously.
|
|
@@ -207,7 +208,8 @@ function runGitNexusCli(cliPath, args, cwd, timeout) {
|
|
|
207
208
|
function handlePreToolUse(input) {
|
|
208
209
|
const cwd = input.cwd || process.cwd();
|
|
209
210
|
if (!path.isAbsolute(cwd)) return;
|
|
210
|
-
|
|
211
|
+
const gitNexusDir = findGitNexusDir(cwd);
|
|
212
|
+
if (!gitNexusDir) return;
|
|
211
213
|
|
|
212
214
|
const toolName = input.tool_name || '';
|
|
213
215
|
const toolInput = input.tool_input || {};
|
|
@@ -217,6 +219,9 @@ function handlePreToolUse(input) {
|
|
|
217
219
|
const pattern = extractPattern(toolName, toolInput);
|
|
218
220
|
if (!pattern || pattern.length < 3) return;
|
|
219
221
|
|
|
222
|
+
const release = acquireHookSlot(gitNexusDir);
|
|
223
|
+
if (!release) return;
|
|
224
|
+
|
|
220
225
|
const cliPath = resolveCliPath();
|
|
221
226
|
let result = '';
|
|
222
227
|
try {
|
|
@@ -226,6 +231,8 @@ function handlePreToolUse(input) {
|
|
|
226
231
|
}
|
|
227
232
|
} catch {
|
|
228
233
|
/* graceful failure */
|
|
234
|
+
} finally {
|
|
235
|
+
release();
|
|
229
236
|
}
|
|
230
237
|
|
|
231
238
|
if (result && result.trim()) {
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const HOOK_LOCK_SUBDIR = '.hook-locks';
|
|
5
|
+
const HOOK_LOCK_MAX_INFLIGHT = 3;
|
|
6
|
+
const HOOK_LOCK_STALE_MS = 30000;
|
|
7
|
+
|
|
8
|
+
function acquireHookSlot(gitNexusDir) {
|
|
9
|
+
const lockDir = path.join(gitNexusDir, HOOK_LOCK_SUBDIR);
|
|
10
|
+
try {
|
|
11
|
+
fs.mkdirSync(lockDir, { recursive: true });
|
|
12
|
+
} catch {
|
|
13
|
+
// Cannot create lock dir (read-only fs, cross-user perm denial, out of
|
|
14
|
+
// inodes, etc.) — fail closed by returning null. Caller skips augment.
|
|
15
|
+
// Fail-open here would let N concurrent hooks all proceed unguarded and
|
|
16
|
+
// reintroduce the #1486 fan-out the guard exists to prevent.
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const myPidStr = String(process.pid);
|
|
21
|
+
|
|
22
|
+
for (let slot = 0; slot < HOOK_LOCK_MAX_INFLIGHT; slot++) {
|
|
23
|
+
const slotPath = path.join(lockDir, `slot-${slot}.lock`);
|
|
24
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
25
|
+
try {
|
|
26
|
+
fs.writeFileSync(slotPath, myPidStr, { flag: 'wx' });
|
|
27
|
+
let released = false;
|
|
28
|
+
const release = () => {
|
|
29
|
+
if (released) return;
|
|
30
|
+
released = true;
|
|
31
|
+
try {
|
|
32
|
+
// Only unlink if we still own the slot. If we appeared stale and
|
|
33
|
+
// another hook took over, the file now belongs to it — leave alone.
|
|
34
|
+
const content = fs.readFileSync(slotPath, 'utf-8').trim();
|
|
35
|
+
if (content === myPidStr) fs.unlinkSync(slotPath);
|
|
36
|
+
} catch {
|
|
37
|
+
/* already removed or unreadable */
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
process.on('exit', release);
|
|
41
|
+
return release;
|
|
42
|
+
} catch {
|
|
43
|
+
// Slot exists. Decide whether to take it over.
|
|
44
|
+
// Open once and inspect mtime + content via the same fd so there's
|
|
45
|
+
// no TOCTOU between the metadata check and the content read
|
|
46
|
+
// (codeql js/file-system-race).
|
|
47
|
+
let fd;
|
|
48
|
+
try {
|
|
49
|
+
fd = fs.openSync(slotPath, 'r');
|
|
50
|
+
} catch {
|
|
51
|
+
continue; // Vanished between EEXIST and open — retry this slot.
|
|
52
|
+
}
|
|
53
|
+
let isLive = false;
|
|
54
|
+
let mtimeMs = Date.now();
|
|
55
|
+
try {
|
|
56
|
+
mtimeMs = fs.fstatSync(fd).mtimeMs;
|
|
57
|
+
const buf = Buffer.alloc(32);
|
|
58
|
+
const n = fs.readSync(fd, buf, 0, 32, 0);
|
|
59
|
+
const ownerStr = buf.slice(0, n).toString('utf-8').trim();
|
|
60
|
+
if (ownerStr === '') {
|
|
61
|
+
// Owner created the file but hasn't written its PID yet. The
|
|
62
|
+
// wx open+write window is microseconds; give it the benefit
|
|
63
|
+
// of the doubt and treat as live.
|
|
64
|
+
isLive = true;
|
|
65
|
+
} else {
|
|
66
|
+
const owner = Number.parseInt(ownerStr, 10);
|
|
67
|
+
if (Number.isFinite(owner) && owner > 0) {
|
|
68
|
+
try {
|
|
69
|
+
process.kill(owner, 0);
|
|
70
|
+
isLive = true;
|
|
71
|
+
} catch (e) {
|
|
72
|
+
// ESRCH = process gone → treat as dead. EPERM = process exists
|
|
73
|
+
// but owned by another user (cross-user lock dir) → still alive,
|
|
74
|
+
// keep the slot. Anything else: be conservative, assume alive.
|
|
75
|
+
if (e && e.code === 'ESRCH') {
|
|
76
|
+
isLive = false;
|
|
77
|
+
} else {
|
|
78
|
+
isLive = true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
} catch {
|
|
84
|
+
/* unreadable — treat as dead */
|
|
85
|
+
} finally {
|
|
86
|
+
try {
|
|
87
|
+
fs.closeSync(fd);
|
|
88
|
+
} catch {
|
|
89
|
+
/* already closed */
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// For slots younger than HOOK_LOCK_STALE_MS, PID-liveness wins —
|
|
93
|
+
// a slow-but-alive hook is never wrongly evicted. For older slots,
|
|
94
|
+
// age is the final arbiter as a defense against PID reuse on long-
|
|
95
|
+
// abandoned slots. 30s >> the 7s augment timeout, so a healthy run
|
|
96
|
+
// never crosses this threshold.
|
|
97
|
+
if (isLive && Date.now() - mtimeMs > HOOK_LOCK_STALE_MS) {
|
|
98
|
+
isLive = false;
|
|
99
|
+
}
|
|
100
|
+
if (isLive) break; // Try the next slot.
|
|
101
|
+
try {
|
|
102
|
+
fs.unlinkSync(slotPath);
|
|
103
|
+
} catch {
|
|
104
|
+
/* another hook beat us to it — retry will hit EEXIST */
|
|
105
|
+
}
|
|
106
|
+
// Loop and retry this slot.
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
module.exports = {
|
|
115
|
+
HOOK_LOCK_SUBDIR,
|
|
116
|
+
HOOK_LOCK_MAX_INFLIGHT,
|
|
117
|
+
HOOK_LOCK_STALE_MS,
|
|
118
|
+
acquireHookSlot,
|
|
119
|
+
};
|
package/package.json
CHANGED