gitnexus 1.6.10-rc.2 → 1.6.10-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 +56 -12
- package/dist/cli/analyze.js +166 -5
- package/dist/cli/clean.js +17 -3
- package/dist/cli/cli-message.d.ts +1 -1
- package/dist/cli/doctor.d.ts +18 -0
- package/dist/cli/doctor.js +76 -3
- package/dist/cli/embeddings.d.ts +12 -0
- package/dist/cli/embeddings.js +49 -0
- package/dist/cli/help-i18n.js +4 -0
- package/dist/cli/i18n/en.d.ts +10 -5
- package/dist/cli/i18n/en.js +10 -5
- package/dist/cli/i18n/resources.d.ts +15 -5
- package/dist/cli/i18n/zh-CN.d.ts +5 -0
- package/dist/cli/i18n/zh-CN.js +10 -5
- package/dist/cli/index.js +12 -1
- package/dist/core/augmentation/engine.js +4 -3
- package/dist/core/embeddings/embedder.js +16 -2
- package/dist/core/embeddings/embedding-pipeline.d.ts +20 -0
- package/dist/core/embeddings/embedding-pipeline.js +10 -2
- package/dist/core/embeddings/http-client.d.ts +28 -1
- package/dist/core/embeddings/http-client.js +107 -15
- package/dist/core/embeddings/node-module-compat.d.ts +23 -0
- package/dist/core/embeddings/node-module-compat.js +23 -0
- package/dist/core/embeddings/onnxruntime-common-resolver.js +6 -3
- package/dist/core/embeddings/onnxruntime-node-resolver.js +31 -5
- package/dist/core/embeddings/runtime-install.d.ts +119 -0
- package/dist/core/embeddings/runtime-install.js +372 -0
- package/dist/core/embeddings/runtime-support.d.ts +48 -17
- package/dist/core/embeddings/runtime-support.js +96 -0
- package/dist/core/group/extractors/http-patterns/python.js +246 -108
- package/dist/core/group/extractors/manifest-extractor.js +4 -0
- package/dist/core/incremental/escalation-gate.d.ts +38 -0
- package/dist/core/incremental/escalation-gate.js +48 -0
- package/dist/core/incremental/shadow-candidates.d.ts +3 -3
- package/dist/core/incremental/shadow-candidates.js +3 -3
- package/dist/core/incremental/subgraph-extract.d.ts +2 -2
- package/dist/core/incremental/subgraph-extract.js +2 -2
- package/dist/core/ingestion/cobol/jcl-processor.js +9 -8
- package/dist/core/ingestion/cobol-processor.js +27 -26
- package/dist/core/ingestion/community-processor.d.ts +51 -1
- package/dist/core/ingestion/community-processor.js +316 -63
- package/dist/core/ingestion/emit-references.js +3 -2
- package/dist/core/ingestion/languages/cpp/captures.js +157 -74
- package/dist/core/ingestion/markdown-processor.js +3 -2
- package/dist/core/ingestion/parsing-processor.d.ts +3 -1
- package/dist/core/ingestion/parsing-processor.js +4 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +42 -0
- package/dist/core/ingestion/route-extractors/constant-resolver.d.ts +83 -0
- package/dist/core/ingestion/route-extractors/constant-resolver.js +131 -0
- package/dist/core/ingestion/route-extractors/python-const-resolver.d.ts +75 -0
- package/dist/core/ingestion/route-extractors/python-const-resolver.js +299 -0
- package/dist/core/ingestion/tree-sitter-queries.d.ts +1 -1
- package/dist/core/ingestion/tree-sitter-queries.js +12 -1
- package/dist/core/ingestion/utils/line-base.d.ts +20 -0
- package/dist/core/ingestion/utils/line-base.js +20 -0
- package/dist/core/ingestion/utils/symbol-labels.d.ts +21 -0
- package/dist/core/ingestion/utils/symbol-labels.js +45 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +34 -0
- package/dist/core/ingestion/workers/parse-worker.js +40 -5
- package/dist/core/ingestion/workers/result-merge.js +4 -0
- package/dist/core/ingestion/workers/worker-pool.d.ts +14 -0
- package/dist/core/ingestion/workers/worker-pool.js +85 -5
- package/dist/core/lbug/csv-generator.js +8 -2
- package/dist/core/lbug/cypher-escape.d.ts +20 -0
- package/dist/core/lbug/cypher-escape.js +20 -0
- package/dist/core/lbug/extension-load-error.d.ts +67 -0
- package/dist/core/lbug/extension-load-error.js +320 -0
- package/dist/core/lbug/extension-loader.d.ts +17 -2
- package/dist/core/lbug/extension-loader.js +38 -13
- package/dist/core/lbug/lbug-adapter.d.ts +147 -1
- package/dist/core/lbug/lbug-adapter.js +390 -70
- package/dist/core/lbug/lbug-config.d.ts +28 -0
- package/dist/core/lbug/lbug-config.js +114 -4
- package/dist/core/lbug/native-check.d.ts +22 -0
- package/dist/core/lbug/native-check.js +66 -0
- package/dist/core/lbug/pool-adapter.js +10 -1
- package/dist/core/lbug/sidecar-recovery.d.ts +172 -0
- package/dist/core/lbug/sidecar-recovery.js +418 -16
- package/dist/core/platform/capabilities.js +27 -1
- package/dist/core/run-analyze.js +470 -108
- package/dist/core/search/fts-indexes.d.ts +7 -0
- package/dist/core/search/fts-indexes.js +37 -0
- package/dist/core/tree-sitter/safe-parse.d.ts +8 -3
- package/dist/core/tree-sitter/safe-parse.js +21 -7
- package/dist/core/wiki/graph-queries.js +6 -5
- package/dist/mcp/core/embedder.js +16 -2
- package/dist/mcp/local/line-display.d.ts +22 -0
- package/dist/mcp/local/line-display.js +3 -0
- package/dist/mcp/local/local-backend.d.ts +14 -0
- package/dist/mcp/local/local-backend.js +63 -19
- package/dist/mcp/local/pdg-impact.d.ts +5 -3
- package/dist/mcp/local/pdg-impact.js +5 -2
- package/dist/mcp/resources.js +1 -0
- package/dist/mcp/tools.js +1 -1
- package/dist/server/analyze-launch.d.ts +6 -0
- package/dist/server/analyze-launch.js +82 -6
- package/dist/server/api.d.ts +30 -0
- package/dist/server/api.js +63 -21
- package/dist/storage/parse-cache.js +1 -1
- package/dist/storage/repo-manager.d.ts +65 -1
- package/dist/storage/repo-manager.js +18 -1
- package/hooks/antigravity/gitnexus-antigravity-hook.cjs +50 -3
- package/hooks/claude/gitnexus-hook.cjs +59 -9
- package/package.json +5 -3
- package/scripts/cross-platform-tests.ts +43 -0
- package/scripts/ensure-fts.ts +32 -0
- package/scripts/install-duckdb-extension.mjs +85 -24
- package/scripts/run-cross-platform.ts +46 -6
- package/scripts/shard-arg.ts +30 -0
- package/web/assets/ProcessFlowModal-Dna7GkwO.js +22 -0
- package/web/assets/{agent-Dli_x7bU.js → agent-DNYDy-KA.js} +49 -49
- package/web/assets/architecture-7EHR7CIX-6QZW5X65-BVWFvYz2.js +1 -0
- package/web/assets/architectureDiagram-UL44E2DR-BU5ZRITg.js +36 -0
- package/web/assets/{blockDiagram-7IZFK4PR-BkWhpzWB.js → blockDiagram-7IZFK4PR-Dut1BpTV.js} +2 -2
- package/web/assets/{c4Diagram-Y2BXMSZH-DtBvpzWD.js → c4Diagram-Y2BXMSZH-DOXVs7-C.js} +2 -2
- package/web/assets/chunk-2T2R6R2M-pkw2oXHH.js +4 -0
- package/web/assets/chunk-2UTLFMKG-CXlhF3Pt.js +1 -0
- package/web/assets/chunk-3SSMPTDK-rPsRM1x0.js +321 -0
- package/web/assets/chunk-4R4BOZG6-Be_F0JoX.js +159 -0
- package/web/assets/{chunk-6764PJDD-DpIS7hxD.js → chunk-6764PJDD-C7xxk063.js} +1 -1
- package/web/assets/{chunk-67TQ5CYL-4sNPJU1q.js → chunk-67TQ5CYL-zxhDWgyF.js} +3 -3
- package/web/assets/chunk-7FYTHRHK-CsmYIswB.js +32 -0
- package/web/assets/chunk-7J6CGLKN-AmkN8jJN.js +10 -0
- package/web/assets/chunk-7W6UQGC5-pyKGssV3.js +1 -0
- package/web/assets/chunk-C62D2QBJ-3gdL8EKc.js +1 -0
- package/web/assets/chunk-CEXFNPSA-CjwtMDkc.js +1 -0
- package/web/assets/{chunk-INKRHTLW-Nu4ri9P-.js → chunk-INKRHTLW-D9BM9HWZ.js} +1 -1
- package/web/assets/chunk-J5EP6P6S-L19oz55R.js +1 -0
- package/web/assets/{chunk-KGFNY3KK-JGMJvKCK.js → chunk-KGFNY3KK-tD2F6wxK.js} +1 -1
- package/web/assets/chunk-KGYTTC2M-zm6n242I.js +1 -0
- package/web/assets/{chunk-KRXBNO2N-BW5o0KUN.js → chunk-KRXBNO2N-C-UNuKKf.js} +1 -1
- package/web/assets/{chunk-LCXTWHL2-CfLLCywY.js → chunk-LCXTWHL2-Ba9JK-6F.js} +2 -2
- package/web/assets/{chunk-LII3EMHJ-D-jm-dLa.js → chunk-LII3EMHJ-EGx-eBIu.js} +1 -1
- package/web/assets/chunk-LRIF4GLE-C3_PqUBq.js +1 -0
- package/web/assets/{chunk-QA3QBVWF-BgRW3SXd.js → chunk-QA3QBVWF-D4cSJBf6.js} +1 -1
- package/web/assets/chunk-RERM46MO-B5dAdWpP.js +1 -0
- package/web/assets/{chunk-RG4AUYOV-CqWzWsV3.js → chunk-RG4AUYOV-OqBBMwuQ.js} +4 -4
- package/web/assets/chunk-RKZBBQEN-MyIQbGgD.js +1 -0
- package/web/assets/chunk-RLI5ZMPA-CkXZ0y0U.js +1 -0
- package/web/assets/{chunk-T2UQINTJ-DSBqkzH7.js → chunk-T2UQINTJ-BJusEiGe.js} +1 -1
- package/web/assets/chunk-UP6H54XL-BG-n7qDp.js +1 -0
- package/web/assets/chunk-UXSXWOXI-CW1_r5cS.js +1 -0
- package/web/assets/{chunk-UY5QBCOK-DyF0vyGd.js → chunk-UY5QBCOK-BCpUlARQ.js} +1 -1
- package/web/assets/chunk-VU6ZFW4Y-BeXVk45_.js +2 -0
- package/web/assets/{chunk-W44A43WB-JqMLIpR0.js → chunk-W44A43WB-BN8EQc_u.js} +1 -1
- package/web/assets/{chunk-ZXARS5L4-CxN8oiwI.js → chunk-ZXARS5L4-BI_qR2Nm.js} +1 -1
- package/web/assets/classDiagram-KGZ6W3CR-D9zSIg0x.js +1 -0
- package/web/assets/classDiagram-v2-72OJOZXJ-D9zSIg0x.js +1 -0
- package/web/assets/cose-bilkent-UX7MHV2Q-BmnZ7Mvj.js +1 -0
- package/web/assets/dagre-ND4H6XIP-CpcV5kbl.js +4 -0
- package/web/assets/diagram-3NCE3AQN-6SMxBRpj.js +43 -0
- package/web/assets/diagram-GF46GFSD-BZEdNcRZ.js +24 -0
- package/web/assets/{diagram-HNR7UZ2L-DFCfd5LI.js → diagram-HNR7UZ2L-DxKW2a2Y.js} +2 -2
- package/web/assets/diagram-QXG6HAR7-HfTHcXX_.js +24 -0
- package/web/assets/diagram-WEQXMOUZ-CyijUS8r.js +10 -0
- package/web/assets/{erDiagram-L5TCEMPS-8X1y6R4Y.js → erDiagram-L5TCEMPS-D0fvpWEX.js} +3 -3
- package/web/assets/eventmodeling-FCH6USID-MREXMVOE-CgFBM7e-.js +1 -0
- package/web/assets/flowDiagram-H6V6AXG4-Bvr9bbhk.js +162 -0
- package/web/assets/{ganttDiagram-JCBTUEKG-D3iL2Aet.js → ganttDiagram-JCBTUEKG-TzODrLzv.js} +2 -2
- package/web/assets/gitGraph-WXDBUCRP-R675I2BI-DCJv7SII.js +1 -0
- package/web/assets/gitGraphDiagram-S2ZK5IYY-ONQSwJIY.js +106 -0
- package/web/assets/index-B4eB4dNZ.js +575 -0
- package/web/assets/index-CX_fADmQ.css +2 -0
- package/web/assets/info-J43DQDTF-KCYPFFUO-BfNy-9Nd.js +1 -0
- package/web/assets/infoDiagram-3YFTVSEB-DIPVUIQ6.js +2 -0
- package/web/assets/{ishikawaDiagram-BNXS4ZKH-DfYpxnE5.js → ishikawaDiagram-BNXS4ZKH-BLjivjXL.js} +3 -3
- package/web/assets/{journeyDiagram-M6C3CM3L-CvArszLo.js → journeyDiagram-M6C3CM3L-Cetgs3NH.js} +3 -3
- package/web/assets/{kanban-definition-75IXJCU3-CpfVLLf9.js → kanban-definition-75IXJCU3-DMlCaIpf.js} +5 -5
- package/web/assets/{katex-K3KEBU37-CTc5BslQ.js → katex-K3KEBU37-qRQBy-EZ.js} +2 -2
- package/web/assets/{mindmap-definition-2TDM6QVE-DypvbOMQ.js → mindmap-definition-2TDM6QVE-B4GbXQBc.js} +3 -3
- package/web/assets/packet-YPE3B663-LP52Z2RK-D4qkeG9k.js +1 -0
- package/web/assets/pie-LRSECV5Y-TCRJHUBD-B_yMKyQa.js +1 -0
- package/web/assets/{pieDiagram-CU6KROY3-s27EFWgP.js → pieDiagram-CU6KROY3-D3YC_xyg.js} +2 -2
- package/web/assets/{quadrantDiagram-VICAPDV7-yXSZ2lLz.js → quadrantDiagram-VICAPDV7-B1TQnlm0.js} +2 -2
- package/web/assets/radar-GUYGQ44K-RDLRG3WG-DeD6yN_0.js +1 -0
- package/web/assets/{requirementDiagram-JXO7QTGE-CWhqInuf.js → requirementDiagram-JXO7QTGE-f8wczLQf.js} +3 -3
- package/web/assets/rolldown-runtime-QTnfLwEv.js +1 -0
- package/web/assets/{sankeyDiagram-URQDO5SZ-1qEjlFIB.js → sankeyDiagram-URQDO5SZ-BTxzG5j0.js} +2 -2
- package/web/assets/{sequenceDiagram-VS2MUI6T-RyTlZ5cR.js → sequenceDiagram-VS2MUI6T-DuamM-IR.js} +5 -5
- package/web/assets/src-BNCXICdA.js +1 -0
- package/web/assets/stateDiagram-7D4R322I-Ch2pEUcw.js +1 -0
- package/web/assets/stateDiagram-v2-36443NZ5-7ANuoZLL.js +1 -0
- package/web/assets/{timeline-definition-O6YCAMPW-DrrqLYAf.js → timeline-definition-O6YCAMPW-07BR8R8G.js} +3 -3
- package/web/assets/treeView-BLDUP644-QA4HXRO3-CDuSYnd4.js +1 -0
- package/web/assets/treemap-LRROVOQU-LLAWBHMP-imazucyJ.js +1 -0
- package/web/assets/{vennDiagram-MWXL3ELB-B0RkWAim.js → vennDiagram-MWXL3ELB-BQX2ossk.js} +4 -4
- package/web/assets/wardley-L42UT6IY-5TKZOOLJ-DqrT9tB8.js +1 -0
- package/web/assets/{wardleyDiagram-CUQ6CDDI-boUxiQ5F.js → wardleyDiagram-CUQ6CDDI-CGIJBBiK.js} +3 -3
- package/web/assets/{xychartDiagram-N2JHSOCM-BOLm0eNm.js → xychartDiagram-N2JHSOCM-B3HNMuWp.js} +4 -4
- package/web/index.html +17 -10
- package/web/assets/architecture-7EHR7CIX-6QZW5X65-DxJw65fT.js +0 -1
- package/web/assets/architectureDiagram-UL44E2DR-dEkqUZN9.js +0 -36
- package/web/assets/chunk-3SSMPTDK-B4_etUhr.js +0 -321
- package/web/assets/chunk-7W6UQGC5-KyEG0HQg.js +0 -1
- package/web/assets/chunk-KGYTTC2M-CWC_c3H9.js +0 -161
- package/web/assets/classDiagram-KGZ6W3CR-CMne4tG9.js +0 -1
- package/web/assets/classDiagram-v2-72OJOZXJ-CMne4tG9.js +0 -1
- package/web/assets/cose-bilkent-UX7MHV2Q-D71wNYRJ.js +0 -1
- package/web/assets/dagre-ND4H6XIP-Co5rRx9X.js +0 -4
- package/web/assets/diagram-3NCE3AQN-DBp4O00j.js +0 -43
- package/web/assets/diagram-GF46GFSD-6N_8yS0i.js +0 -24
- package/web/assets/diagram-QXG6HAR7-Di093rFg.js +0 -24
- package/web/assets/diagram-WEQXMOUZ-DZWi4SUN.js +0 -10
- package/web/assets/eventmodeling-FCH6USID-MREXMVOE-BQm9QzEa.js +0 -1
- package/web/assets/flowDiagram-H6V6AXG4-CMpY8Ufn.js +0 -162
- package/web/assets/gitGraph-WXDBUCRP-R675I2BI-Cr_Bm2Nb.js +0 -1
- package/web/assets/gitGraphDiagram-S2ZK5IYY-CPIDgxGm.js +0 -106
- package/web/assets/index-KctnI3Vq.js +0 -635
- package/web/assets/index-_lgn7hs5.css +0 -2
- package/web/assets/info-J43DQDTF-KCYPFFUO-Cs4p2oyk.js +0 -1
- package/web/assets/infoDiagram-3YFTVSEB-BaLMZkUg.js +0 -2
- package/web/assets/packet-YPE3B663-LP52Z2RK-CKCrztD2.js +0 -1
- package/web/assets/pie-LRSECV5Y-TCRJHUBD-BB7pHqoD.js +0 -1
- package/web/assets/radar-GUYGQ44K-RDLRG3WG-DhoTOcuK.js +0 -1
- package/web/assets/stateDiagram-7D4R322I-DTjVfX4A.js +0 -1
- package/web/assets/stateDiagram-v2-36443NZ5-xY9_k9_Q.js +0 -1
- package/web/assets/treeView-BLDUP644-QA4HXRO3-ODehyGKL.js +0 -1
- package/web/assets/treemap-LRROVOQU-LLAWBHMP-BfpgC-7o.js +0 -1
- package/web/assets/wardley-L42UT6IY-5TKZOOLJ-CNeL6VHE.js +0 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Python from 'tree-sitter-python';
|
|
2
2
|
import { compilePatterns, runCompiledPatterns, unquoteLiteral, } from '../tree-sitter-scanner.js';
|
|
3
3
|
import { normalizeExtractedRoutePath } from '../../../ingestion/route-extractors/route-path.js';
|
|
4
|
+
import { extractPythonModuleConstants, parseConstOperands, resolveOperands, } from '../../../ingestion/route-extractors/python-const-resolver.js';
|
|
4
5
|
/**
|
|
5
6
|
* Python HTTP plugin. Handles:
|
|
6
7
|
* - FastAPI `@app.get("/path")` provider decorators
|
|
@@ -67,6 +68,44 @@ const FASTAPI_ROUTER_PATTERNS = compilePatterns({
|
|
|
67
68
|
},
|
|
68
69
|
],
|
|
69
70
|
});
|
|
71
|
+
// #2391: `@router.<verb>` / `@app.<verb>` whose first argument is a non-literal
|
|
72
|
+
// path — a bare imported constant or a `+`-concatenation. The path is resolved
|
|
73
|
+
// against the repo-wide constant map (parity with the ingestion side) and, on
|
|
74
|
+
// failure, the route is skipped (no provider contract) exactly like ingestion.
|
|
75
|
+
const FASTAPI_ROUTER_EXPR_PATTERNS = compilePatterns({
|
|
76
|
+
name: 'python-fastapi-router-expr',
|
|
77
|
+
language: Python,
|
|
78
|
+
patterns: [
|
|
79
|
+
{
|
|
80
|
+
meta: {},
|
|
81
|
+
query: `
|
|
82
|
+
(decorator
|
|
83
|
+
(call
|
|
84
|
+
function: (attribute
|
|
85
|
+
object: (identifier) @obj (#eq? @obj "router")
|
|
86
|
+
attribute: (identifier) @method (#match? @method "^(get|post|put|delete|patch)$"))
|
|
87
|
+
arguments: (argument_list . [(identifier) (binary_operator)] @path)))
|
|
88
|
+
`,
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
});
|
|
92
|
+
const FASTAPI_APP_EXPR_PATTERNS = compilePatterns({
|
|
93
|
+
name: 'python-fastapi-app-expr',
|
|
94
|
+
language: Python,
|
|
95
|
+
patterns: [
|
|
96
|
+
{
|
|
97
|
+
meta: {},
|
|
98
|
+
query: `
|
|
99
|
+
(decorator
|
|
100
|
+
(call
|
|
101
|
+
function: (attribute
|
|
102
|
+
object: (identifier) @obj (#eq? @obj "app")
|
|
103
|
+
attribute: (identifier) @method (#match? @method "^(get|post|put|delete|patch)$"))
|
|
104
|
+
arguments: (argument_list . [(identifier) (binary_operator)] @path)))
|
|
105
|
+
`,
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
});
|
|
70
109
|
// ─── Provider: Flask `app.add_url_rule('/path', view_func=handler)` ───
|
|
71
110
|
// The imperative Flask route registration: unlike `@app.route` (whose handler
|
|
72
111
|
// is the decorated function, same-file), `view_func` is frequently an IMPORTED
|
|
@@ -830,111 +869,153 @@ function recordPrefix(target, key, prefix) {
|
|
|
830
869
|
set.add(prefix);
|
|
831
870
|
target.set(key, set);
|
|
832
871
|
}
|
|
872
|
+
// Cheap cost-gate pre-filter: a `@router`/`@app.<verb>(` call whose first
|
|
873
|
+
// argument is non-literal — either it STARTS with an identifier (a bare constant
|
|
874
|
+
// or the head of `CONST + "/x"`), or it is a string-literal-LEADING concat
|
|
875
|
+
// (`"/api" + SUFFIX`) detected by a `+` before the decorator's closing paren
|
|
876
|
+
// (#2393). `[^)]*` spans the whole argument, including a Black-formatted concat
|
|
877
|
+
// that wraps across lines, but stays bounded by the decorator's own `)`. Gating
|
|
878
|
+
// the literal-leading case on the `+` (not merely a leading quote) keeps a plain
|
|
879
|
+
// string route `@router.get("/x")` OFF the gate, so a literal-only repo pays no
|
|
880
|
+
// parse pass. Deliberately loose — a false positive only costs a parse; a false
|
|
881
|
+
// negative would silently drop the feature.
|
|
882
|
+
const NONLITERAL_ROUTE_DECORATOR_RE = /@\s*(?:app|router)\s*\.\s*(?:get|post|put|delete|patch)\s*\(\s*(?:[A-Za-z_]|["'][^)]*\+)/;
|
|
833
883
|
function buildPythonRepoContext(files, parser, readFile, parseSource) {
|
|
834
884
|
const prefixesByLongKey = new Map();
|
|
835
885
|
const prefixesByShortKey = new Map();
|
|
836
|
-
//
|
|
837
|
-
//
|
|
838
|
-
//
|
|
839
|
-
//
|
|
886
|
+
// Single read pass (#2393): slurp every `.py` file's content ONCE. This used to
|
|
887
|
+
// be two passes — the include_router pre-pass below and the #2391 constant cost
|
|
888
|
+
// gate each re-read every `.py` file. The composed-route cost gate is computed
|
|
889
|
+
// in the same pass so a literal-only repo still does exactly one read and zero
|
|
890
|
+
// parses.
|
|
891
|
+
const pyContents = new Map();
|
|
892
|
+
let hasComposedRoute = false;
|
|
840
893
|
for (const rel of files) {
|
|
841
894
|
if (!rel.endsWith('.py'))
|
|
842
895
|
continue;
|
|
843
896
|
const src = readFile(rel);
|
|
844
897
|
if (!src)
|
|
845
898
|
continue;
|
|
846
|
-
|
|
899
|
+
pyContents.set(rel, src);
|
|
900
|
+
if (!hasComposedRoute && NONLITERAL_ROUTE_DECORATOR_RE.test(src))
|
|
901
|
+
hasComposedRoute = true;
|
|
902
|
+
}
|
|
903
|
+
// Single PARSE pass (#2391): parse each `.py` at most once and feed BOTH the
|
|
904
|
+
// include_router prefix pre-pass and the composed-constant map below. This used
|
|
905
|
+
// to be two loops, so an include_router file in a composed repo was parsed
|
|
906
|
+
// twice. A file that needs neither pass is not parsed at all (cost gates intact).
|
|
907
|
+
//
|
|
908
|
+
// Cross-file pre-pass: only `include_router` sites need it — they bind a prefix
|
|
909
|
+
// declared in one file to a router defined in another. Same-file
|
|
910
|
+
// `APIRouter(prefix=...)` is resolved in scan() from the file's own tree.
|
|
911
|
+
const constantsByFile = new Map();
|
|
912
|
+
for (const [rel, src] of pyContents) {
|
|
913
|
+
const needsRouter = src.includes('include_router');
|
|
914
|
+
if (!needsRouter && !hasComposedRoute)
|
|
847
915
|
continue;
|
|
848
916
|
parser.setLanguage(Python);
|
|
849
917
|
const tree = parseSource(parser, src);
|
|
850
918
|
if (!tree)
|
|
851
919
|
continue;
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
const
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
// Module-alias map: name imported from a multi-segment package →
|
|
867
|
-
// long key. Lets Shape A look up the precise file for `<name>.router`
|
|
868
|
-
// even when `<name>` collides with another package's basename.
|
|
869
|
-
const localNameToModuleAlias = new Map();
|
|
870
|
-
for (const m of runCompiledPatterns(FROM_IMPORT_MODULE_PATTERNS, tree)) {
|
|
871
|
-
const moduleNode = m.captures.module;
|
|
872
|
-
const importedNode = m.captures.imported;
|
|
873
|
-
const aliasNode = m.captures.alias;
|
|
874
|
-
if (!moduleNode || !importedNode)
|
|
875
|
-
continue;
|
|
876
|
-
// Skip the `router` shape — already handled by FROM_IMPORT_ROUTER_PATTERNS
|
|
877
|
-
// above and stored under its router-aware semantics.
|
|
878
|
-
if (importedNode.text === 'router')
|
|
879
|
-
continue;
|
|
880
|
-
const moduleLong = lastTwoSegmentsAsLongKey(`${moduleNode.text}.${importedNode.text}`);
|
|
881
|
-
if (!moduleLong)
|
|
882
|
-
continue;
|
|
883
|
-
const localName = aliasNode?.text ?? importedNode.text;
|
|
884
|
-
localNameToModuleAlias.set(localName, moduleLong);
|
|
885
|
-
}
|
|
886
|
-
// Shape A: `<host>.include_router(<module>.router, prefix='/x')`.
|
|
887
|
-
// The call site gives us only a short module name. We promote to a
|
|
888
|
-
// long key when the same file imports `<module>` via either
|
|
889
|
-
// `from <pkg> import <module>` (recorded in `localNameToModuleAlias`
|
|
890
|
-
// — the typical pattern) or, less commonly, a router-aware import
|
|
891
|
-
// statement. Only fall back to the basename short key when neither
|
|
892
|
-
// alias is available.
|
|
893
|
-
for (const m of runCompiledPatterns(INCLUDE_ROUTER_ATTR_PATTERNS, tree)) {
|
|
894
|
-
const modNode = m.captures.router_module;
|
|
895
|
-
const prefixNode = m.captures.prefix;
|
|
896
|
-
if (!modNode || !prefixNode)
|
|
897
|
-
continue;
|
|
898
|
-
const prefix = unquoteLiteral(prefixNode.text);
|
|
899
|
-
if (prefix === null)
|
|
900
|
-
continue;
|
|
901
|
-
const moduleShort = modNode.text;
|
|
902
|
-
const aliasLong = localNameToModuleAlias.get(moduleShort);
|
|
903
|
-
const sameFileImport = localNameToModule.get(moduleShort);
|
|
904
|
-
const longKey = aliasLong ?? sameFileImport?.moduleLong;
|
|
905
|
-
if (longKey) {
|
|
906
|
-
recordPrefix(prefixesByLongKey, longKey, prefix);
|
|
920
|
+
if (needsRouter) {
|
|
921
|
+
const localNameToModule = new Map();
|
|
922
|
+
for (const m of runCompiledPatterns(FROM_IMPORT_ROUTER_PATTERNS, tree)) {
|
|
923
|
+
const moduleNode = m.captures.module;
|
|
924
|
+
const aliasNode = m.captures.alias;
|
|
925
|
+
const importedNode = m.captures.imported;
|
|
926
|
+
if (!moduleNode || !importedNode)
|
|
927
|
+
continue;
|
|
928
|
+
const localName = aliasNode?.text ?? importedNode.text;
|
|
929
|
+
const moduleShort = lastSegmentOfDotted(moduleNode.text);
|
|
930
|
+
if (!moduleShort)
|
|
931
|
+
continue;
|
|
932
|
+
const moduleLong = lastTwoSegmentsAsLongKey(moduleNode.text);
|
|
933
|
+
localNameToModule.set(localName, { moduleShort, moduleLong });
|
|
907
934
|
}
|
|
908
|
-
|
|
909
|
-
|
|
935
|
+
// Module-alias map: name imported from a multi-segment package →
|
|
936
|
+
// long key. Lets Shape A look up the precise file for `<name>.router`
|
|
937
|
+
// even when `<name>` collides with another package's basename.
|
|
938
|
+
const localNameToModuleAlias = new Map();
|
|
939
|
+
for (const m of runCompiledPatterns(FROM_IMPORT_MODULE_PATTERNS, tree)) {
|
|
940
|
+
const moduleNode = m.captures.module;
|
|
941
|
+
const importedNode = m.captures.imported;
|
|
942
|
+
const aliasNode = m.captures.alias;
|
|
943
|
+
if (!moduleNode || !importedNode)
|
|
944
|
+
continue;
|
|
945
|
+
// Skip the `router` shape — already handled by FROM_IMPORT_ROUTER_PATTERNS
|
|
946
|
+
// above and stored under its router-aware semantics.
|
|
947
|
+
if (importedNode.text === 'router')
|
|
948
|
+
continue;
|
|
949
|
+
const moduleLong = lastTwoSegmentsAsLongKey(`${moduleNode.text}.${importedNode.text}`);
|
|
950
|
+
if (!moduleLong)
|
|
951
|
+
continue;
|
|
952
|
+
const localName = aliasNode?.text ?? importedNode.text;
|
|
953
|
+
localNameToModuleAlias.set(localName, moduleLong);
|
|
910
954
|
}
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
const
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
955
|
+
// Shape A: `<host>.include_router(<module>.router, prefix='/x')`.
|
|
956
|
+
// The call site gives us only a short module name. We promote to a
|
|
957
|
+
// long key when the same file imports `<module>` via either
|
|
958
|
+
// `from <pkg> import <module>` (recorded in `localNameToModuleAlias`
|
|
959
|
+
// — the typical pattern) or, less commonly, a router-aware import
|
|
960
|
+
// statement. Only fall back to the basename short key when neither
|
|
961
|
+
// alias is available.
|
|
962
|
+
for (const m of runCompiledPatterns(INCLUDE_ROUTER_ATTR_PATTERNS, tree)) {
|
|
963
|
+
const modNode = m.captures.router_module;
|
|
964
|
+
const prefixNode = m.captures.prefix;
|
|
965
|
+
if (!modNode || !prefixNode)
|
|
966
|
+
continue;
|
|
967
|
+
const prefix = unquoteLiteral(prefixNode.text);
|
|
968
|
+
if (prefix === null)
|
|
969
|
+
continue;
|
|
970
|
+
const moduleShort = modNode.text;
|
|
971
|
+
const aliasLong = localNameToModuleAlias.get(moduleShort);
|
|
972
|
+
const sameFileImport = localNameToModule.get(moduleShort);
|
|
973
|
+
const longKey = aliasLong ?? sameFileImport?.moduleLong;
|
|
974
|
+
if (longKey) {
|
|
975
|
+
recordPrefix(prefixesByLongKey, longKey, prefix);
|
|
976
|
+
}
|
|
977
|
+
else {
|
|
978
|
+
recordPrefix(prefixesByShortKey, moduleShort, prefix);
|
|
979
|
+
}
|
|
929
980
|
}
|
|
930
|
-
|
|
931
|
-
|
|
981
|
+
// Shape B: `<host>.include_router(my_router, prefix='/x')` — resolve
|
|
982
|
+
// `my_router` via the import map built above. Whenever the import
|
|
983
|
+
// statement supplied a multi-segment module path the long key is
|
|
984
|
+
// recorded, eliminating cross-package collisions.
|
|
985
|
+
for (const m of runCompiledPatterns(INCLUDE_ROUTER_NAME_PATTERNS, tree)) {
|
|
986
|
+
const nameNode = m.captures.router_name;
|
|
987
|
+
const prefixNode = m.captures.prefix;
|
|
988
|
+
if (!nameNode || !prefixNode)
|
|
989
|
+
continue;
|
|
990
|
+
const localImp = localNameToModule.get(nameNode.text);
|
|
991
|
+
if (!localImp)
|
|
992
|
+
continue;
|
|
993
|
+
const prefix = unquoteLiteral(prefixNode.text);
|
|
994
|
+
if (prefix === null)
|
|
995
|
+
continue;
|
|
996
|
+
if (localImp.moduleLong) {
|
|
997
|
+
recordPrefix(prefixesByLongKey, localImp.moduleLong, prefix);
|
|
998
|
+
}
|
|
999
|
+
else {
|
|
1000
|
+
recordPrefix(prefixesByShortKey, localImp.moduleShort, prefix);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
// #2391: build the repo-wide constant map for resolving non-literal decorator
|
|
1005
|
+
// paths (KTD6 cost gate: only when `hasComposedRoute`). Parse EVERY `.py` so
|
|
1006
|
+
// the resolvable set matches the ingestion aggregate (R4 parity) — a narrower
|
|
1007
|
+
// set would return null where ingestion resolves.
|
|
1008
|
+
if (hasComposedRoute) {
|
|
1009
|
+
const mc = extractPythonModuleConstants(tree);
|
|
1010
|
+
if (mc.literals.size > 0 || mc.exprs.size > 0 || mc.imports.size > 0) {
|
|
1011
|
+
constantsByFile.set(rel, mc);
|
|
932
1012
|
}
|
|
933
1013
|
}
|
|
934
1014
|
}
|
|
935
1015
|
return {
|
|
936
1016
|
prefixesByLongKey,
|
|
937
1017
|
prefixesByShortKey,
|
|
1018
|
+
constantsByFile,
|
|
938
1019
|
};
|
|
939
1020
|
}
|
|
940
1021
|
function joinPrefix(prefix, route) {
|
|
@@ -971,32 +1052,67 @@ export const PYTHON_HTTP_PLUGIN = {
|
|
|
971
1052
|
// statements, so an imperatively-registered handler (Flask `view_func`) that
|
|
972
1053
|
// is an imported (possibly aliased) symbol resolves to its real definition.
|
|
973
1054
|
const importMap = buildPythonImportMap(tree);
|
|
974
|
-
//
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
1055
|
+
// #2391: fold a non-literal decorator argument (bare constant or
|
|
1056
|
+
// `+`-concatenation) to its literal path against the repo constant map, or
|
|
1057
|
+
// `null` → skip (the same floor the ingestion side applies, so provider
|
|
1058
|
+
// contracts and graph Route nodes agree on both resolved and dropped routes).
|
|
1059
|
+
const resolveExprArg = (argNode) => {
|
|
1060
|
+
const cbf = ctx?.constantsByFile;
|
|
1061
|
+
if (!cbf || !fileRel)
|
|
1062
|
+
return null;
|
|
1063
|
+
// Build an operand list and fold via `resolveOperands` — the SAME entry the
|
|
1064
|
+
// ingestion side uses (parse-impl folds `routePathOperands`). Using the
|
|
1065
|
+
// by-name `resolveConstant` here would enter `foldName` one depth shallower,
|
|
1066
|
+
// so at the MAX_RESOLVE_DEPTH boundary the group would resolve a chain
|
|
1067
|
+
// ingestion drops, breaking R4 parity (#2393).
|
|
1068
|
+
const operands = argNode.type === 'identifier'
|
|
1069
|
+
? [{ kind: 'ref', name: argNode.text }]
|
|
1070
|
+
: parseConstOperands(argNode);
|
|
1071
|
+
return operands ? resolveOperands(fileRel, operands, cbf) : null;
|
|
1072
|
+
};
|
|
1073
|
+
const emitAppProvider = (httpMethod, pathVal, line) => {
|
|
986
1074
|
out.push({
|
|
987
1075
|
role: 'provider',
|
|
988
1076
|
framework: 'fastapi',
|
|
989
1077
|
method: httpMethod,
|
|
990
|
-
path,
|
|
1078
|
+
path: pathVal,
|
|
991
1079
|
name: null,
|
|
992
1080
|
// The decorated handler has no captured name → resolve by line-span
|
|
993
1081
|
// containment. Best-effort fallback: FastAPI routes are graph-backed
|
|
994
1082
|
// (ingestion decorator routes) and the function span starts at `def`
|
|
995
1083
|
// (decorators excluded), so this lands the single-decorator case and
|
|
996
1084
|
// degrades to file-level for multi-decorator stacks.
|
|
997
|
-
line
|
|
1085
|
+
line,
|
|
998
1086
|
confidence: 0.8,
|
|
999
1087
|
});
|
|
1088
|
+
};
|
|
1089
|
+
// Providers: FastAPI @app.<verb>("/path") — already absolute path.
|
|
1090
|
+
for (const match of runCompiledPatterns(FASTAPI_APP_PATTERNS, tree)) {
|
|
1091
|
+
const methodNode = match.captures.method;
|
|
1092
|
+
const pathNode = match.captures.path;
|
|
1093
|
+
if (!methodNode || !pathNode)
|
|
1094
|
+
continue;
|
|
1095
|
+
const httpMethod = FASTAPI_VERBS[methodNode.text];
|
|
1096
|
+
if (!httpMethod)
|
|
1097
|
+
continue;
|
|
1098
|
+
const path = unquoteLiteral(pathNode.text);
|
|
1099
|
+
if (path === null)
|
|
1100
|
+
continue;
|
|
1101
|
+
emitAppProvider(httpMethod, path, pathNode.startPosition.row + 1);
|
|
1102
|
+
}
|
|
1103
|
+
// Providers: FastAPI @app.<verb>(CONST | A + "/x") — resolved composed path.
|
|
1104
|
+
for (const match of runCompiledPatterns(FASTAPI_APP_EXPR_PATTERNS, tree)) {
|
|
1105
|
+
const methodNode = match.captures.method;
|
|
1106
|
+
const pathNode = match.captures.path;
|
|
1107
|
+
if (!methodNode || !pathNode)
|
|
1108
|
+
continue;
|
|
1109
|
+
const httpMethod = FASTAPI_VERBS[methodNode.text];
|
|
1110
|
+
if (!httpMethod)
|
|
1111
|
+
continue;
|
|
1112
|
+
const resolved = resolveExprArg(pathNode);
|
|
1113
|
+
if (resolved === null)
|
|
1114
|
+
continue; // skip floor
|
|
1115
|
+
emitAppProvider(httpMethod, resolved, pathNode.startPosition.row + 1);
|
|
1000
1116
|
}
|
|
1001
1117
|
// Django providers come from the graph Route nodes (includes composed by
|
|
1002
1118
|
// the ingestion route extractor), not a per-file source scan — see the note
|
|
@@ -1020,17 +1136,11 @@ export const PYTHON_HTTP_PLUGIN = {
|
|
|
1020
1136
|
// change is strictly additive vs. the prior @app-only behaviour;
|
|
1021
1137
|
// when the same router is mounted under multiple prefixes we emit
|
|
1022
1138
|
// one detection per prefix.
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
const httpMethod = FASTAPI_VERBS[methodNode.text];
|
|
1029
|
-
if (!httpMethod)
|
|
1030
|
-
continue;
|
|
1031
|
-
const rawPath = unquoteLiteral(pathNode.text);
|
|
1032
|
-
if (rawPath === null)
|
|
1033
|
-
continue;
|
|
1139
|
+
// Join a `@router.<verb>` path with the include_router / APIRouter prefix(es)
|
|
1140
|
+
// that apply to this file and emit one provider detection per prefix. Shared
|
|
1141
|
+
// by the literal and the #2391 non-literal (resolved) router loops so both
|
|
1142
|
+
// stack prefixes identically.
|
|
1143
|
+
const emitRouterProvider = (httpMethod, rawPath, line) => {
|
|
1034
1144
|
// Long key first (precise, package-aware), short key as fallback.
|
|
1035
1145
|
// Mirrors the ingestion-side resolution in parse-impl.ts so the
|
|
1036
1146
|
// graph nodes and group contracts agree on which prefix applies.
|
|
@@ -1053,10 +1163,38 @@ export const PYTHON_HTTP_PLUGIN = {
|
|
|
1053
1163
|
path: p,
|
|
1054
1164
|
name: null,
|
|
1055
1165
|
// Best-effort containment fallback — see the @app provider note above.
|
|
1056
|
-
line
|
|
1166
|
+
line,
|
|
1057
1167
|
confidence: 0.8,
|
|
1058
1168
|
});
|
|
1059
1169
|
}
|
|
1170
|
+
};
|
|
1171
|
+
for (const match of runCompiledPatterns(FASTAPI_ROUTER_PATTERNS, tree)) {
|
|
1172
|
+
const methodNode = match.captures.method;
|
|
1173
|
+
const pathNode = match.captures.path;
|
|
1174
|
+
if (!methodNode || !pathNode)
|
|
1175
|
+
continue;
|
|
1176
|
+
const httpMethod = FASTAPI_VERBS[methodNode.text];
|
|
1177
|
+
if (!httpMethod)
|
|
1178
|
+
continue;
|
|
1179
|
+
const rawPath = unquoteLiteral(pathNode.text);
|
|
1180
|
+
if (rawPath === null)
|
|
1181
|
+
continue;
|
|
1182
|
+
emitRouterProvider(httpMethod, rawPath, pathNode.startPosition.row + 1);
|
|
1183
|
+
}
|
|
1184
|
+
// Providers: FastAPI @router.<verb>(CONST | A + "/x") — resolved composed path
|
|
1185
|
+
// (#2391). Null resolution → skip, so provider/graph parity holds.
|
|
1186
|
+
for (const match of runCompiledPatterns(FASTAPI_ROUTER_EXPR_PATTERNS, tree)) {
|
|
1187
|
+
const methodNode = match.captures.method;
|
|
1188
|
+
const pathNode = match.captures.path;
|
|
1189
|
+
if (!methodNode || !pathNode)
|
|
1190
|
+
continue;
|
|
1191
|
+
const httpMethod = FASTAPI_VERBS[methodNode.text];
|
|
1192
|
+
if (!httpMethod)
|
|
1193
|
+
continue;
|
|
1194
|
+
const resolved = resolveExprArg(pathNode);
|
|
1195
|
+
if (resolved === null)
|
|
1196
|
+
continue;
|
|
1197
|
+
emitRouterProvider(httpMethod, resolved, pathNode.startPosition.row + 1);
|
|
1060
1198
|
}
|
|
1061
1199
|
// Providers: Flask `app.add_url_rule('/path', view_func=handler, methods=[…])`.
|
|
1062
1200
|
// The handler is a `view_func` identifier, frequently an imported (possibly
|
|
@@ -7,6 +7,10 @@ import { logger } from '../../logger.js';
|
|
|
7
7
|
// reserved-keyword labels `Macro` and `Union`, and LadybugDB's parser rejects
|
|
8
8
|
// a disjunction that names a reserved keyword (#2325) — which the resolver's
|
|
9
9
|
// try/catch then swallowed. `labels(n) IN` has no such collision.
|
|
10
|
+
// This list overlaps `ingestion/utils/symbol-labels.ts` (SYMBOL_NODE_LABELS) but
|
|
11
|
+
// is a deliberate SUBSET — it omits `Namespace`/`Variable`/`Module`. Unifying the
|
|
12
|
+
// two would widen which nodes resolve as contract symbols and must update the
|
|
13
|
+
// #2325 test, so they are intentionally kept separate for now.
|
|
10
14
|
export const CUSTOM_CONTRACT_RESOLVE_QUERY = `MATCH (n)
|
|
11
15
|
WHERE labels(n) IN ['Function','Method','Class','Interface','Struct','Enum','Trait','Constructor','TypeAlias','Impl','Macro','Union','Typedef','Property','Record','Delegate','Annotation','Template','Const','Static','CodeElement']
|
|
12
16
|
AND n.name = $symbolName
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Escalation gate for the incremental DB writeback (#2409).
|
|
3
|
+
*
|
|
4
|
+
* When the effective write set covers most of the repo, per-file surgery is
|
|
5
|
+
* strictly worse than the proven wipe-and-bulk-COPY plan — the same data
|
|
6
|
+
* volume lands either way, but the surgical plan pays per-table deletes plus
|
|
7
|
+
* COPY-into-non-empty tables, and at that size it measured SLOWER than a full
|
|
8
|
+
* DB load. The orchestrator (`run-analyze.ts`) consults this predicate to
|
|
9
|
+
* decide which write plan to run; only the DB write plan changes on
|
|
10
|
+
* escalation — fileHashes/meta bookkeeping is identical.
|
|
11
|
+
*
|
|
12
|
+
* Extracted to a pure module (tri-review 4669518496) so the AND-gate's
|
|
13
|
+
* boundary corners are pinned by unit tests without multi-minute
|
|
14
|
+
* orchestration permutations — an `&&`→`||` mutation here can no longer
|
|
15
|
+
* survive CI.
|
|
16
|
+
*/
|
|
17
|
+
export declare const INCREMENTAL_MAX_WRITE_FRACTION = 0.5;
|
|
18
|
+
export declare const INCREMENTAL_ESCALATION_MIN_FILES = 50;
|
|
19
|
+
/**
|
|
20
|
+
* Should the incremental writeback escalate from per-file surgery to a full
|
|
21
|
+
* wipe-and-bulk-COPY write plan?
|
|
22
|
+
*
|
|
23
|
+
* @param deleteCount Files whose rows will be DETACH-DELETEd
|
|
24
|
+
* (effective write set ∪ deleted files, deduped).
|
|
25
|
+
* @param effectiveWriteCount Size of the effective write set (toWrite ∪
|
|
26
|
+
* importer-BFS expansion ∪ boundary-crossing files).
|
|
27
|
+
* @param totalFiles Current repo file count (denominator).
|
|
28
|
+
*
|
|
29
|
+
* POPULATION MISMATCH (tri-review 4669518496, documented not "fixed"): the
|
|
30
|
+
* numerator counts effective-write-set members, which include importer-BFS
|
|
31
|
+
* results read from the PRE-pipeline DB — those can be now-DELETED paths that
|
|
32
|
+
* the CURRENT file list (the denominator) no longer contains. The fraction is
|
|
33
|
+
* therefore not a true subset ratio and can exceed 1 on delete-heavy runs.
|
|
34
|
+
* That errs toward escalation, which is the safe direction (the full write
|
|
35
|
+
* plan is always correct); the valve's log line clamps the DISPLAYED
|
|
36
|
+
* percentage to 100 so operators aren't shown ">100% of the repo".
|
|
37
|
+
*/
|
|
38
|
+
export declare const shouldEscalateIncrementalWrite: (deleteCount: number, effectiveWriteCount: number, totalFiles: number) => boolean;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Escalation gate for the incremental DB writeback (#2409).
|
|
3
|
+
*
|
|
4
|
+
* When the effective write set covers most of the repo, per-file surgery is
|
|
5
|
+
* strictly worse than the proven wipe-and-bulk-COPY plan — the same data
|
|
6
|
+
* volume lands either way, but the surgical plan pays per-table deletes plus
|
|
7
|
+
* COPY-into-non-empty tables, and at that size it measured SLOWER than a full
|
|
8
|
+
* DB load. The orchestrator (`run-analyze.ts`) consults this predicate to
|
|
9
|
+
* decide which write plan to run; only the DB write plan changes on
|
|
10
|
+
* escalation — fileHashes/meta bookkeeping is identical.
|
|
11
|
+
*
|
|
12
|
+
* Extracted to a pure module (tri-review 4669518496) so the AND-gate's
|
|
13
|
+
* boundary corners are pinned by unit tests without multi-minute
|
|
14
|
+
* orchestration permutations — an `&&`→`||` mutation here can no longer
|
|
15
|
+
* survive CI.
|
|
16
|
+
*/
|
|
17
|
+
// Escalation cap (#2409): above this fraction of the repo's files, the
|
|
18
|
+
// surgical delete-and-COPY writeback is replaced by the full-rebuild write
|
|
19
|
+
// plan (wipe + bulk COPY of the already-built graph). 0.5 is a coarse
|
|
20
|
+
// crossover knob, not a tuned constant — lower it if surgical writebacks
|
|
21
|
+
// above ~30% ever measure slower than the full COPY.
|
|
22
|
+
export const INCREMENTAL_MAX_WRITE_FRACTION = 0.5;
|
|
23
|
+
// …but only at a scale where the surgical plan's overhead is real. Tiny
|
|
24
|
+
// repos hit huge fractions from a single edit (7 files → one touch can
|
|
25
|
+
// pull in 5) while both write plans finish in well under a second there —
|
|
26
|
+
// escalating would churn the DB files for nothing.
|
|
27
|
+
export const INCREMENTAL_ESCALATION_MIN_FILES = 50;
|
|
28
|
+
/**
|
|
29
|
+
* Should the incremental writeback escalate from per-file surgery to a full
|
|
30
|
+
* wipe-and-bulk-COPY write plan?
|
|
31
|
+
*
|
|
32
|
+
* @param deleteCount Files whose rows will be DETACH-DELETEd
|
|
33
|
+
* (effective write set ∪ deleted files, deduped).
|
|
34
|
+
* @param effectiveWriteCount Size of the effective write set (toWrite ∪
|
|
35
|
+
* importer-BFS expansion ∪ boundary-crossing files).
|
|
36
|
+
* @param totalFiles Current repo file count (denominator).
|
|
37
|
+
*
|
|
38
|
+
* POPULATION MISMATCH (tri-review 4669518496, documented not "fixed"): the
|
|
39
|
+
* numerator counts effective-write-set members, which include importer-BFS
|
|
40
|
+
* results read from the PRE-pipeline DB — those can be now-DELETED paths that
|
|
41
|
+
* the CURRENT file list (the denominator) no longer contains. The fraction is
|
|
42
|
+
* therefore not a true subset ratio and can exceed 1 on delete-heavy runs.
|
|
43
|
+
* That errs toward escalation, which is the safe direction (the full write
|
|
44
|
+
* plan is always correct); the valve's log line clamps the DISPLAYED
|
|
45
|
+
* percentage to 100 so operators aren't shown ">100% of the repo".
|
|
46
|
+
*/
|
|
47
|
+
export const shouldEscalateIncrementalWrite = (deleteCount, effectiveWriteCount, totalFiles) => deleteCount >= INCREMENTAL_ESCALATION_MIN_FILES &&
|
|
48
|
+
effectiveWriteCount / Math.max(1, totalFiles) > INCREMENTAL_MAX_WRITE_FRACTION;
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* Shadow-candidate path derivation for incremental indexing.
|
|
3
3
|
*
|
|
4
4
|
* Background — Bugbot review on PR #1479:
|
|
5
|
-
*
|
|
6
|
-
* pre-pipeline DB, because the new file's IMPORTS
|
|
7
|
-
* written yet. But pre-existing files may have IMPORTS edges that
|
|
5
|
+
* the importer BFS (queryImportersBatch) on a NEWLY ADDED file returns
|
|
6
|
+
* 0 importers in the pre-pipeline DB, because the new file's IMPORTS
|
|
7
|
+
* rows haven't been written yet. But pre-existing files may have IMPORTS edges that
|
|
8
8
|
* *resolved to a sibling path*, and the newcomer can now steal that
|
|
9
9
|
* resolution under standard JS/TS module-resolution rules. Without
|
|
10
10
|
* pulling those pre-existing files into the writable set, their
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* Shadow-candidate path derivation for incremental indexing.
|
|
3
3
|
*
|
|
4
4
|
* Background — Bugbot review on PR #1479:
|
|
5
|
-
*
|
|
6
|
-
* pre-pipeline DB, because the new file's IMPORTS
|
|
7
|
-
* written yet. But pre-existing files may have IMPORTS edges that
|
|
5
|
+
* the importer BFS (queryImportersBatch) on a NEWLY ADDED file returns
|
|
6
|
+
* 0 importers in the pre-pipeline DB, because the new file's IMPORTS
|
|
7
|
+
* rows haven't been written yet. But pre-existing files may have IMPORTS edges that
|
|
8
8
|
* *resolved to a sibling path*, and the newcomer can now steal that
|
|
9
9
|
* resolution under standard JS/TS module-resolution rules. Without
|
|
10
10
|
* pulling those pre-existing files into the writable set, their
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
*
|
|
23
23
|
* `extractChangedSubgraph` intentionally does NOT expand the set it is
|
|
24
24
|
* given — expansion is the orchestrator's job, so the SAME expanded set
|
|
25
|
-
* can be fed to both `
|
|
25
|
+
* can be fed to both `deleteNodesForFiles` and this function (asymmetry
|
|
26
26
|
* between the delete set and the write set silently corrupts the DB).
|
|
27
27
|
* `computeEffectiveWriteSet` below performs the boundary-crossing 1-hop
|
|
28
28
|
* walk; the orchestrator composes it with its importer-BFS expansion and
|
|
@@ -57,7 +57,7 @@ export declare const extractChangedSubgraph: (fullGraph: KnowledgeGraph, toWrite
|
|
|
57
57
|
* deleted + rewritten in lockstep with the changed side.
|
|
58
58
|
*
|
|
59
59
|
* Single pass over the edge list. Does NOT mutate `toWriteSet`. The
|
|
60
|
-
* orchestrator MUST feed the returned set to both `
|
|
60
|
+
* orchestrator MUST feed the returned set to both `deleteNodesForFiles`
|
|
61
61
|
* and `extractChangedSubgraph` — feeding the unexpanded set to either
|
|
62
62
|
* one leaves stale rows or PK-conflicts at COPY time.
|
|
63
63
|
*/
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
*
|
|
23
23
|
* `extractChangedSubgraph` intentionally does NOT expand the set it is
|
|
24
24
|
* given — expansion is the orchestrator's job, so the SAME expanded set
|
|
25
|
-
* can be fed to both `
|
|
25
|
+
* can be fed to both `deleteNodesForFiles` and this function (asymmetry
|
|
26
26
|
* between the delete set and the write set silently corrupts the DB).
|
|
27
27
|
* `computeEffectiveWriteSet` below performs the boundary-crossing 1-hop
|
|
28
28
|
* walk; the orchestrator composes it with its importer-BFS expansion and
|
|
@@ -118,7 +118,7 @@ export const extractChangedSubgraph = (fullGraph, toWriteSet) => {
|
|
|
118
118
|
* deleted + rewritten in lockstep with the changed side.
|
|
119
119
|
*
|
|
120
120
|
* Single pass over the edge list. Does NOT mutate `toWriteSet`. The
|
|
121
|
-
* orchestrator MUST feed the returned set to both `
|
|
121
|
+
* orchestrator MUST feed the returned set to both `deleteNodesForFiles`
|
|
122
122
|
* and `extractChangedSubgraph` — feeding the unexpanded set to either
|
|
123
123
|
* one leaves stale rows or PK-conflicts at COPY time.
|
|
124
124
|
*/
|