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.
Files changed (219) hide show
  1. package/README.md +56 -12
  2. package/dist/cli/analyze.js +166 -5
  3. package/dist/cli/clean.js +17 -3
  4. package/dist/cli/cli-message.d.ts +1 -1
  5. package/dist/cli/doctor.d.ts +18 -0
  6. package/dist/cli/doctor.js +76 -3
  7. package/dist/cli/embeddings.d.ts +12 -0
  8. package/dist/cli/embeddings.js +49 -0
  9. package/dist/cli/help-i18n.js +4 -0
  10. package/dist/cli/i18n/en.d.ts +10 -5
  11. package/dist/cli/i18n/en.js +10 -5
  12. package/dist/cli/i18n/resources.d.ts +15 -5
  13. package/dist/cli/i18n/zh-CN.d.ts +5 -0
  14. package/dist/cli/i18n/zh-CN.js +10 -5
  15. package/dist/cli/index.js +12 -1
  16. package/dist/core/augmentation/engine.js +4 -3
  17. package/dist/core/embeddings/embedder.js +16 -2
  18. package/dist/core/embeddings/embedding-pipeline.d.ts +20 -0
  19. package/dist/core/embeddings/embedding-pipeline.js +10 -2
  20. package/dist/core/embeddings/http-client.d.ts +28 -1
  21. package/dist/core/embeddings/http-client.js +107 -15
  22. package/dist/core/embeddings/node-module-compat.d.ts +23 -0
  23. package/dist/core/embeddings/node-module-compat.js +23 -0
  24. package/dist/core/embeddings/onnxruntime-common-resolver.js +6 -3
  25. package/dist/core/embeddings/onnxruntime-node-resolver.js +31 -5
  26. package/dist/core/embeddings/runtime-install.d.ts +119 -0
  27. package/dist/core/embeddings/runtime-install.js +372 -0
  28. package/dist/core/embeddings/runtime-support.d.ts +48 -17
  29. package/dist/core/embeddings/runtime-support.js +96 -0
  30. package/dist/core/group/extractors/http-patterns/python.js +246 -108
  31. package/dist/core/group/extractors/manifest-extractor.js +4 -0
  32. package/dist/core/incremental/escalation-gate.d.ts +38 -0
  33. package/dist/core/incremental/escalation-gate.js +48 -0
  34. package/dist/core/incremental/shadow-candidates.d.ts +3 -3
  35. package/dist/core/incremental/shadow-candidates.js +3 -3
  36. package/dist/core/incremental/subgraph-extract.d.ts +2 -2
  37. package/dist/core/incremental/subgraph-extract.js +2 -2
  38. package/dist/core/ingestion/cobol/jcl-processor.js +9 -8
  39. package/dist/core/ingestion/cobol-processor.js +27 -26
  40. package/dist/core/ingestion/community-processor.d.ts +51 -1
  41. package/dist/core/ingestion/community-processor.js +316 -63
  42. package/dist/core/ingestion/emit-references.js +3 -2
  43. package/dist/core/ingestion/languages/cpp/captures.js +157 -74
  44. package/dist/core/ingestion/markdown-processor.js +3 -2
  45. package/dist/core/ingestion/parsing-processor.d.ts +3 -1
  46. package/dist/core/ingestion/parsing-processor.js +4 -0
  47. package/dist/core/ingestion/pipeline-phases/parse-impl.js +42 -0
  48. package/dist/core/ingestion/route-extractors/constant-resolver.d.ts +83 -0
  49. package/dist/core/ingestion/route-extractors/constant-resolver.js +131 -0
  50. package/dist/core/ingestion/route-extractors/python-const-resolver.d.ts +75 -0
  51. package/dist/core/ingestion/route-extractors/python-const-resolver.js +299 -0
  52. package/dist/core/ingestion/tree-sitter-queries.d.ts +1 -1
  53. package/dist/core/ingestion/tree-sitter-queries.js +12 -1
  54. package/dist/core/ingestion/utils/line-base.d.ts +20 -0
  55. package/dist/core/ingestion/utils/line-base.js +20 -0
  56. package/dist/core/ingestion/utils/symbol-labels.d.ts +21 -0
  57. package/dist/core/ingestion/utils/symbol-labels.js +45 -0
  58. package/dist/core/ingestion/workers/parse-worker.d.ts +34 -0
  59. package/dist/core/ingestion/workers/parse-worker.js +40 -5
  60. package/dist/core/ingestion/workers/result-merge.js +4 -0
  61. package/dist/core/ingestion/workers/worker-pool.d.ts +14 -0
  62. package/dist/core/ingestion/workers/worker-pool.js +85 -5
  63. package/dist/core/lbug/csv-generator.js +8 -2
  64. package/dist/core/lbug/cypher-escape.d.ts +20 -0
  65. package/dist/core/lbug/cypher-escape.js +20 -0
  66. package/dist/core/lbug/extension-load-error.d.ts +67 -0
  67. package/dist/core/lbug/extension-load-error.js +320 -0
  68. package/dist/core/lbug/extension-loader.d.ts +17 -2
  69. package/dist/core/lbug/extension-loader.js +38 -13
  70. package/dist/core/lbug/lbug-adapter.d.ts +147 -1
  71. package/dist/core/lbug/lbug-adapter.js +390 -70
  72. package/dist/core/lbug/lbug-config.d.ts +28 -0
  73. package/dist/core/lbug/lbug-config.js +114 -4
  74. package/dist/core/lbug/native-check.d.ts +22 -0
  75. package/dist/core/lbug/native-check.js +66 -0
  76. package/dist/core/lbug/pool-adapter.js +10 -1
  77. package/dist/core/lbug/sidecar-recovery.d.ts +172 -0
  78. package/dist/core/lbug/sidecar-recovery.js +418 -16
  79. package/dist/core/platform/capabilities.js +27 -1
  80. package/dist/core/run-analyze.js +470 -108
  81. package/dist/core/search/fts-indexes.d.ts +7 -0
  82. package/dist/core/search/fts-indexes.js +37 -0
  83. package/dist/core/tree-sitter/safe-parse.d.ts +8 -3
  84. package/dist/core/tree-sitter/safe-parse.js +21 -7
  85. package/dist/core/wiki/graph-queries.js +6 -5
  86. package/dist/mcp/core/embedder.js +16 -2
  87. package/dist/mcp/local/line-display.d.ts +22 -0
  88. package/dist/mcp/local/line-display.js +3 -0
  89. package/dist/mcp/local/local-backend.d.ts +14 -0
  90. package/dist/mcp/local/local-backend.js +63 -19
  91. package/dist/mcp/local/pdg-impact.d.ts +5 -3
  92. package/dist/mcp/local/pdg-impact.js +5 -2
  93. package/dist/mcp/resources.js +1 -0
  94. package/dist/mcp/tools.js +1 -1
  95. package/dist/server/analyze-launch.d.ts +6 -0
  96. package/dist/server/analyze-launch.js +82 -6
  97. package/dist/server/api.d.ts +30 -0
  98. package/dist/server/api.js +63 -21
  99. package/dist/storage/parse-cache.js +1 -1
  100. package/dist/storage/repo-manager.d.ts +65 -1
  101. package/dist/storage/repo-manager.js +18 -1
  102. package/hooks/antigravity/gitnexus-antigravity-hook.cjs +50 -3
  103. package/hooks/claude/gitnexus-hook.cjs +59 -9
  104. package/package.json +5 -3
  105. package/scripts/cross-platform-tests.ts +43 -0
  106. package/scripts/ensure-fts.ts +32 -0
  107. package/scripts/install-duckdb-extension.mjs +85 -24
  108. package/scripts/run-cross-platform.ts +46 -6
  109. package/scripts/shard-arg.ts +30 -0
  110. package/web/assets/ProcessFlowModal-Dna7GkwO.js +22 -0
  111. package/web/assets/{agent-Dli_x7bU.js → agent-DNYDy-KA.js} +49 -49
  112. package/web/assets/architecture-7EHR7CIX-6QZW5X65-BVWFvYz2.js +1 -0
  113. package/web/assets/architectureDiagram-UL44E2DR-BU5ZRITg.js +36 -0
  114. package/web/assets/{blockDiagram-7IZFK4PR-BkWhpzWB.js → blockDiagram-7IZFK4PR-Dut1BpTV.js} +2 -2
  115. package/web/assets/{c4Diagram-Y2BXMSZH-DtBvpzWD.js → c4Diagram-Y2BXMSZH-DOXVs7-C.js} +2 -2
  116. package/web/assets/chunk-2T2R6R2M-pkw2oXHH.js +4 -0
  117. package/web/assets/chunk-2UTLFMKG-CXlhF3Pt.js +1 -0
  118. package/web/assets/chunk-3SSMPTDK-rPsRM1x0.js +321 -0
  119. package/web/assets/chunk-4R4BOZG6-Be_F0JoX.js +159 -0
  120. package/web/assets/{chunk-6764PJDD-DpIS7hxD.js → chunk-6764PJDD-C7xxk063.js} +1 -1
  121. package/web/assets/{chunk-67TQ5CYL-4sNPJU1q.js → chunk-67TQ5CYL-zxhDWgyF.js} +3 -3
  122. package/web/assets/chunk-7FYTHRHK-CsmYIswB.js +32 -0
  123. package/web/assets/chunk-7J6CGLKN-AmkN8jJN.js +10 -0
  124. package/web/assets/chunk-7W6UQGC5-pyKGssV3.js +1 -0
  125. package/web/assets/chunk-C62D2QBJ-3gdL8EKc.js +1 -0
  126. package/web/assets/chunk-CEXFNPSA-CjwtMDkc.js +1 -0
  127. package/web/assets/{chunk-INKRHTLW-Nu4ri9P-.js → chunk-INKRHTLW-D9BM9HWZ.js} +1 -1
  128. package/web/assets/chunk-J5EP6P6S-L19oz55R.js +1 -0
  129. package/web/assets/{chunk-KGFNY3KK-JGMJvKCK.js → chunk-KGFNY3KK-tD2F6wxK.js} +1 -1
  130. package/web/assets/chunk-KGYTTC2M-zm6n242I.js +1 -0
  131. package/web/assets/{chunk-KRXBNO2N-BW5o0KUN.js → chunk-KRXBNO2N-C-UNuKKf.js} +1 -1
  132. package/web/assets/{chunk-LCXTWHL2-CfLLCywY.js → chunk-LCXTWHL2-Ba9JK-6F.js} +2 -2
  133. package/web/assets/{chunk-LII3EMHJ-D-jm-dLa.js → chunk-LII3EMHJ-EGx-eBIu.js} +1 -1
  134. package/web/assets/chunk-LRIF4GLE-C3_PqUBq.js +1 -0
  135. package/web/assets/{chunk-QA3QBVWF-BgRW3SXd.js → chunk-QA3QBVWF-D4cSJBf6.js} +1 -1
  136. package/web/assets/chunk-RERM46MO-B5dAdWpP.js +1 -0
  137. package/web/assets/{chunk-RG4AUYOV-CqWzWsV3.js → chunk-RG4AUYOV-OqBBMwuQ.js} +4 -4
  138. package/web/assets/chunk-RKZBBQEN-MyIQbGgD.js +1 -0
  139. package/web/assets/chunk-RLI5ZMPA-CkXZ0y0U.js +1 -0
  140. package/web/assets/{chunk-T2UQINTJ-DSBqkzH7.js → chunk-T2UQINTJ-BJusEiGe.js} +1 -1
  141. package/web/assets/chunk-UP6H54XL-BG-n7qDp.js +1 -0
  142. package/web/assets/chunk-UXSXWOXI-CW1_r5cS.js +1 -0
  143. package/web/assets/{chunk-UY5QBCOK-DyF0vyGd.js → chunk-UY5QBCOK-BCpUlARQ.js} +1 -1
  144. package/web/assets/chunk-VU6ZFW4Y-BeXVk45_.js +2 -0
  145. package/web/assets/{chunk-W44A43WB-JqMLIpR0.js → chunk-W44A43WB-BN8EQc_u.js} +1 -1
  146. package/web/assets/{chunk-ZXARS5L4-CxN8oiwI.js → chunk-ZXARS5L4-BI_qR2Nm.js} +1 -1
  147. package/web/assets/classDiagram-KGZ6W3CR-D9zSIg0x.js +1 -0
  148. package/web/assets/classDiagram-v2-72OJOZXJ-D9zSIg0x.js +1 -0
  149. package/web/assets/cose-bilkent-UX7MHV2Q-BmnZ7Mvj.js +1 -0
  150. package/web/assets/dagre-ND4H6XIP-CpcV5kbl.js +4 -0
  151. package/web/assets/diagram-3NCE3AQN-6SMxBRpj.js +43 -0
  152. package/web/assets/diagram-GF46GFSD-BZEdNcRZ.js +24 -0
  153. package/web/assets/{diagram-HNR7UZ2L-DFCfd5LI.js → diagram-HNR7UZ2L-DxKW2a2Y.js} +2 -2
  154. package/web/assets/diagram-QXG6HAR7-HfTHcXX_.js +24 -0
  155. package/web/assets/diagram-WEQXMOUZ-CyijUS8r.js +10 -0
  156. package/web/assets/{erDiagram-L5TCEMPS-8X1y6R4Y.js → erDiagram-L5TCEMPS-D0fvpWEX.js} +3 -3
  157. package/web/assets/eventmodeling-FCH6USID-MREXMVOE-CgFBM7e-.js +1 -0
  158. package/web/assets/flowDiagram-H6V6AXG4-Bvr9bbhk.js +162 -0
  159. package/web/assets/{ganttDiagram-JCBTUEKG-D3iL2Aet.js → ganttDiagram-JCBTUEKG-TzODrLzv.js} +2 -2
  160. package/web/assets/gitGraph-WXDBUCRP-R675I2BI-DCJv7SII.js +1 -0
  161. package/web/assets/gitGraphDiagram-S2ZK5IYY-ONQSwJIY.js +106 -0
  162. package/web/assets/index-B4eB4dNZ.js +575 -0
  163. package/web/assets/index-CX_fADmQ.css +2 -0
  164. package/web/assets/info-J43DQDTF-KCYPFFUO-BfNy-9Nd.js +1 -0
  165. package/web/assets/infoDiagram-3YFTVSEB-DIPVUIQ6.js +2 -0
  166. package/web/assets/{ishikawaDiagram-BNXS4ZKH-DfYpxnE5.js → ishikawaDiagram-BNXS4ZKH-BLjivjXL.js} +3 -3
  167. package/web/assets/{journeyDiagram-M6C3CM3L-CvArszLo.js → journeyDiagram-M6C3CM3L-Cetgs3NH.js} +3 -3
  168. package/web/assets/{kanban-definition-75IXJCU3-CpfVLLf9.js → kanban-definition-75IXJCU3-DMlCaIpf.js} +5 -5
  169. package/web/assets/{katex-K3KEBU37-CTc5BslQ.js → katex-K3KEBU37-qRQBy-EZ.js} +2 -2
  170. package/web/assets/{mindmap-definition-2TDM6QVE-DypvbOMQ.js → mindmap-definition-2TDM6QVE-B4GbXQBc.js} +3 -3
  171. package/web/assets/packet-YPE3B663-LP52Z2RK-D4qkeG9k.js +1 -0
  172. package/web/assets/pie-LRSECV5Y-TCRJHUBD-B_yMKyQa.js +1 -0
  173. package/web/assets/{pieDiagram-CU6KROY3-s27EFWgP.js → pieDiagram-CU6KROY3-D3YC_xyg.js} +2 -2
  174. package/web/assets/{quadrantDiagram-VICAPDV7-yXSZ2lLz.js → quadrantDiagram-VICAPDV7-B1TQnlm0.js} +2 -2
  175. package/web/assets/radar-GUYGQ44K-RDLRG3WG-DeD6yN_0.js +1 -0
  176. package/web/assets/{requirementDiagram-JXO7QTGE-CWhqInuf.js → requirementDiagram-JXO7QTGE-f8wczLQf.js} +3 -3
  177. package/web/assets/rolldown-runtime-QTnfLwEv.js +1 -0
  178. package/web/assets/{sankeyDiagram-URQDO5SZ-1qEjlFIB.js → sankeyDiagram-URQDO5SZ-BTxzG5j0.js} +2 -2
  179. package/web/assets/{sequenceDiagram-VS2MUI6T-RyTlZ5cR.js → sequenceDiagram-VS2MUI6T-DuamM-IR.js} +5 -5
  180. package/web/assets/src-BNCXICdA.js +1 -0
  181. package/web/assets/stateDiagram-7D4R322I-Ch2pEUcw.js +1 -0
  182. package/web/assets/stateDiagram-v2-36443NZ5-7ANuoZLL.js +1 -0
  183. package/web/assets/{timeline-definition-O6YCAMPW-DrrqLYAf.js → timeline-definition-O6YCAMPW-07BR8R8G.js} +3 -3
  184. package/web/assets/treeView-BLDUP644-QA4HXRO3-CDuSYnd4.js +1 -0
  185. package/web/assets/treemap-LRROVOQU-LLAWBHMP-imazucyJ.js +1 -0
  186. package/web/assets/{vennDiagram-MWXL3ELB-B0RkWAim.js → vennDiagram-MWXL3ELB-BQX2ossk.js} +4 -4
  187. package/web/assets/wardley-L42UT6IY-5TKZOOLJ-DqrT9tB8.js +1 -0
  188. package/web/assets/{wardleyDiagram-CUQ6CDDI-boUxiQ5F.js → wardleyDiagram-CUQ6CDDI-CGIJBBiK.js} +3 -3
  189. package/web/assets/{xychartDiagram-N2JHSOCM-BOLm0eNm.js → xychartDiagram-N2JHSOCM-B3HNMuWp.js} +4 -4
  190. package/web/index.html +17 -10
  191. package/web/assets/architecture-7EHR7CIX-6QZW5X65-DxJw65fT.js +0 -1
  192. package/web/assets/architectureDiagram-UL44E2DR-dEkqUZN9.js +0 -36
  193. package/web/assets/chunk-3SSMPTDK-B4_etUhr.js +0 -321
  194. package/web/assets/chunk-7W6UQGC5-KyEG0HQg.js +0 -1
  195. package/web/assets/chunk-KGYTTC2M-CWC_c3H9.js +0 -161
  196. package/web/assets/classDiagram-KGZ6W3CR-CMne4tG9.js +0 -1
  197. package/web/assets/classDiagram-v2-72OJOZXJ-CMne4tG9.js +0 -1
  198. package/web/assets/cose-bilkent-UX7MHV2Q-D71wNYRJ.js +0 -1
  199. package/web/assets/dagre-ND4H6XIP-Co5rRx9X.js +0 -4
  200. package/web/assets/diagram-3NCE3AQN-DBp4O00j.js +0 -43
  201. package/web/assets/diagram-GF46GFSD-6N_8yS0i.js +0 -24
  202. package/web/assets/diagram-QXG6HAR7-Di093rFg.js +0 -24
  203. package/web/assets/diagram-WEQXMOUZ-DZWi4SUN.js +0 -10
  204. package/web/assets/eventmodeling-FCH6USID-MREXMVOE-BQm9QzEa.js +0 -1
  205. package/web/assets/flowDiagram-H6V6AXG4-CMpY8Ufn.js +0 -162
  206. package/web/assets/gitGraph-WXDBUCRP-R675I2BI-Cr_Bm2Nb.js +0 -1
  207. package/web/assets/gitGraphDiagram-S2ZK5IYY-CPIDgxGm.js +0 -106
  208. package/web/assets/index-KctnI3Vq.js +0 -635
  209. package/web/assets/index-_lgn7hs5.css +0 -2
  210. package/web/assets/info-J43DQDTF-KCYPFFUO-Cs4p2oyk.js +0 -1
  211. package/web/assets/infoDiagram-3YFTVSEB-BaLMZkUg.js +0 -2
  212. package/web/assets/packet-YPE3B663-LP52Z2RK-CKCrztD2.js +0 -1
  213. package/web/assets/pie-LRSECV5Y-TCRJHUBD-BB7pHqoD.js +0 -1
  214. package/web/assets/radar-GUYGQ44K-RDLRG3WG-DhoTOcuK.js +0 -1
  215. package/web/assets/stateDiagram-7D4R322I-DTjVfX4A.js +0 -1
  216. package/web/assets/stateDiagram-v2-36443NZ5-xY9_k9_Q.js +0 -1
  217. package/web/assets/treeView-BLDUP644-QA4HXRO3-ODehyGKL.js +0 -1
  218. package/web/assets/treemap-LRROVOQU-LLAWBHMP-BfpgC-7o.js +0 -1
  219. package/web/assets/wardley-L42UT6IY-5TKZOOLJ-CNeL6VHE.js +0 -1
