@thoughtflow/codebase 0.2.0

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 (278) hide show
  1. package/dist/contexts/context-decomposer.d.ts +13 -0
  2. package/dist/contexts/context-decomposer.d.ts.map +1 -0
  3. package/dist/contexts/context-decomposer.js +97 -0
  4. package/dist/contexts/context-decomposer.js.map +1 -0
  5. package/dist/contexts/context-document-manager.d.ts +21 -0
  6. package/dist/contexts/context-document-manager.d.ts.map +1 -0
  7. package/dist/contexts/context-document-manager.js +69 -0
  8. package/dist/contexts/context-document-manager.js.map +1 -0
  9. package/dist/contexts/context-router.d.ts +13 -0
  10. package/dist/contexts/context-router.d.ts.map +1 -0
  11. package/dist/contexts/context-router.js +94 -0
  12. package/dist/contexts/context-router.js.map +1 -0
  13. package/dist/index.d.ts +53 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +76 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/libs/codebase/deep-analyzer.d.ts +58 -0
  18. package/dist/libs/codebase/deep-analyzer.d.ts.map +1 -0
  19. package/dist/libs/codebase/deep-analyzer.js +239 -0
  20. package/dist/libs/codebase/deep-analyzer.js.map +1 -0
  21. package/dist/libs/codebase/graph/dependency-extractor.d.ts +20 -0
  22. package/dist/libs/codebase/graph/dependency-extractor.d.ts.map +1 -0
  23. package/dist/libs/codebase/graph/dependency-extractor.js +174 -0
  24. package/dist/libs/codebase/graph/dependency-extractor.js.map +1 -0
  25. package/dist/libs/codebase/graph/graph-analysis.d.ts +42 -0
  26. package/dist/libs/codebase/graph/graph-analysis.d.ts.map +1 -0
  27. package/dist/libs/codebase/graph/graph-analysis.js +222 -0
  28. package/dist/libs/codebase/graph/graph-analysis.js.map +1 -0
  29. package/dist/libs/codebase/graph/graph-visualizer.d.ts +12 -0
  30. package/dist/libs/codebase/graph/graph-visualizer.d.ts.map +1 -0
  31. package/dist/libs/codebase/graph/graph-visualizer.js +99 -0
  32. package/dist/libs/codebase/graph/graph-visualizer.js.map +1 -0
  33. package/dist/libs/codebase/indexer/batch-processor.d.ts +18 -0
  34. package/dist/libs/codebase/indexer/batch-processor.d.ts.map +1 -0
  35. package/dist/libs/codebase/indexer/batch-processor.js +39 -0
  36. package/dist/libs/codebase/indexer/batch-processor.js.map +1 -0
  37. package/dist/libs/codebase/indexer/chunker-markdown.d.ts +19 -0
  38. package/dist/libs/codebase/indexer/chunker-markdown.d.ts.map +1 -0
  39. package/dist/libs/codebase/indexer/chunker-markdown.js +138 -0
  40. package/dist/libs/codebase/indexer/chunker-markdown.js.map +1 -0
  41. package/dist/libs/codebase/indexer/chunker.d.ts +21 -0
  42. package/dist/libs/codebase/indexer/chunker.d.ts.map +1 -0
  43. package/dist/libs/codebase/indexer/chunker.js +133 -0
  44. package/dist/libs/codebase/indexer/chunker.js.map +1 -0
  45. package/dist/libs/codebase/indexer/explorer.d.ts +29 -0
  46. package/dist/libs/codebase/indexer/explorer.d.ts.map +1 -0
  47. package/dist/libs/codebase/indexer/explorer.js +278 -0
  48. package/dist/libs/codebase/indexer/explorer.js.map +1 -0
  49. package/dist/libs/codebase/indexer/indexer.d.ts +35 -0
  50. package/dist/libs/codebase/indexer/indexer.d.ts.map +1 -0
  51. package/dist/libs/codebase/indexer/indexer.js +172 -0
  52. package/dist/libs/codebase/indexer/indexer.js.map +1 -0
  53. package/dist/libs/codebase/knowledge/cross-referencer.d.ts +32 -0
  54. package/dist/libs/codebase/knowledge/cross-referencer.d.ts.map +1 -0
  55. package/dist/libs/codebase/knowledge/cross-referencer.js +132 -0
  56. package/dist/libs/codebase/knowledge/cross-referencer.js.map +1 -0
  57. package/dist/libs/codebase/knowledge/markdown-extractor.d.ts +34 -0
  58. package/dist/libs/codebase/knowledge/markdown-extractor.d.ts.map +1 -0
  59. package/dist/libs/codebase/knowledge/markdown-extractor.js +273 -0
  60. package/dist/libs/codebase/knowledge/markdown-extractor.js.map +1 -0
  61. package/dist/libs/codebase/knowledge/summarizer.d.ts +25 -0
  62. package/dist/libs/codebase/knowledge/summarizer.d.ts.map +1 -0
  63. package/dist/libs/codebase/knowledge/summarizer.js +61 -0
  64. package/dist/libs/codebase/knowledge/summarizer.js.map +1 -0
  65. package/dist/libs/codebase/search/bm25.d.ts +23 -0
  66. package/dist/libs/codebase/search/bm25.d.ts.map +1 -0
  67. package/dist/libs/codebase/search/bm25.js +93 -0
  68. package/dist/libs/codebase/search/bm25.js.map +1 -0
  69. package/dist/libs/codebase/search/hybrid-search.d.ts +22 -0
  70. package/dist/libs/codebase/search/hybrid-search.d.ts.map +1 -0
  71. package/dist/libs/codebase/search/hybrid-search.js +80 -0
  72. package/dist/libs/codebase/search/hybrid-search.js.map +1 -0
  73. package/dist/libs/codebase-index.class.d.ts +66 -0
  74. package/dist/libs/codebase-index.class.d.ts.map +1 -0
  75. package/dist/libs/codebase-index.class.js +595 -0
  76. package/dist/libs/codebase-index.class.js.map +1 -0
  77. package/dist/tools/codebase/codebase-analyze.tool.d.ts +8 -0
  78. package/dist/tools/codebase/codebase-analyze.tool.d.ts.map +1 -0
  79. package/dist/tools/codebase/codebase-analyze.tool.js +20 -0
  80. package/dist/tools/codebase/codebase-analyze.tool.js.map +1 -0
  81. package/dist/tools/codebase/codebase-crossref.tool.d.ts +8 -0
  82. package/dist/tools/codebase/codebase-crossref.tool.d.ts.map +1 -0
  83. package/dist/tools/codebase/codebase-crossref.tool.js +20 -0
  84. package/dist/tools/codebase/codebase-crossref.tool.js.map +1 -0
  85. package/dist/tools/codebase/codebase-flow.tool.d.ts +8 -0
  86. package/dist/tools/codebase/codebase-flow.tool.d.ts.map +1 -0
  87. package/dist/tools/codebase/codebase-flow.tool.js +20 -0
  88. package/dist/tools/codebase/codebase-flow.tool.js.map +1 -0
  89. package/dist/tools/codebase/codebase-graph-circular.tool.d.ts +8 -0
  90. package/dist/tools/codebase/codebase-graph-circular.tool.d.ts.map +1 -0
  91. package/dist/tools/codebase/codebase-graph-circular.tool.js +17 -0
  92. package/dist/tools/codebase/codebase-graph-circular.tool.js.map +1 -0
  93. package/dist/tools/codebase/codebase-graph-query.tool.d.ts +8 -0
  94. package/dist/tools/codebase/codebase-graph-query.tool.d.ts.map +1 -0
  95. package/dist/tools/codebase/codebase-graph-query.tool.js +21 -0
  96. package/dist/tools/codebase/codebase-graph-query.tool.js.map +1 -0
  97. package/dist/tools/codebase/codebase-graph-stats.tool.d.ts +8 -0
  98. package/dist/tools/codebase/codebase-graph-stats.tool.d.ts.map +1 -0
  99. package/dist/tools/codebase/codebase-graph-stats.tool.js +17 -0
  100. package/dist/tools/codebase/codebase-graph-stats.tool.js.map +1 -0
  101. package/dist/tools/codebase/codebase-graph-visualize.tool.d.ts +8 -0
  102. package/dist/tools/codebase/codebase-graph-visualize.tool.d.ts.map +1 -0
  103. package/dist/tools/codebase/codebase-graph-visualize.tool.js +21 -0
  104. package/dist/tools/codebase/codebase-graph-visualize.tool.js.map +1 -0
  105. package/dist/tools/codebase/codebase-impact.tool.d.ts +8 -0
  106. package/dist/tools/codebase/codebase-impact.tool.d.ts.map +1 -0
  107. package/dist/tools/codebase/codebase-impact.tool.js +21 -0
  108. package/dist/tools/codebase/codebase-impact.tool.js.map +1 -0
  109. package/dist/tools/codebase/codebase-index.tool.d.ts +8 -0
  110. package/dist/tools/codebase/codebase-index.tool.d.ts.map +1 -0
  111. package/dist/tools/codebase/codebase-index.tool.js +21 -0
  112. package/dist/tools/codebase/codebase-index.tool.js.map +1 -0
  113. package/dist/tools/codebase/codebase-knowledge.tool.d.ts +8 -0
  114. package/dist/tools/codebase/codebase-knowledge.tool.d.ts.map +1 -0
  115. package/dist/tools/codebase/codebase-knowledge.tool.js +22 -0
  116. package/dist/tools/codebase/codebase-knowledge.tool.js.map +1 -0
  117. package/dist/tools/codebase/codebase-search.tool.d.ts +8 -0
  118. package/dist/tools/codebase/codebase-search.tool.d.ts.map +1 -0
  119. package/dist/tools/codebase/codebase-search.tool.js +24 -0
  120. package/dist/tools/codebase/codebase-search.tool.js.map +1 -0
  121. package/dist/tools/codebase/codebase-status.tool.d.ts +8 -0
  122. package/dist/tools/codebase/codebase-status.tool.d.ts.map +1 -0
  123. package/dist/tools/codebase/codebase-status.tool.js +19 -0
  124. package/dist/tools/codebase/codebase-status.tool.js.map +1 -0
  125. package/dist/tools/neural/comparative-analysis.tool.d.ts +37 -0
  126. package/dist/tools/neural/comparative-analysis.tool.d.ts.map +1 -0
  127. package/dist/tools/neural/comparative-analysis.tool.js +241 -0
  128. package/dist/tools/neural/comparative-analysis.tool.js.map +1 -0
  129. package/dist/tools/neural/index.d.ts +12 -0
  130. package/dist/tools/neural/index.d.ts.map +1 -0
  131. package/dist/tools/neural/index.js +28 -0
  132. package/dist/tools/neural/index.js.map +1 -0
  133. package/dist/tools/neural/neural-deadcode.tool.d.ts +10 -0
  134. package/dist/tools/neural/neural-deadcode.tool.d.ts.map +1 -0
  135. package/dist/tools/neural/neural-deadcode.tool.js +133 -0
  136. package/dist/tools/neural/neural-deadcode.tool.js.map +1 -0
  137. package/dist/tools/neural/neural-debug.tool.d.ts +11 -0
  138. package/dist/tools/neural/neural-debug.tool.d.ts.map +1 -0
  139. package/dist/tools/neural/neural-debug.tool.js +205 -0
  140. package/dist/tools/neural/neural-debug.tool.js.map +1 -0
  141. package/dist/tools/neural/neural-explain.tool.d.ts +9 -0
  142. package/dist/tools/neural/neural-explain.tool.d.ts.map +1 -0
  143. package/dist/tools/neural/neural-explain.tool.js +100 -0
  144. package/dist/tools/neural/neural-explain.tool.js.map +1 -0
  145. package/dist/tools/neural/neural-optimize.tool.d.ts +9 -0
  146. package/dist/tools/neural/neural-optimize.tool.d.ts.map +1 -0
  147. package/dist/tools/neural/neural-optimize.tool.js +117 -0
  148. package/dist/tools/neural/neural-optimize.tool.js.map +1 -0
  149. package/dist/tools/neural/neural-pattern.tool.d.ts +10 -0
  150. package/dist/tools/neural/neural-pattern.tool.d.ts.map +1 -0
  151. package/dist/tools/neural/neural-pattern.tool.js +169 -0
  152. package/dist/tools/neural/neural-pattern.tool.js.map +1 -0
  153. package/dist/tools/neural/neural-review.tool.d.ts +11 -0
  154. package/dist/tools/neural/neural-review.tool.d.ts.map +1 -0
  155. package/dist/tools/neural/neural-review.tool.js +152 -0
  156. package/dist/tools/neural/neural-review.tool.js.map +1 -0
  157. package/dist/tools/neural/neural-security.tool.d.ts +16 -0
  158. package/dist/tools/neural/neural-security.tool.d.ts.map +1 -0
  159. package/dist/tools/neural/neural-security.tool.js +256 -0
  160. package/dist/tools/neural/neural-security.tool.js.map +1 -0
  161. package/dist/tools/neural/neural-similar.tool.d.ts +9 -0
  162. package/dist/tools/neural/neural-similar.tool.d.ts.map +1 -0
  163. package/dist/tools/neural/neural-similar.tool.js +139 -0
  164. package/dist/tools/neural/neural-similar.tool.js.map +1 -0
  165. package/dist/tools/neural/neural-smells.tool.d.ts +9 -0
  166. package/dist/tools/neural/neural-smells.tool.d.ts.map +1 -0
  167. package/dist/tools/neural/neural-smells.tool.js +123 -0
  168. package/dist/tools/neural/neural-smells.tool.js.map +1 -0
  169. package/dist/tools/neural/neural-suggest.tool.d.ts +9 -0
  170. package/dist/tools/neural/neural-suggest.tool.d.ts.map +1 -0
  171. package/dist/tools/neural/neural-suggest.tool.js +64 -0
  172. package/dist/tools/neural/neural-suggest.tool.js.map +1 -0
  173. package/dist/tools/quality/index.d.ts +8 -0
  174. package/dist/tools/quality/index.d.ts.map +1 -0
  175. package/dist/tools/quality/index.js +24 -0
  176. package/dist/tools/quality/index.js.map +1 -0
  177. package/dist/tools/quality/quality-complexity.tool.d.ts +11 -0
  178. package/dist/tools/quality/quality-complexity.tool.d.ts.map +1 -0
  179. package/dist/tools/quality/quality-complexity.tool.js +152 -0
  180. package/dist/tools/quality/quality-complexity.tool.js.map +1 -0
  181. package/dist/tools/quality/quality-coupling.tool.d.ts +9 -0
  182. package/dist/tools/quality/quality-coupling.tool.d.ts.map +1 -0
  183. package/dist/tools/quality/quality-coupling.tool.js +51 -0
  184. package/dist/tools/quality/quality-coupling.tool.js.map +1 -0
  185. package/dist/tools/quality/quality-coverage.tool.d.ts +9 -0
  186. package/dist/tools/quality/quality-coverage.tool.d.ts.map +1 -0
  187. package/dist/tools/quality/quality-coverage.tool.js +104 -0
  188. package/dist/tools/quality/quality-coverage.tool.js.map +1 -0
  189. package/dist/tools/quality/quality-deps.tool.d.ts +11 -0
  190. package/dist/tools/quality/quality-deps.tool.d.ts.map +1 -0
  191. package/dist/tools/quality/quality-deps.tool.js +119 -0
  192. package/dist/tools/quality/quality-deps.tool.js.map +1 -0
  193. package/dist/tools/quality/quality-duplication.tool.d.ts +9 -0
  194. package/dist/tools/quality/quality-duplication.tool.d.ts.map +1 -0
  195. package/dist/tools/quality/quality-duplication.tool.js +100 -0
  196. package/dist/tools/quality/quality-duplication.tool.js.map +1 -0
  197. package/dist/tools/quality/quality-size.tool.d.ts +9 -0
  198. package/dist/tools/quality/quality-size.tool.d.ts.map +1 -0
  199. package/dist/tools/quality/quality-size.tool.js +94 -0
  200. package/dist/tools/quality/quality-size.tool.js.map +1 -0
  201. package/dist/tools/quality/quality-types.tool.d.ts +9 -0
  202. package/dist/tools/quality/quality-types.tool.d.ts.map +1 -0
  203. package/dist/tools/quality/quality-types.tool.js +95 -0
  204. package/dist/tools/quality/quality-types.tool.js.map +1 -0
  205. package/dist/tools/workspace/analyze-project.tool.d.ts +36 -0
  206. package/dist/tools/workspace/analyze-project.tool.d.ts.map +1 -0
  207. package/dist/tools/workspace/analyze-project.tool.js +454 -0
  208. package/dist/tools/workspace/analyze-project.tool.js.map +1 -0
  209. package/dist/tools/workspace/codebase-research.tool.d.ts +10 -0
  210. package/dist/tools/workspace/codebase-research.tool.d.ts.map +1 -0
  211. package/dist/tools/workspace/codebase-research.tool.js +89 -0
  212. package/dist/tools/workspace/codebase-research.tool.js.map +1 -0
  213. package/dist/tools/workspace/project-scanner.tool.d.ts +55 -0
  214. package/dist/tools/workspace/project-scanner.tool.d.ts.map +1 -0
  215. package/dist/tools/workspace/project-scanner.tool.js +978 -0
  216. package/dist/tools/workspace/project-scanner.tool.js.map +1 -0
  217. package/dist/tools/workspace/search-code.tool.d.ts +14 -0
  218. package/dist/tools/workspace/search-code.tool.d.ts.map +1 -0
  219. package/dist/tools/workspace/search-code.tool.js +286 -0
  220. package/dist/tools/workspace/search-code.tool.js.map +1 -0
  221. package/package.json +17 -0
  222. package/src/contexts/context-decomposer.ts +113 -0
  223. package/src/contexts/context-document-manager.ts +70 -0
  224. package/src/contexts/context-router.ts +121 -0
  225. package/src/index.ts +65 -0
  226. package/src/libs/codebase/deep-analyzer.ts +287 -0
  227. package/src/libs/codebase/graph/dependency-extractor.ts +165 -0
  228. package/src/libs/codebase/graph/graph-analysis.ts +231 -0
  229. package/src/libs/codebase/graph/graph-visualizer.ts +124 -0
  230. package/src/libs/codebase/indexer/batch-processor.ts +47 -0
  231. package/src/libs/codebase/indexer/chunker-markdown.ts +175 -0
  232. package/src/libs/codebase/indexer/chunker.ts +126 -0
  233. package/src/libs/codebase/indexer/explorer.ts +269 -0
  234. package/src/libs/codebase/indexer/indexer.ts +174 -0
  235. package/src/libs/codebase/knowledge/cross-referencer.ts +182 -0
  236. package/src/libs/codebase/knowledge/markdown-extractor.ts +270 -0
  237. package/src/libs/codebase/knowledge/summarizer.ts +85 -0
  238. package/src/libs/codebase/search/bm25.ts +109 -0
  239. package/src/libs/codebase/search/hybrid-search.ts +109 -0
  240. package/src/libs/codebase-index.class.ts +715 -0
  241. package/src/tools/codebase/codebase-analyze.tool.ts +18 -0
  242. package/src/tools/codebase/codebase-crossref.tool.ts +18 -0
  243. package/src/tools/codebase/codebase-flow.tool.ts +18 -0
  244. package/src/tools/codebase/codebase-graph-circular.tool.ts +15 -0
  245. package/src/tools/codebase/codebase-graph-query.tool.ts +19 -0
  246. package/src/tools/codebase/codebase-graph-stats.tool.ts +15 -0
  247. package/src/tools/codebase/codebase-graph-visualize.tool.ts +19 -0
  248. package/src/tools/codebase/codebase-impact.tool.ts +19 -0
  249. package/src/tools/codebase/codebase-index.tool.ts +19 -0
  250. package/src/tools/codebase/codebase-knowledge.tool.ts +20 -0
  251. package/src/tools/codebase/codebase-search.tool.ts +22 -0
  252. package/src/tools/codebase/codebase-status.tool.ts +17 -0
  253. package/src/tools/neural/comparative-analysis.tool.ts +309 -0
  254. package/src/tools/neural/index.ts +11 -0
  255. package/src/tools/neural/neural-deadcode.tool.ts +105 -0
  256. package/src/tools/neural/neural-debug.tool.ts +199 -0
  257. package/src/tools/neural/neural-explain.tool.ts +67 -0
  258. package/src/tools/neural/neural-optimize.tool.ts +93 -0
  259. package/src/tools/neural/neural-pattern.tool.ts +141 -0
  260. package/src/tools/neural/neural-review.tool.ts +145 -0
  261. package/src/tools/neural/neural-security.tool.ts +226 -0
  262. package/src/tools/neural/neural-similar.tool.ts +113 -0
  263. package/src/tools/neural/neural-smells.tool.ts +98 -0
  264. package/src/tools/neural/neural-suggest.tool.ts +65 -0
  265. package/src/tools/quality/index.ts +7 -0
  266. package/src/tools/quality/quality-complexity.tool.ts +132 -0
  267. package/src/tools/quality/quality-coupling.tool.ts +57 -0
  268. package/src/tools/quality/quality-coverage.tool.ts +78 -0
  269. package/src/tools/quality/quality-deps.tool.ts +89 -0
  270. package/src/tools/quality/quality-duplication.tool.ts +71 -0
  271. package/src/tools/quality/quality-size.tool.ts +66 -0
  272. package/src/tools/quality/quality-types.tool.ts +66 -0
  273. package/src/tools/workspace/analyze-project.tool.ts +429 -0
  274. package/src/tools/workspace/codebase-research.tool.ts +100 -0
  275. package/src/tools/workspace/project-scanner.tool.ts +1058 -0
  276. package/src/tools/workspace/search-code.tool.ts +344 -0
  277. package/tsconfig.build.json +12 -0
  278. package/tsconfig.json +9 -0