@@ -1,3 +1,10 @@
1
+ /**
2
+ * Warning attached to search responses when BM25/FTS is degraded. Prefers the
3
+ * live extension-load failure (with LadybugDB's real reason, #2374) over the
4
+ * generic indexes-missing message, so "indexes exist but the extension broke"
5
+ * is not misreported as missing indexes.
6
+ */
7
+ export declare const ftsDegradedWarning: () => string;
1
8
  export declare const SUPPORTED_FTS_STEMMERS: ReadonlySet<string>;
2
9
  export interface CreateSearchFTSIndexesOptions {
3
10
  onIndexStart?: (table: string, indexName: string) => void;
@@ -1,5 +1,42 @@
1
1
  import { createFTSIndex, dropFTSIndex, DEFAULT_FTS_STEMMER } from '../lbug/lbug-adapter.js';
2
+ import { getExtensionCapabilities } from '../lbug/extension-loader.js';
3
+ import { classifyExtensionLoadError } from '../lbug/extension-load-error.js';
2
4
  import { FTS_INDEXES } from './fts-schema.js';
5
+ /**
6
+ * Strip filesystem paths from a LadybugDB error before it reaches the HTTP
7
+ * `/api/search` and MCP query surfaces (#2374, PR #2375): the raw LOAD error
8
+ * embeds the absolute extension path (username, home dir) which must not leak to
9
+ * a network client. The error class words ("Failed to load library", "invalid
10
+ * ELF header", "has not been installed") have no leading path separator and
11
+ * survive. CLI/doctor/log surfaces keep the full path (they read the reason
12
+ * directly, not through this function).
13
+ */
14
+ const redactPaths = (reason) => reason.replace(/(?:[A-Za-z]:\\|\/)[^\s'"]+/g, '<path>');
15
+ /**
16
+ * Warning attached to search responses when BM25/FTS is degraded. Prefers the
17
+ * live extension-load failure (with LadybugDB's real reason, #2374) over the
18
+ * generic indexes-missing message, so "indexes exist but the extension broke"
19
+ * is not misreported as missing indexes.
20
+ */
21
+ export const ftsDegradedWarning = () => {
22
+ const fts = getExtensionCapabilities().find((c) => c.name === 'fts');
23
+ if (fts && !fts.loaded) {
24
+ const reason = fts.reason ? redactPaths(fts.reason).replace(/\.$/, '') : undefined;
25
+ // A missing *runtime dependency* (Windows error 126, etc.) is not healed by
26
+ // reinstalling (#2374) — surface the classified remedy instead of the generic
27
+ // reinstall tail. Read the diagnosis cached at mark-unavailable time so this
28
+ // per-request path (HTTP /api/search + MCP query) does NO file I/O (#2383 F3);
29
+ // fall back to the pure, no-I/O string classifier if it is somehow absent.
30
+ const { kind, remedy } = fts.diagnosis ?? classifyExtensionLoadError(fts.reason);
31
+ const tail = kind === 'missing_dependency'
32
+ ? ` ${remedy}`
33
+ : '. Run `gitnexus doctor` for details, then `gitnexus analyze --repair-fts` with network access to reinstall.';
34
+ return ('FTS extension failed to load — keyword search degraded' +
35
+ (reason ? ` (${reason})` : '') +
36
+ tail);
37
+ }
38
+ return 'FTS indexes missing — keyword search degraded. Run: gitnexus analyze --repair-fts (or gitnexus analyze --force) to rebuild indexes.';
39
+ };
3
40
  // Stemmers shipped by the LadybugDB FTS extension. Mirrors the lowercase token
4
41
  // set in the extension bundled with @ladybugdb/core 0.18.x (see package.json).
5
42
  // Keep in sync on a LadybugDB minor bump — a value here that the installed
@@ -42,7 +42,7 @@ export declare function getParseDiagnostics(tree: Parser.Tree): {
42
42
  /**
43
43
  * Parse `sourceText` safely on every platform.
44
44
  *
45
- * This is the single "parse safely" entry point and its contract covers three
45
+ * This is the single "parse safely" entry point and its contract covers four
46
46
  * concerns:
47
47
  *
48
48
  * 1. **Windows crash workaround.** Inputs longer than 32 767 chars are fed
@@ -62,7 +62,12 @@ export declare function getParseDiagnostics(tree: Parser.Tree): {
62
62
  * the tree is **returned anyway** — error recovery is a downgrade, never a
63
63
  * drop. Callers wanting the boolean use {@link parseHadErrors}.
64
64
  *
65
- * @param label optional context (e.g. file path) attached to timeout errors
66
- * and degraded-parse logs. Non-breaking trailing param.
65
+ * 4. **Embedded NUL recovery.** U+0000 is replaced with one ASCII space in
66
+ * the parser-only input. The one-for-one substitution keeps tree indices
67
+ * aligned with the original source while preventing language lexers from
68
+ * swallowing declarations during error recovery.
69
+ *
70
+ * @param label optional context (e.g. file path) attached to timeout errors,
71
+ * recovery warnings, and degraded-parse logs. Non-breaking trailing param.
67
72
  */
68
73
  export declare function parseSourceSafe(parser: Parser, sourceText: string, oldTree?: Parser.Tree, options?: Parser.Options, label?: string): Parser.Tree;
@@ -155,7 +155,7 @@ export function getParseDiagnostics(tree) {
155
155
  /**
156
156
  * Parse `sourceText` safely on every platform.
157
157
  *
158
- * This is the single "parse safely" entry point and its contract covers three
158
+ * This is the single "parse safely" entry point and its contract covers four
159
159
  * concerns:
160
160
  *
161
161
  * 1. **Windows crash workaround.** Inputs longer than 32 767 chars are fed
@@ -175,22 +175,36 @@ export function getParseDiagnostics(tree) {
175
175
  * the tree is **returned anyway** — error recovery is a downgrade, never a
176
176
  * drop. Callers wanting the boolean use {@link parseHadErrors}.
177
177
  *
178
- * @param label optional context (e.g. file path) attached to timeout errors
179
- * and degraded-parse logs. Non-breaking trailing param.
178
+ * 4. **Embedded NUL recovery.** U+0000 is replaced with one ASCII space in
179
+ * the parser-only input. The one-for-one substitution keeps tree indices
180
+ * aligned with the original source while preventing language lexers from
181
+ * swallowing declarations during error recovery.
182
+ *
183
+ * @param label optional context (e.g. file path) attached to timeout errors,
184
+ * recovery warnings, and degraded-parse logs. Non-breaking trailing param.
180
185
  */
181
186
  export function parseSourceSafe(parser, sourceText, oldTree, options, label) {
187
+ let parserInput = sourceText;
188
+ if (sourceText.includes('\0')) {
189
+ let nullByteCount = 0;
190
+ parserInput = sourceText.replaceAll('\0', () => {
191
+ nullByteCount += 1;
192
+ return ' ';
193
+ });
194
+ logger.warn({ ...(label ? { file: label } : {}), nullByteCount }, 'replaced embedded NUL bytes before tree-sitter parsing');
195
+ }
182
196
  const budgetMs = resolveParseTimeoutMs();
183
197
  const armed = armParseBudget(parser, budgetMs);
184
198
  let tree;
185
199
  try {
186
- if (sourceText.length <= DIRECT_PARSE_LIMIT_CHARS) {
187
- tree = parser.parse(sourceText, oldTree, options);
200
+ if (parserInput.length <= DIRECT_PARSE_LIMIT_CHARS) {
201
+ tree = parser.parse(parserInput, oldTree, options);
188
202
  }
189
203
  else {
190
204
  const input = (index) => {
191
- if (index >= sourceText.length)
205
+ if (index >= parserInput.length)
192
206
  return null;
193
- return sourceText.slice(index, index + SAFE_PARSE_CHUNK_CHARS);
207
+ return parserInput.slice(index, index + SAFE_PARSE_CHUNK_CHARS);
194
208
  };
195
209
  tree = parser.parse(input, oldTree, options);
196
210
  }
@@ -5,6 +5,7 @@
5
5
  * Uses the MCP-style pooled lbug-adapter for connection management.
6
6
  */
7
7
  import { initLbug, executeQuery, closeLbug, touchRepo, pinRepo } from '../lbug/pool-adapter.js';
8
+ import { escapeCypherString } from '../lbug/cypher-escape.js';
8
9
  const REPO_ID = '__wiki__';
9
10
  /**
10
11
  * Touch the wiki DB connection to prevent idle timeout during long LLM calls.
@@ -98,7 +99,7 @@ export async function getInterFileCallEdges() {
98
99
  export async function getIntraModuleCallEdges(filePaths) {
99
100
  if (filePaths.length === 0)
100
101
  return [];
101
- const fileList = filePaths.map((f) => `'${f.replace(/'/g, "''")}'`).join(', ');
102
+ const fileList = filePaths.map((f) => `'${escapeCypherString(f)}'`).join(', ');
102
103
  const rows = await executeQuery(REPO_ID, `
103
104
  MATCH (a)-[:CodeRelation {type: 'CALLS'}]->(b)
104
105
  WHERE a.filePath IN [${fileList}] AND b.filePath IN [${fileList}]
@@ -118,7 +119,7 @@ export async function getIntraModuleCallEdges(filePaths) {
118
119
  export async function getInterModuleCallEdges(filePaths) {
119
120
  if (filePaths.length === 0)
120
121
  return { outgoing: [], incoming: [] };
121
- const fileList = filePaths.map((f) => `'${f.replace(/'/g, "''")}'`).join(', ');
122
+ const fileList = filePaths.map((f) => `'${escapeCypherString(f)}'`).join(', ');
122
123
  const outRows = await executeQuery(REPO_ID, `
123
124
  MATCH (a)-[:CodeRelation {type: 'CALLS'}]->(b)
124
125
  WHERE a.filePath IN [${fileList}] AND NOT b.filePath IN [${fileList}]
@@ -155,7 +156,7 @@ export async function getInterModuleCallEdges(filePaths) {
155
156
  export async function getProcessesForFiles(filePaths, limit = 5) {
156
157
  if (filePaths.length === 0)
157
158
  return [];
158
- const fileList = filePaths.map((f) => `'${f.replace(/'/g, "''")}'`).join(', ');
159
+ const fileList = filePaths.map((f) => `'${escapeCypherString(f)}'`).join(', ');
159
160
  // Find processes that have steps in the given files
160
161
  const procRows = await executeQuery(REPO_ID, `
161
162
  MATCH (s)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process)
@@ -173,7 +174,7 @@ export async function getProcessesForFiles(filePaths, limit = 5) {
173
174
  const stepCount = row.stepCount || row[3] || 0;
174
175
  // Get the full step trace for this process
175
176
  const stepRows = await executeQuery(REPO_ID, `
176
- MATCH (s)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process {id: '${procId.replace(/'/g, "''")}'})
177
+ MATCH (s)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process {id: '${escapeCypherString(procId)}'})
177
178
  RETURN s.name AS name, s.filePath AS filePath, labels(s)[0] AS type, r.step AS step
178
179
  ORDER BY r.step
179
180
  `);
@@ -210,7 +211,7 @@ export async function getAllProcesses(limit = 20) {
210
211
  const type = row.type || row[2] || 'unknown';
211
212
  const stepCount = row.stepCount || row[3] || 0;
212
213
  const stepRows = await executeQuery(REPO_ID, `
213
- MATCH (s)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process {id: '${procId.replace(/'/g, "''")}'})
214
+ MATCH (s)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process {id: '${escapeCypherString(procId)}'})
214
215
  RETURN s.name AS name, s.filePath AS filePath, labels(s)[0] AS type, r.step AS step
215
216
  ORDER BY r.step
216
217
  `);
@@ -7,8 +7,9 @@
7
7
  import { isHttpMode, getHttpDimensions, httpEmbedQuery, } from '../../core/embeddings/http-client.js';
8
8
  import { resolveEmbeddingConfig } from '../../core/embeddings/config.js';
9
9
  import { applyHfEnvOverrides, isHfDownloadFailure, withHfDownloadRetry, } from '../../core/embeddings/hf-env.js';
10
- import { getLocalEmbeddingRuntimeBlocker } from '../../core/embeddings/runtime-support.js';
10
+ import { getLocalEmbeddingRuntimeBlocker, getMissingLocalEmbeddingStackMessage, } from '../../core/embeddings/runtime-support.js';
11
11
  import { ensureOnnxRuntimeCommonResolvable } from '../../core/embeddings/onnxruntime-common-resolver.js';
12
+ import { ensureEmbeddingStackResolvable } from '../../core/embeddings/runtime-install.js';
12
13
  import { ensureOnnxRuntimeNodeMatchesSystem } from '../../core/embeddings/onnxruntime-node-resolver.js';
13
14
  import { silenceStdout, restoreStdout, realStderrWrite } from '../../core/lbug/pool-adapter.js';
14
15
  import { logger } from '../../core/logger.js';
@@ -45,6 +46,11 @@ export const initEmbedder = async () => {
45
46
  try {
46
47
  // Lazy-load transformers.js only after the runtime guard has passed, so
47
48
  // unsupported platforms never reach the native ONNX import (#1515).
49
+ // Registered FIRST so it sits last in the hook chain (registerHooks runs
50
+ // the most recent hook first): when the optional stack was pruned at
51
+ // install time (#2370), its bare specifiers fall back to the on-demand
52
+ // runtime prefix.
53
+ ensureEmbeddingStackResolvable();
48
54
  // Under pnpm-strict / `pnpm dlx`, transformers' phantom `onnxruntime-common`
49
55
  // import is unresolvable; register the fallback resolver first (#307).
50
56
  ensureOnnxRuntimeCommonResolvable();
@@ -55,7 +61,15 @@ export const initEmbedder = async () => {
55
61
  // Windows/DirectML, and macOS. Mirrors the core embedder's call site so
56
62
  // MCP query-time embedding gets the same CUDA-13 fix.
57
63
  ensureOnnxRuntimeNodeMatchesSystem();
58
- const { pipeline, env } = await import('@huggingface/transformers');
64
+ // The stack is an optionalDependency: npm prunes it when onnxruntime-node's
65
+ // postinstall can't reach api.nuget.org (#2370). Rethrow with actionable
66
+ // reinstall guidance instead of a raw ERR_MODULE_NOT_FOUND.
67
+ const { pipeline, env } = await import('@huggingface/transformers').catch((err) => {
68
+ const missing = getMissingLocalEmbeddingStackMessage(err);
69
+ if (missing)
70
+ throw new Error(missing);
71
+ throw err;
72
+ });
59
73
  env.allowLocalModels = false;
60
74
  // Bridge user-controlled env vars to transformers.js: HF_HOME →
61
75
  // env.cacheDir, HF_ENDPOINT → env.remoteHost (#1205). Centralised in
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Convert a 0-based GraphNode `startLine`/`endLine` to the 1-based line number
3
+ * shown to humans and LLMs in MCP tool output.
4
+ *
5
+ * Storage is 0-based (tree-sitter `startPosition.row`; see
6
+ * `ingestion/utils/line-base.ts`), which matches editors/`sed`/`less -N` only
7
+ * after `+ 1`. The `context`, `query`, and `impact` tools present line numbers a
8
+ * user cross-references against source, so they convert here at the response
9
+ * boundary (#2377).
10
+ *
11
+ * Apply ONLY to a symbol node's 0-based `startLine`/`endLine`. Do NOT apply to:
12
+ * - BasicBlock / CFG `functionStartLine` and PDG statement lines — already
13
+ * 1-based (they use `startPosition.row + 1`);
14
+ * - the internal `sym.startLine + 1` join params that target the 1-based
15
+ * BasicBlock id space;
16
+ * - raw `cypher` results, which pass LadybugDB columns through verbatim and
17
+ * stay 0-based (documented).
18
+ *
19
+ * `undefined`/`null` pass through so optional line fields stay absent.
20
+ */
21
+ export declare function toDisplayLine(zeroBasedLine: number): number;
22
+ export declare function toDisplayLine(zeroBasedLine: number | null | undefined): number | undefined;
@@ -0,0 +1,3 @@
1
+ export function toDisplayLine(zeroBasedLine) {
2
+ return typeof zeroBasedLine === 'number' ? zeroBasedLine + 1 : undefined;
3
+ }
@@ -162,10 +162,24 @@ export declare class LocalBackend {
162
162
  * stderr noisy per DoD §2.8.
163
163
  */
164
164
  private warnedVectorUnsupported;
165
+ /**
166
+ * One-shot warning when a pruned or Node-unloadable optional embedding stack
167
+ * (#2370/#2372) forces semantic search to fall back to BM25 — so the
168
+ * degradation is visible once instead of silent.
169
+ */
170
+ private warnedMissingEmbeddingStack;
165
171
  /**
166
172
  * Cross-repo group tools (CLI). Shares logic with MCP `group_*` handlers.
167
173
  */
168
174
  getGroupService(): GroupService;
175
+ /**
176
+ * Adapt local `trace` to the group port. The assembled group/cross-repo trace
177
+ * presents 1-based endpoints (via resolveSymbolForGroup), so convert the hop
178
+ * lines here too — otherwise one response mixes 1-based endpoints with 0-based
179
+ * hops (#2380). Single-repo `trace` dispatches directly (not through this
180
+ * port) and stays 0-based (documented full-parity follow-up).
181
+ */
182
+ private traceForGroup;
169
183
  /**
170
184
  * Adapt the shared symbol resolver to the GroupToolPort contract. Used by the
171
185
  * cross-repo trace path to locate which member repo an endpoint lives in and
@@ -10,6 +10,7 @@ import path from 'path';
10
10
  import { createHash } from 'crypto';
11
11
  import { initLbug, executeQuery, executeParameterized, closeLbug, isLbugReady, } from '../../core/lbug/pool-adapter.js';
12
12
  import { isValidQueryParams } from '../../core/lbug/query-params.js';
13
+ import { toDisplayLine } from './line-display.js';
13
14
  import { isWalCorruptionError, WAL_RECOVERY_SUGGESTION } from '../../core/lbug/lbug-config.js';
14
15
  // Embedding imports are lazy (dynamic import) to avoid loading onnxruntime-node
15
16
  // at MCP server startup — crashes on unsupported Node ABI versions (#89)
@@ -26,9 +27,11 @@ import { rankExactEmbeddingRows, } from '../../core/embeddings/exact-search.js';
26
27
  import { EMBEDDING_TABLE_NAME, EMBEDDING_INDEX_NAME } from '../../core/lbug/schema.js';
27
28
  import { getExactScanLimit, isVectorExtensionSupportedByPlatform, } from '../../core/platform/capabilities.js';
28
29
  import { PhaseTimer } from '../../core/search/phase-timer.js';
30
+ import { ftsDegradedWarning } from '../../core/search/fts-indexes.js';
29
31
  import { cjkSegmentationModeMismatch, containsSegmentableCjkRun, getSearchFTSCjkSegmentation, isSupportedCjkSegmentationMode, MAX_CJK_SEGMENTATION_QUERY_LENGTH, } from '../../core/search/cjk-segmentation.js';
30
32
  import { checkStalenessAsync, checkCwdMatch } from '../../core/git-staleness.js';
31
33
  import { logger } from '../../core/logger.js';
34
+ import { isLocalEmbeddingRuntimeBlockerMessage, isMissingLocalEmbeddingStackMessage, } from '../../core/embeddings/runtime-support.js';
32
35
  import { LIST_REPOS_DEFAULT_LIMIT, LIST_REPOS_MAX_LIMIT, EXPLAIN_DEFAULT_LIMIT, EXPLAIN_MAX_LIMIT, PDG_QUERY_DEFAULT_LIMIT, PDG_QUERY_MAX_LIMIT, } from '../tools.js';
33
36
  import { findImportCycles } from '../../core/graph/import-cycles.js';
34
37
  import { decodeTaintPath } from '../../core/ingestion/taint/path-codec.js';
@@ -434,6 +437,12 @@ export class LocalBackend {
434
437
  * stderr noisy per DoD §2.8.
435
438
  */
436
439
  warnedVectorUnsupported = false;
440
+ /**
441
+ * One-shot warning when a pruned or Node-unloadable optional embedding stack
442
+ * (#2370/#2372) forces semantic search to fall back to BM25 — so the
443
+ * degradation is visible once instead of silent.
444
+ */
445
+ warnedMissingEmbeddingStack = false;
437
446
  /**
438
447
  * Cross-repo group tools (CLI). Shares logic with MCP `group_*` handlers.
439
448
  */
@@ -445,7 +454,7 @@ export class LocalBackend {
445
454
  query: (r, p) => this.query(r, p),
446
455
  impactByUid: (id, uid, d, o) => this.impactByUid(id, uid, d, o),
447
456
  context: (r, p) => this.context(r, p),
448
- trace: (r, p) => this.trace(r, p),
457
+ trace: (r, p) => this.traceForGroup(r, p),
449
458
  resolveSymbol: (r, q) => this.resolveSymbolForGroup(r, q),
450
459
  pdgFlows: (r, anchor, opts) => this.pdgFlowsForGroup(r, anchor, opts),
451
460
  };
@@ -453,6 +462,23 @@ export class LocalBackend {
453
462
  }
454
463
  return this.groupToolSvc;
455
464
  }
465
+ /**
466
+ * Adapt local `trace` to the group port. The assembled group/cross-repo trace
467
+ * presents 1-based endpoints (via resolveSymbolForGroup), so convert the hop
468
+ * lines here too — otherwise one response mixes 1-based endpoints with 0-based
469
+ * hops (#2380). Single-repo `trace` dispatches directly (not through this
470
+ * port) and stays 0-based (documented full-parity follow-up).
471
+ */
472
+ async traceForGroup(repo, params) {
473
+ const result = await this.trace(repo, params);
474
+ const hops = result.hops;
475
+ if (Array.isArray(hops)) {
476
+ for (const hop of hops) {
477
+ hop.startLine = toDisplayLine(hop.startLine);
478
+ }
479
+ }
480
+ return result;
481
+ }
456
482
  /**
457
483
  * Adapt the shared symbol resolver to the GroupToolPort contract. Used by the
458
484
  * cross-repo trace path to locate which member repo an endpoint lives in and
@@ -470,8 +496,8 @@ export class LocalBackend {
470
496
  name: s.name,
471
497
  type: s.type,
472
498
  filePath: s.filePath,
473
- startLine: s.startLine,
474
- endLine: s.endLine,
499
+ startLine: toDisplayLine(s.startLine),
500
+ endLine: toDisplayLine(s.endLine),
475
501
  },
476
502
  };
477
503
  }
@@ -483,7 +509,7 @@ export class LocalBackend {
483
509
  name: c.name,
484
510
  type: c.type,
485
511
  filePath: c.filePath,
486
- startLine: c.startLine,
512
+ startLine: toDisplayLine(c.startLine),
487
513
  })),
488
514
  };
489
515
  }
@@ -1564,8 +1590,8 @@ export class LocalBackend {
1564
1590
  name: sym.name,
1565
1591
  type: sym.type,
1566
1592
  filePath: sym.filePath,
1567
- startLine: sym.startLine,
1568
- endLine: sym.endLine,
1593
+ startLine: toDisplayLine(sym.startLine),
1594
+ endLine: toDisplayLine(sym.endLine),
1569
1595
  ...(module ? { module } : {}),
1570
1596
  ...(includeContent && content ? { content } : {}),
1571
1597
  };
@@ -1652,7 +1678,7 @@ export class LocalBackend {
1652
1678
  // path, leaving the success-path response shape byte-identical.
1653
1679
  const warnings = [];
1654
1680
  if (!ftsUsed) {
1655
- warnings.push('FTS indexes missing — keyword search degraded. Run: gitnexus analyze --repair-fts (or gitnexus analyze --force) to rebuild indexes.');
1681
+ warnings.push(ftsDegradedWarning());
1656
1682
  }
1657
1683
  // #2331: a CJK query against a server process resolving
1658
1684
  // GITNEXUS_FTS_CJK_SEGMENTATION to 'none' silently misses sub-phrase
@@ -1791,8 +1817,11 @@ export class LocalBackend {
1791
1817
  name: sym.name || sym[1],
1792
1818
  type: sym.type || sym[2],
1793
1819
  filePath: sym.filePath || sym[3],
1794
- startLine: sym.startLine || sym[4],
1795
- endLine: sym.endLine || sym[5],
1820
+ // Raw 0-based here — `bm25Search` is only called from `query()`,
1821
+ // whose aggregation loop applies `toDisplayLine` once (see below).
1822
+ // Converting here too would double-shift BM25-matched lines (#2380).
1823
+ startLine: sym.startLine ?? sym[4],
1824
+ endLine: sym.endLine ?? sym[5],
1796
1825
  bm25Score: bm25Result.score,
1797
1826
  });
1798
1827
  }
@@ -1927,8 +1956,20 @@ export class LocalBackend {
1927
1956
  }
1928
1957
  return results;
1929
1958
  }
1930
- catch {
1931
- // Expected when embeddings are disabled silently fall back to BM25-only
1959
+ catch (err) {
1960
+ // Embeddings disabled is the common, silent case. But a pruned or
1961
+ // Node-unloadable optional stack (#2370/#2372) also lands here — surface it
1962
+ // once so semantic search doesn't silently degrade to BM25 with no hint
1963
+ // (the exact silent-degradation mode #2370 exists to fix). Emitted once per
1964
+ // LocalBackend instance to keep stderr quiet on hot paths (like the VECTOR
1965
+ // fallback above). All other errors stay silent, as before.
1966
+ const message = err instanceof Error ? err.message : '';
1967
+ if (!this.warnedMissingEmbeddingStack &&
1968
+ (isMissingLocalEmbeddingStackMessage(message) ||
1969
+ isLocalEmbeddingRuntimeBlockerMessage(message))) {
1970
+ this.warnedMissingEmbeddingStack = true;
1971
+ logger.warn(`GitNexus [query:vector]: ${message}`);
1972
+ }
1932
1973
  return [];
1933
1974
  }
1934
1975
  }
@@ -2376,7 +2417,7 @@ export class LocalBackend {
2376
2417
  name: c.name,
2377
2418
  kind: c.type,
2378
2419
  filePath: c.filePath,
2379
- line: c.startLine,
2420
+ line: toDisplayLine(c.startLine),
2380
2421
  score: Number(c.score.toFixed(2)),
2381
2422
  })),
2382
2423
  };
@@ -2583,8 +2624,8 @@ export class LocalBackend {
2583
2624
  name: sym.name || sym[1],
2584
2625
  kind: symKind,
2585
2626
  filePath: sym.filePath || sym[3],
2586
- startLine: sym.startLine || sym[4],
2587
- endLine: sym.endLine || sym[5],
2627
+ startLine: toDisplayLine(sym.startLine ?? sym[4]),
2628
+ endLine: toDisplayLine(sym.endLine ?? sym[5]),
2588
2629
  ...(include_content && (sym.content || sym[6]) ? { content: sym.content || sym[6] } : {}),
2589
2630
  ...(methodMetadata ? { methodMetadata } : {}),
2590
2631
  },
@@ -2660,7 +2701,7 @@ export class LocalBackend {
2660
2701
  name: c.name,
2661
2702
  kind: c.type,
2662
2703
  filePath: c.filePath,
2663
- line: c.startLine,
2704
+ line: toDisplayLine(c.startLine),
2664
2705
  score: Number(c.score.toFixed(2)),
2665
2706
  })),
2666
2707
  },
@@ -2678,11 +2719,14 @@ export class LocalBackend {
2678
2719
  return {
2679
2720
  anchorClause: 'a.id STARTS WITH $idPrefix AND a.startLine >= $symStart AND a.startLine <= $symEnd',
2680
2721
  queryParams: { idPrefix, symStart: sym.startLine + 1, symEnd: sym.endLine + 1 },
2722
+ // Display anchor is 1-based, matching the ambiguous-candidate branch and
2723
+ // the context/query/impact tools (#2380). This is display-only — the
2724
+ // BasicBlock join above uses the raw `sym.startLine + 1` in `symStart`.
2681
2725
  anchor: {
2682
2726
  file: sym.filePath,
2683
2727
  symbol: sym.name,
2684
- startLine: sym.startLine,
2685
- endLine: sym.endLine,
2728
+ startLine: toDisplayLine(sym.startLine),
2729
+ endLine: toDisplayLine(sym.endLine),
2686
2730
  },
2687
2731
  };
2688
2732
  }
@@ -4016,7 +4060,7 @@ export class LocalBackend {
4016
4060
  name: c.name,
4017
4061
  kind: c.type,
4018
4062
  filePath: c.filePath,
4019
- line: c.startLine,
4063
+ line: toDisplayLine(c.startLine),
4020
4064
  score: Number(c.score.toFixed(2)),
4021
4065
  })),
4022
4066
  };
@@ -4069,7 +4113,7 @@ export class LocalBackend {
4069
4113
  name: c.name,
4070
4114
  kind: c.type,
4071
4115
  filePath: c.filePath,
4072
- line: c.startLine,
4116
+ line: toDisplayLine(c.startLine),
4073
4117
  score: Number(c.score.toFixed(2)),
4074
4118
  impactedCount: summary?.impactedCount ?? 0,
4075
4119
  risk: summary?.risk ?? 'UNKNOWN',
@@ -34,8 +34,10 @@ export declare function splitCalleeIds(raw: unknown): string[];
34
34
  * Contract version of the mode:'pdg' impact result shape. A stable discriminator
35
35
  * for external MCP/agent consumers — distinct from the DB INCREMENTAL_SCHEMA_VERSION.
36
36
  * Bump on any breaking change to the PDG result fields.
37
+ * v2: `startLine` in the result is now 1-based display (#2380), matching the
38
+ * context/query/impact tools (was 0-based).
37
39
  */
38
- export declare const PDG_RESULT_VERSION: 1;
40
+ export declare const PDG_RESULT_VERSION: 2;
39
41
  /** A reachable dependence block resolved to its source statement. */
40
42
  export interface PdgStatement {
41
43
  /** 1-based source line where the statement's block starts. */
@@ -105,7 +107,7 @@ export interface PdgInterproceduralImpact {
105
107
  export interface PdgImpactBaseResult extends PdgImpactParityFields {
106
108
  mode: 'pdg';
107
109
  /** Contract version of the mode:'pdg' impact result shape; bump on any breaking change to the PDG result fields. */
108
- pdgResultVersion: 1;
110
+ pdgResultVersion: 2;
109
111
  target: PdgImpactTarget;
110
112
  direction: 'upstream' | 'downstream';
111
113
  impactedCount: number;
@@ -172,7 +174,7 @@ export interface PdgImpactDegradedResult extends PdgImpactBaseResult {
172
174
  export interface PdgImpactErrorResult {
173
175
  mode?: 'pdg';
174
176
  /** Contract version of the mode:'pdg' impact result shape; bump on any breaking change to the PDG result fields. */
175
- pdgResultVersion: 1;
177
+ pdgResultVersion: 2;
176
178
  error: string;
177
179
  target: PdgImpactTarget;
178
180
  direction: 'upstream' | 'downstream';
@@ -9,6 +9,7 @@ import path from 'path';
9
9
  import { loadMeta } from '../../storage/repo-manager.js';
10
10
  import { IMPACT_MAX_DEPTH, PDG_QUERY_DEFAULT_LIMIT, PDG_QUERY_MAX_LIMIT } from '../tools.js';
11
11
  import { CALLEES_TRUNCATED_SENTINEL, CALLEE_ID_SEP } from '../../core/ingestion/cfg/emit.js';
12
+ import { toDisplayLine } from './line-display.js';
12
13
  import { decodeCallSummary } from '../../core/ingestion/taint/call-summary-codec.js';
13
14
  import { decodeReachingDefReason } from '../../core/ingestion/cfg/reaching-def-reason-codec.js';
14
15
  import { getProviderForFile } from '../../core/ingestion/languages/index.js';
@@ -84,8 +85,10 @@ export function splitCalleeIds(raw) {
84
85
  * Contract version of the mode:'pdg' impact result shape. A stable discriminator
85
86
  * for external MCP/agent consumers — distinct from the DB INCREMENTAL_SCHEMA_VERSION.
86
87
  * Bump on any breaking change to the PDG result fields.
88
+ * v2: `startLine` in the result is now 1-based display (#2380), matching the
89
+ * context/query/impact tools (was 0-based).
87
90
  */
88
- export const PDG_RESULT_VERSION = 1;
91
+ export const PDG_RESULT_VERSION = 2;
89
92
  /**
90
93
  * FU-B-2 intra-block def→use line walk. Given a block's self REACHING_DEF
91
94
  * def→use line PAIRS (every `defLine → useLine` step decoded from the edge
@@ -519,7 +522,7 @@ function assemblePdgImpactResult(input) {
519
522
  name: s.name,
520
523
  type: s.type,
521
524
  filePath: s.filePath,
522
- ...(s.startLine !== undefined ? { startLine: s.startLine } : {}),
525
+ ...(s.startLine !== undefined ? { startLine: toDisplayLine(s.startLine) } : {}),
523
526
  ...(s.ambiguous ? { ambiguous: true } : {}),
524
527
  ...(s.id === null ? { unresolved: true } : {}),
525
528
  pdgEvidence: (s.id === null ? 'degraded' : 'owner-projection'),
@@ -365,6 +365,7 @@ additional_node_types: "Multi-language: Struct, Enum, Macro, Typedef, Union, Nam
365
365
 
366
366
  node_properties:
367
367
  common: "name (STRING), filePath (STRING), startLine (INT32), endLine (INT32)"
368
+ line_numbers: "startLine/endLine on symbol nodes are 0-BASED (tree-sitter rows) in storage AND in raw Cypher results. The context, query, impact, group/cross-repo trace, and explain/pdg_query (symbol anchor) tools present them 1-BASED (editor / sed / less -N aligned), so a symbol spans editor lines (startLine+1)..(endLine+1) — e.g. sed '<startLine+1>,<endLine+1>!d' <file>. Single-repo trace symbol lines stay 0-BASED for now (full-parity follow-up). content holds the exact symbol span. (BasicBlock / PDG statement lines are separately 1-based.) (#2377, #2380)"
368
369
  Method: "parameterCount (INT32), returnType (STRING), isVariadic (BOOL), visibility (STRING), isStatic (BOOL), isAbstract (BOOL), isFinal (BOOL), isVirtual (BOOL), isOverride (BOOL), isAsync (BOOL), isPartial (BOOL), requiredParameterCount (INT32), parameterTypes (STRING[]), annotations (STRING[])"
369
370
  Function: "parameterCount (INT32), returnType (STRING), isVariadic (BOOL), visibility (STRING), isStatic (BOOL), isAbstract (BOOL), isFinal (BOOL), isAsync (BOOL), parameterTypes (STRING[]), annotations (STRING[])"
370
371
  Property: "declaredType (STRING) — the field's type annotation (e.g., 'Address', 'City'). Used for field-access chain resolution."
package/dist/mcp/tools.js CHANGED
@@ -379,7 +379,7 @@ MODE (opt-in): "callgraph" (default) walks symbol→symbol edges (CALLS/IMPORTS/
379
379
 
380
380
  STATEMENT-ANCHORED PDG SLICE: with mode:'pdg', pass "line" (1-based source line within the target symbol) to seed the dependence slice on the statement at that line and return what depends on it in affectedStatements (line + text). Inter-procedural symbols are still reported through interproceduralByDepth/pdgInterprocedural and the compatibility byDepth bucket. Without "line", pdg returns whole-symbol inter-procedural reach plus local whole-symbol PDG diagnostics.
381
381
 
382
- PDG OUTPUT CONTRACT: every mode:'pdg' result (success, empty, degraded, or error) carries pdgResultVersion:1 — a stable discriminator for external consumers that bumps on any breaking change to the PDG result shape (distinct from the DB schema version). Successful PDG results include mode:'pdg', a full target envelope (id/name/type/filePath), affectedStatements, affectedStatementCount, interproceduralByDepth/pdgInterprocedural for cross-function reach, compatibility byDepth/byDepthCounts, risk:'UNKNOWN', and a note describing the unified contract. Degraded PDG results (no-layer, sub-layer-missing, unknown) keep mode:'pdg', pdgResultVersion:1, target metadata when the target resolves, risk:'UNKNOWN', note/remediation, and empty byDepth parity fields — never a false-safe zero. If depth and limit both bound the slice, truncatedByReasons reports both causes while truncatedBy remains scalar.
382
+ PDG OUTPUT CONTRACT: every mode:'pdg' result (success, empty, degraded, or error) carries pdgResultVersion:2 — a stable discriminator for external consumers that bumps on any breaking change to the PDG result shape (distinct from the DB schema version). Successful PDG results include mode:'pdg', a full target envelope (id/name/type/filePath), affectedStatements, affectedStatementCount, interproceduralByDepth/pdgInterprocedural for cross-function reach, compatibility byDepth/byDepthCounts, risk:'UNKNOWN', and a note describing the unified contract. Degraded PDG results (no-layer, sub-layer-missing, unknown) keep mode:'pdg', pdgResultVersion:2, target metadata when the target resolves, risk:'UNKNOWN', note/remediation, and empty byDepth parity fields — never a false-safe zero. If depth and limit both bound the slice, truncatedByReasons reports both causes while truncatedBy remains scalar.
383
383
 
384
384
  WHEN TO USE: Before making code changes — especially refactoring, renaming, or modifying shared code. Shows what would break.
385
385
  AFTER THIS: Review d=1 items (WILL BREAK). Use context() on high-risk symbols.
@@ -17,6 +17,12 @@ export interface LaunchDeps {
17
17
  };
18
18
  acquireRepoLock: (key: string) => string | null;
19
19
  releaseRepoLock: (key: string) => void;
20
+ /**
21
+ * Drops the server's cached LadybugDB handle (closeLbug). The worker
22
+ * process rewrites the repo's DB files on disk, so a connection opened
23
+ * before the rewrite keeps reading the pre-rewrite state until evicted.
24
+ */
25
+ closeDbHandle: () => Promise<void>;
20
26
  }
21
27
  export interface LaunchOptions {
22
28
  force?: boolean;