@@ -0,0 +1,715 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+
4
+ export type CodebaseSearchMode = "hybrid" | "content" | "path" | "symbol";
5
+ export type CodebaseSymbolKind =
6
+ | "class"
7
+ | "function"
8
+ | "method"
9
+ | "interface"
10
+ | "type"
11
+ | "enum"
12
+ | "const"
13
+ | "variable";
14
+
15
+ export interface CodebaseSearchFilters {
16
+ pathPrefix?: string;
17
+ extensions?: string[];
18
+ includeGlobs?: string[];
19
+ excludeGlobs?: string[];
20
+ includeHidden?: boolean;
21
+ }
22
+
23
+ export interface CodebaseSearchOptions extends CodebaseSearchFilters {
24
+ query?: string;
25
+ mode: CodebaseSearchMode;
26
+ limit: number;
27
+ offset: number;
28
+ regex?: boolean;
29
+ caseSensitive?: boolean;
30
+ symbolKinds?: CodebaseSymbolKind[];
31
+ maxSnippetChars?: number;
32
+ }
33
+
34
+ export interface CodebaseSearchHit {
35
+ path: string;
36
+ line?: number;
37
+ column?: number;
38
+ snippet?: string;
39
+ symbol?: string;
40
+ kind?: CodebaseSymbolKind;
41
+ source: "path" | "content" | "symbol";
42
+ score: number;
43
+ }
44
+
45
+ export interface CodebaseIndexStats {
46
+ rootDir: string;
47
+ fileCount: number;
48
+ symbolCount: number;
49
+ hasFts: boolean;
50
+ indexedAt: string | null;
51
+ }
52
+
53
+ interface IndexedFileRow {
54
+ id: number;
55
+ path: string;
56
+ ext: string;
57
+ content: string;
58
+ }
59
+
60
+ interface SymbolRow {
61
+ path: string;
62
+ name: string;
63
+ kind: CodebaseSymbolKind;
64
+ line: number;
65
+ column: number;
66
+ signature: string | null;
67
+ }
68
+
69
+ type SourceLanguage =
70
+ | "ts"
71
+ | "js"
72
+ | "python"
73
+ | "php"
74
+ | "go"
75
+ | "java"
76
+ | "csharp"
77
+ | "rust"
78
+ | "ruby"
79
+ | "other";
80
+
81
+ interface SymbolPattern {
82
+ kind: CodebaseSymbolKind;
83
+ regex: RegExp;
84
+ languages: SourceLanguage[];
85
+ }
86
+
87
+ const DEFAULT_SKIP_DIRS = new Set([
88
+ "node_modules",
89
+ ".git",
90
+ "dist",
91
+ "build",
92
+ "coverage",
93
+ "out",
94
+ ".next",
95
+ ".turbo",
96
+ ".cache",
97
+ "vendor",
98
+ ]);
99
+
100
+ const DEFAULT_BINARY_EXTS = new Set([
101
+ ".png", ".jpg", ".jpeg", ".gif", ".webp", ".ico", ".pdf", ".zip", ".gz", ".tar", ".tgz",
102
+ ".bz2", ".7z", ".rar", ".mp4", ".mov", ".avi", ".mp3", ".wav", ".woff", ".woff2", ".ttf",
103
+ ".eot", ".class", ".jar", ".exe", ".dll", ".so", ".dylib", ".bin", ".wasm", ".map",
104
+ ]);
105
+
106
+ const SYMBOL_PATTERNS: SymbolPattern[] = [
107
+ // TS/JS
108
+ { kind: "class", regex: /\bclass\s+([A-Za-z_$][\w$]*)/g, languages: ["ts", "js"] },
109
+ { kind: "interface", regex: /\binterface\s+([A-Za-z_$][\w$]*)/g, languages: ["ts"] },
110
+ { kind: "type", regex: /\btype\s+([A-Za-z_$][\w$]*)\s*=/g, languages: ["ts"] },
111
+ { kind: "enum", regex: /\benum\s+([A-Za-z_$][\w$]*)/g, languages: ["ts", "js"] },
112
+ { kind: "function", regex: /\bfunction\s+([A-Za-z_$][\w$]*)\s*\(/g, languages: ["ts", "js"] },
113
+ { kind: "const", regex: /\bconst\s+([A-Za-z_$][\w$]*)\s*=/g, languages: ["ts", "js"] },
114
+ { kind: "variable", regex: /\b(?:let|var)\s+([A-Za-z_$][\w$]*)\s*=/g, languages: ["ts", "js"] },
115
+ { kind: "method", regex: /^\s*(?:public|private|protected|async|static|readonly\s+)*([A-Za-z_$][\w$]*)\s*\([^\n)]*\)\s*\{/g, languages: ["ts", "js"] },
116
+
117
+ // Python
118
+ { kind: "class", regex: /^\s*class\s+([A-Za-z_][\w]*)\s*(?:\(|:)/g, languages: ["python"] },
119
+ { kind: "function", regex: /^\s*def\s+([A-Za-z_][\w]*)\s*\(/g, languages: ["python"] },
120
+
121
+ // PHP
122
+ { kind: "class", regex: /\bclass\s+([A-Za-z_][\w]*)/g, languages: ["php"] },
123
+ { kind: "interface", regex: /\binterface\s+([A-Za-z_][\w]*)/g, languages: ["php"] },
124
+ { kind: "enum", regex: /\benum\s+([A-Za-z_][\w]*)/g, languages: ["php"] },
125
+ { kind: "function", regex: /\bfunction\s+([A-Za-z_][\w]*)\s*\(/g, languages: ["php"] },
126
+
127
+ // Go
128
+ { kind: "type", regex: /^\s*type\s+([A-Za-z_][\w]*)\s+(?:struct|interface|\w+)/g, languages: ["go"] },
129
+ { kind: "function", regex: /^\s*func\s+(?:\([^)]+\)\s*)?([A-Za-z_][\w]*)\s*\(/g, languages: ["go"] },
130
+
131
+ // Java
132
+ { kind: "class", regex: /\bclass\s+([A-Za-z_][\w]*)/g, languages: ["java"] },
133
+ { kind: "interface", regex: /\binterface\s+([A-Za-z_][\w]*)/g, languages: ["java"] },
134
+ { kind: "enum", regex: /\benum\s+([A-Za-z_][\w]*)/g, languages: ["java"] },
135
+ { kind: "method", regex: /^\s*(?:public|private|protected|static|final|synchronized|native|abstract)\s+[A-Za-z_<>,\[\]\s]+\s+([A-Za-z_][\w]*)\s*\(/g, languages: ["java"] },
136
+
137
+ // C#
138
+ { kind: "class", regex: /\bclass\s+([A-Za-z_][\w]*)/g, languages: ["csharp"] },
139
+ { kind: "interface", regex: /\binterface\s+([A-Za-z_][\w]*)/g, languages: ["csharp"] },
140
+ { kind: "enum", regex: /\benum\s+([A-Za-z_][\w]*)/g, languages: ["csharp"] },
141
+ { kind: "method", regex: /^\s*(?:public|private|protected|internal|static|virtual|override|async)\s+[A-Za-z_<>,\[\]\?\s]+\s+([A-Za-z_][\w]*)\s*\(/g, languages: ["csharp"] },
142
+
143
+ // Rust
144
+ { kind: "type", regex: /^\s*(?:pub\s+)?(?:struct|trait|type)\s+([A-Za-z_][\w]*)/g, languages: ["rust"] },
145
+ { kind: "enum", regex: /^\s*(?:pub\s+)?enum\s+([A-Za-z_][\w]*)/g, languages: ["rust"] },
146
+ { kind: "function", regex: /^\s*(?:pub\s+)?fn\s+([A-Za-z_][\w]*)\s*\(/g, languages: ["rust"] },
147
+
148
+ // Ruby
149
+ { kind: "class", regex: /^\s*class\s+([A-Za-z_][\w:]*)/g, languages: ["ruby"] },
150
+ { kind: "function", regex: /^\s*def\s+([A-Za-z_][\w!?=]*)/g, languages: ["ruby"] },
151
+ ];
152
+
153
+ export class CodebaseIndex {
154
+ private static readonly shared = new Map<string, CodebaseIndex>();
155
+
156
+ static async forRoot(rootDir: string): Promise<CodebaseIndex> {
157
+ const { default: Database } = await import("better-sqlite3");
158
+ const key = path.resolve(rootDir);
159
+ const existing = this.shared.get(key);
160
+ if (existing) return existing;
161
+ const created = new CodebaseIndex(key, Database);
162
+ this.shared.set(key, created);
163
+ return created;
164
+ }
165
+
166
+ private readonly db: any;
167
+ private hasFts = false;
168
+ private indexedAtMs: number | null = null;
169
+ private indexed = false;
170
+
171
+ private constructor(private readonly rootDir: string, Database: any) {
172
+ this.db = new Database(":memory:");
173
+ this.db.pragma("journal_mode = OFF");
174
+ this.db.pragma("synchronous = OFF");
175
+ this.db.pragma("temp_store = MEMORY");
176
+ this.createSchema();
177
+ }
178
+
179
+ isIndexed(): boolean {
180
+ return this.indexed;
181
+ }
182
+
183
+ rebuild(maxFileBytes = 1024 * 1024): CodebaseIndexStats {
184
+ this.clearIndex();
185
+
186
+ const files = this.collectFiles(this.rootDir);
187
+
188
+ const insertFile = this.db.prepare(
189
+ "INSERT INTO files (rel_path, ext, mtime_ms, size_bytes, content) VALUES (?, ?, ?, ?, ?)"
190
+ );
191
+ const insertFts = this.hasFts
192
+ ? this.db.prepare("INSERT INTO files_fts (rowid, rel_path, content) VALUES (?, ?, ?)")
193
+ : null;
194
+ const insertSymbol = this.db.prepare(
195
+ "INSERT INTO symbols (file_id, name, kind, line, column, signature) VALUES (?, ?, ?, ?, ?, ?)"
196
+ );
197
+
198
+ const tx = this.db.transaction((absolutePaths: string[]) => {
199
+ for (const absPath of absolutePaths) {
200
+ let stat: fs.Stats;
201
+ try {
202
+ stat = fs.statSync(absPath);
203
+ } catch {
204
+ continue;
205
+ }
206
+ if (!stat.isFile()) continue;
207
+ if (stat.size > maxFileBytes) continue;
208
+ if (!this.isLikelyText(absPath)) continue;
209
+
210
+ let content: string;
211
+ try {
212
+ content = fs.readFileSync(absPath, "utf8");
213
+ } catch {
214
+ continue;
215
+ }
216
+
217
+ const relPath = this.normalizeRel(absPath);
218
+ const ext = path.extname(absPath).toLowerCase();
219
+ const inserted = insertFile.run(relPath, ext, Math.floor(stat.mtimeMs), stat.size, content);
220
+ const fileId = Number(inserted.lastInsertRowid);
221
+
222
+ if (insertFts) {
223
+ insertFts.run(fileId, relPath, content);
224
+ }
225
+
226
+ for (const symbol of this.extractSymbols(content, ext)) {
227
+ insertSymbol.run(
228
+ fileId,
229
+ symbol.name,
230
+ symbol.kind,
231
+ symbol.line,
232
+ symbol.column,
233
+ symbol.signature
234
+ );
235
+ }
236
+ }
237
+ });
238
+
239
+ tx(files);
240
+ this.indexedAtMs = Date.now();
241
+ this.indexed = true;
242
+ return this.getStats();
243
+ }
244
+
245
+ getStats(): CodebaseIndexStats {
246
+ const fileRow = this.db.prepare("SELECT COUNT(*) AS c FROM files").get() as { c?: number } | undefined;
247
+ const symbolRow = this.db.prepare("SELECT COUNT(*) AS c FROM symbols").get() as { c?: number } | undefined;
248
+ const fileCount = Number(fileRow?.c ?? 0);
249
+ const symbolCount = Number(symbolRow?.c ?? 0);
250
+ return {
251
+ rootDir: this.rootDir,
252
+ fileCount,
253
+ symbolCount,
254
+ hasFts: this.hasFts,
255
+ indexedAt: this.indexedAtMs ? new Date(this.indexedAtMs).toISOString() : null,
256
+ };
257
+ }
258
+
259
+ search(options: CodebaseSearchOptions): CodebaseSearchHit[] {
260
+ const mode = options.mode;
261
+ const allHits: CodebaseSearchHit[] = [];
262
+
263
+ if (mode === "path" || mode === "hybrid") {
264
+ allHits.push(...this.searchPaths(options));
265
+ }
266
+
267
+ if (mode === "content" || mode === "hybrid") {
268
+ allHits.push(...this.searchContent(options));
269
+ }
270
+
271
+ if (mode === "symbol" || mode === "hybrid") {
272
+ allHits.push(...this.searchSymbols(options));
273
+ }
274
+
275
+ const dedup = new Map<string, CodebaseSearchHit>();
276
+ for (const hit of allHits) {
277
+ const key = `${hit.source}|${hit.path}|${hit.line ?? 0}|${hit.column ?? 0}|${hit.symbol ?? ""}`;
278
+ const existing = dedup.get(key);
279
+ if (!existing || hit.score > existing.score) dedup.set(key, hit);
280
+ }
281
+
282
+ const sorted = Array.from(dedup.values()).sort((a, b) => {
283
+ if (b.score !== a.score) return b.score - a.score;
284
+ if (a.path !== b.path) return a.path.localeCompare(b.path);
285
+ return (a.line ?? 0) - (b.line ?? 0);
286
+ });
287
+
288
+ const offset = Math.max(0, options.offset);
289
+ const limit = Math.max(1, options.limit);
290
+ return sorted.slice(offset, offset + limit);
291
+ }
292
+
293
+ private searchPaths(options: CodebaseSearchOptions): CodebaseSearchHit[] {
294
+ const docs = this.queryDocuments(options);
295
+ const query = (options.query ?? "").trim();
296
+ if (!query && !options.pathPrefix && !options.includeGlobs?.length) return [];
297
+
298
+ const matcher = this.makeMatcher(query, options.regex === true, options.caseSensitive === true);
299
+ const hits: CodebaseSearchHit[] = [];
300
+
301
+ for (const doc of docs) {
302
+ if (!query || matcher(doc.path)) {
303
+ hits.push({
304
+ path: doc.path,
305
+ source: "path",
306
+ score: this.pathScore(doc.path, query),
307
+ });
308
+ }
309
+ }
310
+
311
+ return hits;
312
+ }
313
+
314
+ private searchContent(options: CodebaseSearchOptions): CodebaseSearchHit[] {
315
+ const query = (options.query ?? "").trim();
316
+ if (!query) return [];
317
+
318
+ const docs = this.queryDocuments(options, { forContent: true, query, regex: options.regex === true });
319
+ const matchLine = this.makeLineFinder(
320
+ query,
321
+ options.regex === true,
322
+ options.caseSensitive === true,
323
+ options.maxSnippetChars ?? 220
324
+ );
325
+
326
+ const hits: CodebaseSearchHit[] = [];
327
+ for (const doc of docs) {
328
+ const lineHit = matchLine(doc.content);
329
+ if (!lineHit) continue;
330
+ hits.push({
331
+ path: doc.path,
332
+ line: lineHit.line,
333
+ column: lineHit.column,
334
+ snippet: lineHit.snippet,
335
+ source: "content",
336
+ score: lineHit.exact ? 95 : 78,
337
+ });
338
+ }
339
+
340
+ return hits;
341
+ }
342
+
343
+ private searchSymbols(options: CodebaseSearchOptions): CodebaseSearchHit[] {
344
+ const query = (options.query ?? "").trim();
345
+ if (!query) return [];
346
+
347
+ const conds: string[] = [];
348
+ const params: unknown[] = [];
349
+
350
+ if (options.pathPrefix) {
351
+ conds.push("f.rel_path LIKE ? ESCAPE '\\\\'");
352
+ params.push(`${escapeLike(this.normalizeRel(options.pathPrefix))}%`);
353
+ }
354
+
355
+ if (options.extensions && options.extensions.length > 0) {
356
+ conds.push(`f.ext IN (${options.extensions.map(() => "?").join(",")})`);
357
+ params.push(...options.extensions.map((ext) => normalizeExt(ext)));
358
+ }
359
+
360
+ if (options.symbolKinds && options.symbolKinds.length > 0) {
361
+ conds.push(`s.kind IN (${options.symbolKinds.map(() => "?").join(",")})`);
362
+ params.push(...options.symbolKinds);
363
+ }
364
+
365
+ let sql =
366
+ "SELECT f.rel_path AS path, s.name AS name, s.kind AS kind, s.line AS line, s.column AS column, s.signature AS signature " +
367
+ "FROM symbols s JOIN files f ON f.id = s.file_id";
368
+ if (conds.length > 0) sql += ` WHERE ${conds.join(" AND ")}`;
369
+
370
+ const rows = this.db.prepare(sql).all(...params) as SymbolRow[];
371
+ const matchName = this.makeMatcher(query, options.regex === true, options.caseSensitive === true);
372
+
373
+ const hits: CodebaseSearchHit[] = [];
374
+ for (const row of rows) {
375
+ if (!this.applyGlobFilters(row.path, options)) continue;
376
+ if (!matchName(row.name)) continue;
377
+ hits.push({
378
+ path: row.path,
379
+ line: row.line,
380
+ column: row.column,
381
+ snippet: row.signature ?? undefined,
382
+ symbol: row.name,
383
+ kind: row.kind,
384
+ source: "symbol",
385
+ score: row.name === query ? 100 : 86,
386
+ });
387
+ }
388
+
389
+ return hits;
390
+ }
391
+
392
+ private queryDocuments(
393
+ filters: CodebaseSearchFilters,
394
+ contentQuery?: { forContent: boolean; query: string; regex: boolean }
395
+ ): IndexedFileRow[] {
396
+ if (contentQuery && this.hasFts && !contentQuery.regex) {
397
+ const ftsQuery = toFtsQuery(contentQuery.query);
398
+ if (ftsQuery) {
399
+ try {
400
+ const rows = this.db
401
+ .prepare(
402
+ "SELECT f.id AS id, f.rel_path AS path, f.ext AS ext, f.content AS content " +
403
+ "FROM files_fts ft JOIN files f ON f.id = ft.rowid " +
404
+ "WHERE files_fts MATCH ? LIMIT 500"
405
+ )
406
+ .all(ftsQuery) as IndexedFileRow[];
407
+ return rows.filter((row) => this.applyDocumentFilters(row.path, row.ext, filters));
408
+ } catch {
409
+ // Fallback below.
410
+ }
411
+ }
412
+ }
413
+
414
+ const conds: string[] = [];
415
+ const params: unknown[] = [];
416
+
417
+ if (filters.pathPrefix) {
418
+ conds.push("rel_path LIKE ? ESCAPE '\\\\'");
419
+ params.push(`${escapeLike(this.normalizeRel(filters.pathPrefix))}%`);
420
+ }
421
+
422
+ if (filters.extensions && filters.extensions.length > 0) {
423
+ conds.push(`ext IN (${filters.extensions.map(() => "?").join(",")})`);
424
+ params.push(...filters.extensions.map((ext) => normalizeExt(ext)));
425
+ }
426
+
427
+ let sql = "SELECT id, rel_path AS path, ext, content FROM files";
428
+ if (conds.length > 0) sql += ` WHERE ${conds.join(" AND ")}`;
429
+
430
+ const rows = this.db.prepare(sql).all(...params) as IndexedFileRow[];
431
+ return rows.filter((row) => this.applyDocumentFilters(row.path, row.ext, filters));
432
+ }
433
+
434
+ private applyDocumentFilters(filePath: string, ext: string, filters: CodebaseSearchFilters): boolean {
435
+ if (filters.pathPrefix) {
436
+ const prefix = this.normalizeRel(filters.pathPrefix);
437
+ if (prefix && !filePath.startsWith(prefix)) return false;
438
+ }
439
+
440
+ if (filters.extensions && filters.extensions.length > 0) {
441
+ const normalized = new Set(filters.extensions.map((value) => normalizeExt(value)));
442
+ if (!normalized.has(ext)) return false;
443
+ }
444
+
445
+ return this.applyGlobFilters(filePath, filters);
446
+ }
447
+
448
+ private makeMatcher(query: string, regex: boolean, caseSensitive: boolean): (value: string) => boolean {
449
+ if (!query) return () => true;
450
+
451
+ if (regex) {
452
+ const flags = caseSensitive ? "" : "i";
453
+ const re = new RegExp(query, flags);
454
+ return (value: string) => re.test(value);
455
+ }
456
+
457
+ const needle = caseSensitive ? query : query.toLowerCase();
458
+ return (value: string) => (caseSensitive ? value : value.toLowerCase()).includes(needle);
459
+ }
460
+
461
+ private makeLineFinder(query: string, regex: boolean, caseSensitive: boolean, maxChars: number): (content: string) => {
462
+ line: number;
463
+ column: number;
464
+ snippet: string;
465
+ exact: boolean;
466
+ } | null {
467
+ if (regex) {
468
+ const flags = caseSensitive ? "" : "i";
469
+ const re = new RegExp(query, flags);
470
+ return (content: string) => {
471
+ const lines = content.split("\n");
472
+ for (let i = 0; i < lines.length; i++) {
473
+ const line = lines[i] ?? "";
474
+ re.lastIndex = 0;
475
+ const match = re.exec(line);
476
+ if (!match) continue;
477
+ const snippet = trimSnippet(line, maxChars);
478
+ return { line: i + 1, column: (match.index ?? 0) + 1, snippet, exact: false };
479
+ }
480
+ return null;
481
+ };
482
+ }
483
+
484
+ const needle = caseSensitive ? query : query.toLowerCase();
485
+ return (content: string) => {
486
+ const lines = content.split("\n");
487
+ for (let i = 0; i < lines.length; i++) {
488
+ const line = lines[i] ?? "";
489
+ const hay = caseSensitive ? line : line.toLowerCase();
490
+ const idx = hay.indexOf(needle);
491
+ if (idx === -1) continue;
492
+ const snippet = trimSnippet(line, maxChars);
493
+ return { line: i + 1, column: idx + 1, snippet, exact: line.includes(query) };
494
+ }
495
+ return null;
496
+ };
497
+ }
498
+
499
+ private pathScore(filePath: string, query: string): number {
500
+ if (!query) return 55;
501
+ const lowerPath = filePath.toLowerCase();
502
+ const lowerQuery = query.toLowerCase();
503
+ if (lowerPath === lowerQuery) return 98;
504
+ if (lowerPath.endsWith(`/${lowerQuery}`)) return 90;
505
+ if (lowerPath.includes(lowerQuery)) return 80;
506
+ return 60;
507
+ }
508
+
509
+ private applyGlobFilters(filePath: string, filters: CodebaseSearchFilters): boolean {
510
+ if (filters.includeHidden !== true) {
511
+ const segments = filePath.split("/");
512
+ if (segments.some((segment) => segment.startsWith("."))) return false;
513
+ }
514
+
515
+ const includeGlobs = filters.includeGlobs ?? [];
516
+ if (includeGlobs.length > 0 && !includeGlobs.some((glob) => globMatch(glob, filePath))) {
517
+ return false;
518
+ }
519
+
520
+ const excludeGlobs = filters.excludeGlobs ?? [];
521
+ if (excludeGlobs.some((glob) => globMatch(glob, filePath))) {
522
+ return false;
523
+ }
524
+
525
+ return true;
526
+ }
527
+
528
+ private createSchema(): void {
529
+ this.db.exec(
530
+ "CREATE TABLE IF NOT EXISTS files (" +
531
+ "id INTEGER PRIMARY KEY, " +
532
+ "rel_path TEXT NOT NULL UNIQUE, " +
533
+ "ext TEXT NOT NULL, " +
534
+ "mtime_ms INTEGER NOT NULL, " +
535
+ "size_bytes INTEGER NOT NULL, " +
536
+ "content TEXT NOT NULL" +
537
+ ");"
538
+ );
539
+
540
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_files_ext ON files(ext);");
541
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_files_path ON files(rel_path);");
542
+
543
+ this.db.exec(
544
+ "CREATE TABLE IF NOT EXISTS symbols (" +
545
+ "id INTEGER PRIMARY KEY, " +
546
+ "file_id INTEGER NOT NULL, " +
547
+ "name TEXT NOT NULL, " +
548
+ "kind TEXT NOT NULL, " +
549
+ "line INTEGER NOT NULL, " +
550
+ "column INTEGER NOT NULL, " +
551
+ "signature TEXT, " +
552
+ "FOREIGN KEY(file_id) REFERENCES files(id) ON DELETE CASCADE" +
553
+ ");"
554
+ );
555
+
556
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols(name);");
557
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_symbols_kind ON symbols(kind);");
558
+
559
+ try {
560
+ this.db.exec("CREATE VIRTUAL TABLE IF NOT EXISTS files_fts USING fts5(rel_path, content, tokenize='unicode61');");
561
+ this.hasFts = true;
562
+ } catch {
563
+ this.hasFts = false;
564
+ }
565
+ }
566
+
567
+ private clearIndex(): void {
568
+ this.db.exec("DELETE FROM symbols;");
569
+ this.db.exec("DELETE FROM files;");
570
+ if (this.hasFts) this.db.exec("DELETE FROM files_fts;");
571
+ }
572
+
573
+ private collectFiles(root: string): string[] {
574
+ const out: string[] = [];
575
+
576
+ const walk = (dir: string) => {
577
+ let entries: fs.Dirent[];
578
+ try {
579
+ entries = fs.readdirSync(dir, { withFileTypes: true });
580
+ } catch {
581
+ return;
582
+ }
583
+
584
+ for (const entry of entries) {
585
+ const abs = path.join(dir, entry.name);
586
+
587
+ if (entry.isDirectory()) {
588
+ if (DEFAULT_SKIP_DIRS.has(entry.name)) continue;
589
+ walk(abs);
590
+ continue;
591
+ }
592
+
593
+ if (!entry.isFile()) continue;
594
+ out.push(abs);
595
+ }
596
+ };
597
+
598
+ walk(root);
599
+ return out;
600
+ }
601
+
602
+ private isLikelyText(absPath: string): boolean {
603
+ const ext = path.extname(absPath).toLowerCase();
604
+ if (DEFAULT_BINARY_EXTS.has(ext)) return false;
605
+
606
+ let fd: number | null = null;
607
+ try {
608
+ fd = fs.openSync(absPath, "r");
609
+ const sample = Buffer.allocUnsafe(1024);
610
+ const read = fs.readSync(fd, sample, 0, sample.length, 0);
611
+ for (let i = 0; i < read; i++) {
612
+ if (sample[i] === 0) return false;
613
+ }
614
+ return true;
615
+ } catch {
616
+ return false;
617
+ } finally {
618
+ if (fd !== null) {
619
+ try { fs.closeSync(fd); } catch { /* noop */ }
620
+ }
621
+ }
622
+ }
623
+
624
+ private normalizeRel(p: string): string {
625
+ const rel = path.isAbsolute(p) ? path.relative(this.rootDir, p) : p;
626
+ return rel.replace(/\\/g, "/").replace(/^\.\//, "");
627
+ }
628
+
629
+ private extractSymbols(content: string, ext: string): Array<{
630
+ name: string;
631
+ kind: CodebaseSymbolKind;
632
+ line: number;
633
+ column: number;
634
+ signature: string;
635
+ }> {
636
+ const language = languageFromExt(ext);
637
+ const rows = content.split("\n");
638
+ const out: Array<{ name: string; kind: CodebaseSymbolKind; line: number; column: number; signature: string }> = [];
639
+
640
+ for (let i = 0; i < rows.length; i++) {
641
+ const line = rows[i] ?? "";
642
+ for (const pattern of SYMBOL_PATTERNS) {
643
+ if (!pattern.languages.includes(language)) continue;
644
+ pattern.regex.lastIndex = 0;
645
+ const match = pattern.regex.exec(line);
646
+ if (!match || !match[1]) continue;
647
+
648
+ const name = String(match[1]);
649
+ const column = (match.index ?? 0) + 1;
650
+ out.push({
651
+ name,
652
+ kind: pattern.kind,
653
+ line: i + 1,
654
+ column,
655
+ signature: line.trim().slice(0, 240),
656
+ });
657
+ }
658
+ }
659
+
660
+ return out;
661
+ }
662
+ }
663
+
664
+ function languageFromExt(ext: string): SourceLanguage {
665
+ const normalized = ext.toLowerCase();
666
+ if (normalized === ".ts" || normalized === ".tsx" || normalized === ".mts" || normalized === ".cts") return "ts";
667
+ if (normalized === ".js" || normalized === ".jsx" || normalized === ".mjs" || normalized === ".cjs") return "js";
668
+ if (normalized === ".py") return "python";
669
+ if (normalized === ".php" || normalized === ".phtml") return "php";
670
+ if (normalized === ".go") return "go";
671
+ if (normalized === ".java") return "java";
672
+ if (normalized === ".cs") return "csharp";
673
+ if (normalized === ".rs") return "rust";
674
+ if (normalized === ".rb") return "ruby";
675
+ return "other";
676
+ }
677
+
678
+ function normalizeExt(raw: string): string {
679
+ if (!raw) return raw;
680
+ return raw.startsWith(".") ? raw.toLowerCase() : `.${raw.toLowerCase()}`;
681
+ }
682
+
683
+ function escapeLike(value: string): string {
684
+ return value.replace(/[\\%_]/g, "\\$&");
685
+ }
686
+
687
+ function trimSnippet(line: string, maxChars: number): string {
688
+ const clean = line.trim();
689
+ if (clean.length <= maxChars) return clean;
690
+ return `${clean.slice(0, Math.max(20, maxChars - 1))}...`;
691
+ }
692
+
693
+ function toFtsQuery(query: string): string | null {
694
+ const tokens = query.toLowerCase().match(/[a-z0-9_]+/g);
695
+ if (!tokens || tokens.length === 0) return null;
696
+ return tokens.map((token) => `${token}*`).join(" AND ");
697
+ }
698
+
699
+ function globMatch(glob: string, filePath: string): boolean {
700
+ const normGlob = glob.replace(/\\/g, "/");
701
+ const normPath = filePath.replace(/\\/g, "/");
702
+
703
+ const escaped = normGlob
704
+ .replace(/[.+^${}()|[\]\\]/g, "\\$&")
705
+ .replace(/\*\*/g, "@@DOUBLESTAR@@")
706
+ .replace(/\*/g, "[^/]*")
707
+ .replace(/@@DOUBLESTAR@@/g, ".*")
708
+ .replace(/\?/g, "[^/]");
709
+
710
+ try {
711
+ return new RegExp(`^${escaped}$`).test(normPath);
712
+ } catch {
713
+ return normPath.includes(normGlob);
714
+ }
715
+ }