@jafreck/lore 0.3.6 → 0.3.8

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 (206) hide show
  1. package/README.md +38 -150
  2. package/dist/cli.d.ts +2 -1
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +80 -32
  5. package/dist/cli.js.map +1 -1
  6. package/dist/db/read-only.d.ts +22 -43
  7. package/dist/db/read-only.d.ts.map +1 -1
  8. package/dist/db/read-only.js +80 -156
  9. package/dist/db/read-only.js.map +1 -1
  10. package/dist/db/schema.d.ts +9 -0
  11. package/dist/db/schema.d.ts.map +1 -1
  12. package/dist/db/schema.js +171 -85
  13. package/dist/db/schema.js.map +1 -1
  14. package/dist/discovery/poller.d.ts +6 -16
  15. package/dist/discovery/poller.d.ts.map +1 -1
  16. package/dist/discovery/poller.js +21 -52
  17. package/dist/discovery/poller.js.map +1 -1
  18. package/dist/discovery/scip-flush.d.ts +44 -0
  19. package/dist/discovery/scip-flush.d.ts.map +1 -0
  20. package/dist/discovery/scip-flush.js +67 -0
  21. package/dist/discovery/scip-flush.js.map +1 -0
  22. package/dist/discovery/watcher.d.ts +6 -16
  23. package/dist/discovery/watcher.d.ts.map +1 -1
  24. package/dist/discovery/watcher.js +22 -54
  25. package/dist/discovery/watcher.js.map +1 -1
  26. package/dist/docs/docs.d.ts +0 -2
  27. package/dist/docs/docs.d.ts.map +1 -1
  28. package/dist/docs/docs.js +0 -27
  29. package/dist/docs/docs.js.map +1 -1
  30. package/dist/embeddings/embedder.d.ts +12 -8
  31. package/dist/embeddings/embedder.d.ts.map +1 -1
  32. package/dist/embeddings/embedder.js +35 -19
  33. package/dist/embeddings/embedder.js.map +1 -1
  34. package/dist/index.d.ts +5 -2
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +3 -2
  37. package/dist/index.js.map +1 -1
  38. package/dist/indexer/index.d.ts +19 -10
  39. package/dist/indexer/index.d.ts.map +1 -1
  40. package/dist/indexer/index.js +135 -28
  41. package/dist/indexer/index.js.map +1 -1
  42. package/dist/indexer/pipeline.d.ts +44 -21
  43. package/dist/indexer/pipeline.d.ts.map +1 -1
  44. package/dist/indexer/pipeline.js +48 -26
  45. package/dist/indexer/pipeline.js.map +1 -1
  46. package/dist/indexer/stages/docs-index.d.ts +1 -3
  47. package/dist/indexer/stages/docs-index.d.ts.map +1 -1
  48. package/dist/indexer/stages/docs-index.js +1 -25
  49. package/dist/indexer/stages/docs-index.js.map +1 -1
  50. package/dist/indexer/stages/index.d.ts +4 -2
  51. package/dist/indexer/stages/index.d.ts.map +1 -1
  52. package/dist/indexer/stages/index.js +3 -2
  53. package/dist/indexer/stages/index.js.map +1 -1
  54. package/dist/indexer/stages/lsp-enrichment.d.ts +1 -1
  55. package/dist/indexer/stages/lsp-enrichment.d.ts.map +1 -1
  56. package/dist/indexer/stages/lsp-enrichment.js +44 -21
  57. package/dist/indexer/stages/lsp-enrichment.js.map +1 -1
  58. package/dist/indexer/stages/overlay-cleanup.d.ts +31 -0
  59. package/dist/indexer/stages/overlay-cleanup.d.ts.map +1 -0
  60. package/dist/indexer/stages/overlay-cleanup.js +64 -0
  61. package/dist/indexer/stages/overlay-cleanup.js.map +1 -0
  62. package/dist/indexer/stages/reverse-deps.d.ts +13 -0
  63. package/dist/indexer/stages/reverse-deps.d.ts.map +1 -0
  64. package/dist/indexer/stages/reverse-deps.js +81 -0
  65. package/dist/indexer/stages/reverse-deps.js.map +1 -0
  66. package/dist/indexer/stages/{scip-source.d.ts → scip-indexer.d.ts} +38 -9
  67. package/dist/indexer/stages/scip-indexer.d.ts.map +1 -0
  68. package/dist/indexer/stages/{scip-source.js → scip-indexer.js} +359 -98
  69. package/dist/indexer/stages/scip-indexer.js.map +1 -0
  70. package/dist/indexer/stages/source-index.d.ts +3 -3
  71. package/dist/indexer/stages/source-index.d.ts.map +1 -1
  72. package/dist/indexer/stages/source-index.js +188 -70
  73. package/dist/indexer/stages/source-index.js.map +1 -1
  74. package/dist/lsp/enrichment.d.ts.map +1 -1
  75. package/dist/lsp/enrichment.js +1 -18
  76. package/dist/lsp/enrichment.js.map +1 -1
  77. package/dist/parsing/extractors/go.d.ts.map +1 -1
  78. package/dist/parsing/extractors/go.js +0 -38
  79. package/dist/parsing/extractors/go.js.map +1 -1
  80. package/dist/parsing/extractors/javascript.d.ts.map +1 -1
  81. package/dist/parsing/extractors/javascript.js +0 -47
  82. package/dist/parsing/extractors/javascript.js.map +1 -1
  83. package/dist/parsing/extractors/python.d.ts.map +1 -1
  84. package/dist/parsing/extractors/python.js +0 -22
  85. package/dist/parsing/extractors/python.js.map +1 -1
  86. package/dist/parsing/extractors/types.d.ts +0 -16
  87. package/dist/parsing/extractors/types.d.ts.map +1 -1
  88. package/dist/parsing/extractors/types.js +1 -1
  89. package/dist/parsing/extractors/types.js.map +1 -1
  90. package/dist/parsing/extractors/typescript.d.ts.map +1 -1
  91. package/dist/parsing/extractors/typescript.js +18 -2
  92. package/dist/parsing/extractors/typescript.js.map +1 -1
  93. package/dist/resolution/call-graph.d.ts +3 -1
  94. package/dist/resolution/call-graph.d.ts.map +1 -1
  95. package/dist/resolution/call-graph.js +15 -13
  96. package/dist/resolution/call-graph.js.map +1 -1
  97. package/dist/resolution/resolution-method.d.ts +1 -1
  98. package/dist/resolution/resolution-method.d.ts.map +1 -1
  99. package/dist/resolution/resolution-method.js +2 -0
  100. package/dist/resolution/resolution-method.js.map +1 -1
  101. package/dist/resolution/resolver.d.ts +6 -0
  102. package/dist/resolution/resolver.d.ts.map +1 -1
  103. package/dist/resolution/resolver.js +21 -0
  104. package/dist/resolution/resolver.js.map +1 -1
  105. package/dist/runtime.d.ts +0 -2
  106. package/dist/runtime.d.ts.map +1 -1
  107. package/dist/runtime.js.map +1 -1
  108. package/dist/scip/compdb.d.ts +38 -0
  109. package/dist/scip/compdb.d.ts.map +1 -0
  110. package/dist/scip/compdb.js +138 -0
  111. package/dist/scip/compdb.js.map +1 -0
  112. package/dist/scip/enrichment.d.ts +6 -1
  113. package/dist/scip/enrichment.d.ts.map +1 -1
  114. package/dist/scip/enrichment.js +62 -5
  115. package/dist/scip/enrichment.js.map +1 -1
  116. package/dist/scip/installer.d.ts +59 -0
  117. package/dist/scip/installer.d.ts.map +1 -0
  118. package/dist/scip/installer.js +302 -0
  119. package/dist/scip/installer.js.map +1 -0
  120. package/dist/scip/registry.d.ts +1 -1
  121. package/dist/scip/registry.d.ts.map +1 -1
  122. package/dist/scip/registry.js +33 -5
  123. package/dist/scip/registry.js.map +1 -1
  124. package/dist/server/server.d.ts +3 -8
  125. package/dist/server/server.d.ts.map +1 -1
  126. package/dist/server/server.js +7 -66
  127. package/dist/server/server.js.map +1 -1
  128. package/dist/server/tool-registry.d.ts +2 -2
  129. package/dist/server/tool-registry.d.ts.map +1 -1
  130. package/dist/server/tool-registry.js +37 -41
  131. package/dist/server/tool-registry.js.map +1 -1
  132. package/dist/server/tools/cohesion.d.ts +46 -0
  133. package/dist/server/tools/cohesion.d.ts.map +1 -0
  134. package/dist/server/tools/cohesion.js +100 -0
  135. package/dist/server/tools/cohesion.js.map +1 -0
  136. package/dist/server/tools/dependents.d.ts +139 -0
  137. package/dist/server/tools/dependents.d.ts.map +1 -0
  138. package/dist/server/tools/dependents.js +354 -0
  139. package/dist/server/tools/dependents.js.map +1 -0
  140. package/dist/server/tools/diff.d.ts +73 -0
  141. package/dist/server/tools/diff.d.ts.map +1 -0
  142. package/dist/server/tools/diff.js +157 -0
  143. package/dist/server/tools/diff.js.map +1 -0
  144. package/dist/server/tools/graph.d.ts +7 -3
  145. package/dist/server/tools/graph.d.ts.map +1 -1
  146. package/dist/server/tools/graph.js +15 -85
  147. package/dist/server/tools/graph.js.map +1 -1
  148. package/dist/server/tools/metrics.d.ts +5 -30
  149. package/dist/server/tools/metrics.d.ts.map +1 -1
  150. package/dist/server/tools/metrics.js +21 -57
  151. package/dist/server/tools/metrics.js.map +1 -1
  152. package/dist/server/tools/structure.d.ts +77 -0
  153. package/dist/server/tools/structure.d.ts.map +1 -0
  154. package/dist/server/tools/structure.js +309 -0
  155. package/dist/server/tools/structure.js.map +1 -0
  156. package/dist/server/tools/test-map.d.ts +7 -0
  157. package/dist/server/tools/test-map.d.ts.map +1 -1
  158. package/dist/server/tools/test-map.js +18 -1
  159. package/dist/server/tools/test-map.js.map +1 -1
  160. package/dist/server/tools/trace.d.ts +84 -0
  161. package/dist/server/tools/trace.d.ts.map +1 -0
  162. package/dist/server/tools/trace.js +317 -0
  163. package/dist/server/tools/trace.js.map +1 -0
  164. package/dist/testing/coverage.d.ts +11 -0
  165. package/dist/testing/coverage.d.ts.map +1 -1
  166. package/dist/testing/coverage.js +34 -2
  167. package/dist/testing/coverage.js.map +1 -1
  168. package/dist/testing/test-mapper.d.ts +1 -1
  169. package/dist/testing/test-mapper.d.ts.map +1 -1
  170. package/dist/testing/test-mapper.js +1 -0
  171. package/dist/testing/test-mapper.js.map +1 -1
  172. package/package.json +3 -1
  173. package/dist/indexer/stages/scip-enrichment.d.ts +0 -43
  174. package/dist/indexer/stages/scip-enrichment.d.ts.map +0 -1
  175. package/dist/indexer/stages/scip-enrichment.js +0 -174
  176. package/dist/indexer/stages/scip-enrichment.js.map +0 -1
  177. package/dist/indexer/stages/scip-source.d.ts.map +0 -1
  178. package/dist/indexer/stages/scip-source.js.map +0 -1
  179. package/dist/server/tools/annotations.d.ts +0 -40
  180. package/dist/server/tools/annotations.d.ts.map +0 -1
  181. package/dist/server/tools/annotations.js +0 -35
  182. package/dist/server/tools/annotations.js.map +0 -1
  183. package/dist/server/tools/architecture.d.ts +0 -67
  184. package/dist/server/tools/architecture.d.ts.map +0 -1
  185. package/dist/server/tools/architecture.js +0 -209
  186. package/dist/server/tools/architecture.js.map +0 -1
  187. package/dist/server/tools/coverage.d.ts +0 -67
  188. package/dist/server/tools/coverage.d.ts.map +0 -1
  189. package/dist/server/tools/coverage.js +0 -74
  190. package/dist/server/tools/coverage.js.map +0 -1
  191. package/dist/server/tools/graph-analysis.d.ts +0 -64
  192. package/dist/server/tools/graph-analysis.d.ts.map +0 -1
  193. package/dist/server/tools/graph-analysis.js +0 -82
  194. package/dist/server/tools/graph-analysis.js.map +0 -1
  195. package/dist/server/tools/notes.d.ts +0 -165
  196. package/dist/server/tools/notes.d.ts.map +0 -1
  197. package/dist/server/tools/notes.js +0 -214
  198. package/dist/server/tools/notes.js.map +0 -1
  199. package/dist/server/tools/routes.d.ts +0 -38
  200. package/dist/server/tools/routes.d.ts.map +0 -1
  201. package/dist/server/tools/routes.js +0 -38
  202. package/dist/server/tools/routes.js.map +0 -1
  203. package/dist/server/tools/writeback.d.ts +0 -49
  204. package/dist/server/tools/writeback.d.ts.map +0 -1
  205. package/dist/server/tools/writeback.js +0 -68
  206. package/dist/server/tools/writeback.js.map +0 -1
@@ -1,19 +1,25 @@
1
1
  /**
2
- * @module indexer/stages/scip-source
2
+ * @module indexer/stages/scip-indexer
3
3
  *
4
4
  * Pipeline stage: for SCIP-covered languages, populate `files`, `symbols`,
5
5
  * `symbol_refs`, `type_refs`, `symbol_relationships`, and `file_imports`
6
6
  * **directly from the SCIP index** — bypassing tree-sitter entirely.
7
7
  *
8
- * This is the SCIP-primary architecture. SCIP is the source of truth for
9
- * both the symbol table and the call graph. For each SCIP document:
8
+ * This is the single-pass SCIP architecture. SCIP is the source of truth
9
+ * for the symbol table, the call graph, **and** enrichment metadata (type
10
+ * signatures, definition locations). All data is written in one pass.
11
+ *
12
+ * For each SCIP document:
10
13
  *
11
14
  * 1. **Symbols**: Definition occurrences → `symbols` rows; kinds inferred
12
15
  * from SCIP descriptor suffixes; spans from `enclosing_range`.
16
+ * Enrichment columns (`resolved_type_signature`, `resolved_return_type`,
17
+ * `definition_uri`, `definition_path`) are populated inline.
13
18
  * 2. **Refs**: Non-definition, non-local reference occurrences →
14
19
  * `symbol_refs` rows with both `caller_id` and `callee_id` resolved
15
20
  * using containment (which symbol's span encloses this ref?) and
16
21
  * definition lookup (where is the referenced SCIP symbol defined?).
22
+ * Enrichment columns are populated inline from the same SCIP data.
17
23
  *
18
24
  * SCIP refs are inserted **pre-resolved** with `resolution_method =
19
25
  * 'scip_definition'`. The downstream resolution stage only processes
@@ -23,24 +29,36 @@
23
29
  *
24
30
  * Same tables as `SourceIndexStage`: `files`, `symbols`, `symbols_fts`,
25
31
  * `symbol_refs`, `type_refs`, `symbol_relationships`, `file_imports`.
32
+ * Additionally populates enrichment columns (type signatures, definition
33
+ * locations) inline during the same pass.
26
34
  *
27
35
  * ## Pipeline ordering
28
36
  *
29
37
  * This stage runs **before** `SourceIndexStage`. It stores which
30
- * languages and files it handled in `context.scipSourcedLanguages` and
31
- * `context.scipSourcedFiles` so the tree-sitter path can skip them.
38
+ * languages and files it handled in `context.scipSourcedLanguages`,
39
+ * `context.scipSourcedFiles`, and `context.scipCoveredLanguages` so
40
+ * `SourceIndexStage` can skip full extraction (but still compute
41
+ * tree-sitter metrics) and `LspEnrichmentStage` knows not to re-enrich
42
+ * these languages.
32
43
  */
33
44
  import * as fs from 'node:fs';
34
45
  import * as crypto from 'node:crypto';
46
+ import { tmpdir } from 'node:os';
35
47
  import { resolve } from 'node:path';
48
+ import { pathToFileURL } from 'node:url';
36
49
  import { fromBinary } from '@bufbuild/protobuf';
37
50
  import { IndexSchema, SymbolRole, } from '../../scip/scip_pb.js';
38
- import { buildStructuralEmbeddingText } from '../../embeddings/embedder.js';
39
51
  import { normalizeTypeName } from '../../resolution/call-graph.js';
40
52
  import { SCIP_SUPPORTED_LANGUAGES, resolveScipIndexerRegistry } from '../../scip/registry.js';
53
+ import { getLogger } from '../../logger.js';
54
+ import { extractReturnType } from '../../scip/index-reader.js';
55
+ import { getSpecsForLanguage, installScipIndexer } from '../../scip/installer.js';
56
+ import { ensureCompilationDatabase } from '../../scip/compdb.js';
57
+ import { EXT_TO_LANG } from '../../discovery/walker.js';
41
58
  import { readFileSync, existsSync } from 'node:fs';
42
59
  import { join } from 'node:path';
43
- import { execFileSync } from 'node:child_process';
60
+ import { execFile } from 'node:child_process';
61
+ import { promisify } from 'node:util';
44
62
  // ─── SCIP symbol string → Lore kind mapping ──────────────────────────────────
45
63
  /**
46
64
  * Infer a Lore symbol `kind` from a SCIP symbol string.
@@ -89,6 +107,10 @@ function inferKindFromScipSymbol(scipSymbol, docHint) {
89
107
  return 'constant';
90
108
  if (docHint.includes('(property)'))
91
109
  return 'property';
110
+ // scip-clang uses term descriptors for C/C++ functions:
111
+ // ` $ funcName(hexhash).` — the (hash) indicates a function, not a variable.
112
+ if (/\([0-9a-f]{8,}\)\.$/.test(scipSymbol))
113
+ return 'function';
92
114
  return 'variable';
93
115
  }
94
116
  // Meta: ends with :
@@ -110,12 +132,17 @@ function extractNameFromScipSymbol(scipSymbol) {
110
132
  let cleaned = scipSymbol.replace(/[.#/:]$/, '');
111
133
  // For methods, strip the disambiguator: `name(+1).` → `name`
112
134
  cleaned = cleaned.replace(/\(\+?\d*\)$/, '');
135
+ // scip-clang uses ` $ name(hash)` for C/C++ symbols — strip the hash.
136
+ // E.g., ` $ parse_analyze_fixedparams(39d222e79bbfb7c0)` → `parse_analyze_fixedparams`
137
+ cleaned = cleaned.replace(/\([0-9a-f]{8,}\)$/, '');
113
138
  // Get the last descriptor's name
114
139
  // Descriptors are separated by ., #, /, :, or ()
115
140
  const parts = cleaned.split(/[.#/:]/);
116
141
  let name = parts[parts.length - 1] || '';
117
142
  // Remove backtick escaping
118
143
  name = name.replace(/`/g, '');
144
+ // Strip leading ` $ ` prefix used by scip-clang
145
+ name = name.replace(/^\s*\$\s*/, '');
119
146
  // Handle parameter descriptors like `(paramName)`
120
147
  if (name.startsWith('(') && name.endsWith(')')) {
121
148
  name = name.slice(1, -1);
@@ -243,11 +270,14 @@ function inferTypeRefKind(sourceLines, refLine, refChar) {
243
270
  return 'other';
244
271
  }
245
272
  // ─── Stage implementation ────────────────────────────────────────────────────
246
- export class ScipSourceStage {
247
- name = 'scip-source';
273
+ export class ScipIndexerStage {
274
+ name = 'scip-indexer';
248
275
  async execute(context, mode) {
249
276
  if (!context.scip?.enabled)
250
277
  return;
278
+ // SCIP only runs during baseline builds — never during overlay updates.
279
+ if (context.layer === 'overlay')
280
+ return;
251
281
  const log = context.log;
252
282
  const rootDir = context.walkerConfig.rootDir;
253
283
  // In update mode, determine which SCIP-supported languages have changed
@@ -266,19 +296,27 @@ export class ScipSourceStage {
266
296
  }
267
297
  }
268
298
  if (staleLanguages.size === 0) {
269
- log.indexing('scip-source: no SCIP-supported languages in changed files, skipping');
299
+ log.indexing('scip-indexer: no SCIP-supported languages in changed files, skipping');
270
300
  return;
271
301
  }
272
- log.indexing('scip-source: stale languages', { languages: [...staleLanguages] });
302
+ log.indexing('scip-indexer: stale languages', { languages: [...staleLanguages] });
273
303
  }
274
- // Load SCIP index
275
- const indexBuffer = this.loadScipIndex(context.scip, rootDir, staleLanguages);
276
- if (!indexBuffer) {
277
- log.indexing('scip-source: no SCIP index available');
304
+ // Load SCIP indexes (one per indexer that succeeds)
305
+ const indexBuffers = await this.loadScipIndexes(context.scip, rootDir, staleLanguages);
306
+ if (indexBuffers.length === 0) {
307
+ log.indexing('scip-indexer: no SCIP index available');
278
308
  return;
279
309
  }
280
- const scipIndex = fromBinary(IndexSchema, indexBuffer);
281
- log.indexing('scip-source: loaded index', {
310
+ // Parse all SCIP index buffers and combine their documents
311
+ const allDocuments = [];
312
+ const allExternalSymbols = [];
313
+ for (const buf of indexBuffers) {
314
+ const parsed = fromBinary(IndexSchema, buf);
315
+ allDocuments.push(...parsed.documents);
316
+ allExternalSymbols.push(...parsed.externalSymbols);
317
+ }
318
+ const scipIndex = { documents: allDocuments, externalSymbols: allExternalSymbols };
319
+ log.indexing('scip-indexer: loaded index', {
282
320
  documents: scipIndex.documents.length,
283
321
  externalSymbols: scipIndex.externalSymbols.length,
284
322
  });
@@ -294,7 +332,7 @@ export class ScipSourceStage {
294
332
  if (loreLang)
295
333
  coveredLanguages.add(loreLang);
296
334
  }
297
- log.indexing('scip-source: languages covered', { languages: [...coveredLanguages] });
335
+ log.indexing('scip-indexer: languages covered', { languages: [...coveredLanguages] });
298
336
  // Determine the project's SCIP symbol prefix so we can distinguish
299
337
  // internal symbols from external ones (stdlib, node_modules, etc.).
300
338
  // Internal symbols are those whose SCIP string starts with the same
@@ -350,18 +388,19 @@ export class ScipSourceStage {
350
388
  const db = context.db;
351
389
  const branch = context.branch;
352
390
  // Prepared statements
353
- const insertFile = db.prepare(`INSERT INTO files (path, branch, language, size_bytes, last_hash, source)
354
- VALUES (?, ?, ?, ?, ?, ?)`);
355
- const insertSymbol = db.prepare(`INSERT INTO symbols (file_id, name, kind, start_line, end_line, signature, doc_comment)
356
- VALUES (?, ?, ?, ?, ?, ?, ?)`);
357
- const insertFts = db.prepare('INSERT INTO symbols_fts(rowid, name, signature, kind) VALUES (?, ?, ?, ?)');
358
- const insertCallRef = db.prepare(`INSERT INTO symbol_refs (caller_id, file_id, callee_id, callee_name, call_line, call_character, call_kind, resolution_method)
391
+ const insertFile = db.prepare(`INSERT INTO files (path, branch, language, size_bytes, last_hash, source, layer, generation)
359
392
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)`);
360
- const insertTypeRef = db.prepare(`INSERT INTO type_refs (file_id, symbol_id, type_id, type_name, type_name_bare, ref_kind, ref_line, ref_character, resolution_method)
361
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`);
362
- const insertImport = db.prepare('INSERT INTO file_imports (file_id, raw_import) VALUES (?, ?)');
363
- const insertRelationship = db.prepare(`INSERT INTO symbol_relationships (file_id, source_symbol_id, target_symbol_name, relationship_type, line, character, resolution_method)
364
- VALUES (?, ?, ?, ?, ?, ?, ?)`);
393
+ const insertSymbol = db.prepare(`INSERT INTO symbols (file_id, name, kind, start_line, end_line, signature, doc_comment, resolved_type_signature, resolved_return_type, definition_uri, definition_path, layer, generation)
394
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
395
+ const insertCallRef = db.prepare(`INSERT INTO symbol_refs (caller_id, file_id, callee_id, callee_name, call_line, call_character, call_kind, resolution_method, resolved_type_signature, resolved_return_type, definition_uri, definition_path, definition_line, definition_character, layer, generation)
396
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
397
+ const insertTypeRef = db.prepare(`INSERT INTO type_refs (file_id, symbol_id, type_id, type_name, type_name_bare, ref_kind, ref_line, ref_character, resolution_method, resolved_type_signature, definition_uri, definition_path, definition_line, definition_character, layer, generation)
398
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
399
+ const insertImport = db.prepare('INSERT INTO file_imports (file_id, raw_import, resolved_id, layer, generation) VALUES (?, ?, ?, ?, ?)');
400
+ const insertRelationship = db.prepare(`INSERT INTO symbol_relationships (file_id, source_symbol_id, target_symbol_name, relationship_type, line, character, resolution_method, definition_uri, definition_path, definition_line, definition_character, layer, generation)
401
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
402
+ const layer = context.layer;
403
+ const generation = context.generation;
365
404
  // Global map: SCIP symbol string → Lore numeric symbol ID (across all files)
366
405
  const scipToLoreId = new Map();
367
406
  // Pass 1: Create files and symbols
@@ -380,12 +419,13 @@ export class ScipSourceStage {
380
419
  catch {
381
420
  continue;
382
421
  }
422
+ // Cache for downstream stages (metrics computation, enrichment).
423
+ context.sourceCache.set(absPath, source);
383
424
  const sizeBytes = Buffer.byteLength(source, 'utf8');
384
425
  const hash = crypto.createHash('sha256').update(source).digest('hex');
385
426
  // Delete existing data for this file (like SourceIndexStage does)
386
427
  const existing = db.prepare('SELECT id FROM files WHERE path = ? AND branch = ?').get(absPath, branch);
387
428
  if (existing) {
388
- db.prepare('DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_id = ?)').run(existing.id);
389
429
  db.prepare('DELETE FROM symbol_relationships WHERE file_id = ?').run(existing.id);
390
430
  db.prepare('DELETE FROM type_refs WHERE file_id = ?').run(existing.id);
391
431
  db.prepare('UPDATE symbol_refs SET callee_id = NULL WHERE callee_id IN (SELECT id FROM symbols WHERE file_id = ?)').run(existing.id);
@@ -395,7 +435,7 @@ export class ScipSourceStage {
395
435
  db.prepare('DELETE FROM files WHERE id = ?').run(existing.id);
396
436
  }
397
437
  // Insert file
398
- const fileInfo = insertFile.run(absPath, branch, loreLang, sizeBytes, hash, source);
438
+ const fileInfo = insertFile.run(absPath, branch, loreLang, sizeBytes, hash, source, layer, generation);
399
439
  const fileId = Number(fileInfo.lastInsertRowid);
400
440
  fileIdMap.set(absPath, fileId);
401
441
  coveredFiles.add(absPath);
@@ -452,14 +492,18 @@ export class ScipSourceStage {
452
492
  continue;
453
493
  const signature = extractSignatureFromDoc(firstDoc);
454
494
  const docComment = symInfo.documentation.slice(1).join('\n').trim() || null;
455
- const info = insertSymbol.run(fileId, name, kind, defLoc.startLine, defLoc.endLine, signature || null, docComment);
495
+ // Compute enrichment data inline (definition + type signature)
496
+ const resolvedTypeSig = signature || null;
497
+ const resolvedReturnType = extractReturnType(resolvedTypeSig);
498
+ const definitionUri = pathToFileURL(absPath).toString();
499
+ const info = insertSymbol.run(fileId, name, kind, defLoc.startLine, defLoc.endLine, signature || null, docComment, resolvedTypeSig, resolvedReturnType, definitionUri, absPath, layer, generation);
456
500
  const loreId = Number(info.lastInsertRowid);
457
501
  scipToLoreId.set(symInfo.symbol, loreId);
458
- insertFts.run(loreId, name, buildStructuralEmbeddingText({ name, signature: signature || null }), kind);
459
502
  }
460
503
  // Insert imports (from Import-role occurrences)
461
- // Prefer the actual import path from source; fall back to SCIP package
462
- const seenImports = new Set();
504
+ // Prefer the actual import path from source; fall back to SCIP package.
505
+ // Use symbolDefinitions to pre-resolve imports to target file IDs.
506
+ const seenImports = new Map(); // rawImport → resolved file ID
463
507
  for (const occ of doc.occurrences) {
464
508
  if ((occ.symbolRoles & SymbolRole.Import) !== 0 && occ.symbol) {
465
509
  const importLine = occ.range[0] ?? 0;
@@ -475,9 +519,23 @@ export class ScipSourceStage {
475
519
  const parts = occ.symbol.split(' ');
476
520
  rawImport = parts.length >= 4 ? parts[3] : occ.symbol;
477
521
  }
478
- if (rawImport && !seenImports.has(rawImport)) {
479
- seenImports.add(rawImport);
480
- insertImport.run(fileId, rawImport);
522
+ if (!rawImport)
523
+ continue;
524
+ // Resolve the import's target file via SCIP symbol → definition location
525
+ const defLoc = symbolDefinitions.get(occ.symbol);
526
+ const resolvedFileId = defLoc ? (fileIdMap.get(defLoc.filePath) ?? null) : null;
527
+ if (seenImports.has(rawImport)) {
528
+ // If we already inserted this import without a resolved_id,
529
+ // upgrade it now that we have one.
530
+ if (resolvedFileId && !seenImports.get(rawImport)) {
531
+ seenImports.set(rawImport, resolvedFileId);
532
+ db.prepare('UPDATE file_imports SET resolved_id = ? WHERE file_id = ? AND raw_import = ?')
533
+ .run(resolvedFileId, fileId, rawImport);
534
+ }
535
+ }
536
+ else {
537
+ seenImports.set(rawImport, resolvedFileId);
538
+ insertImport.run(fileId, rawImport, resolvedFileId, layer, generation);
481
539
  }
482
540
  }
483
541
  }
@@ -517,7 +575,10 @@ export class ScipSourceStage {
517
575
  // relationships for symbols whose definition is in another file or
518
576
  // external package, so defLoc may be undefined.
519
577
  const defLoc = symbolDefinitions.get(symInfo.symbol);
520
- insertRelationship.run(fileId, sourceId, targetName, relType, defLoc?.line ?? null, defLoc?.character ?? null, targetId ? 'scip_definition' : 'unresolved');
578
+ // Resolve the target's definition location for enrichment
579
+ const targetDef = symbolDefinitions.get(rel.symbol);
580
+ const relDefUri = targetDef ? pathToFileURL(targetDef.filePath).toString() : null;
581
+ insertRelationship.run(fileId, sourceId, targetName, relType, defLoc?.line ?? null, defLoc?.character ?? null, targetId ? 'scip_definition' : 'unresolved', relDefUri, targetDef?.filePath ?? null, targetDef?.line ?? null, targetDef?.character ?? null, layer, generation);
521
582
  // If we have both source and target IDs, update the resolved target
522
583
  if (targetId) {
523
584
  db.prepare('UPDATE symbol_relationships SET target_symbol_id = ? WHERE file_id = ? AND source_symbol_id = ? AND target_symbol_name = ? AND relationship_type = ?').run(targetId, fileId, sourceId, targetName, relType);
@@ -527,7 +588,7 @@ export class ScipSourceStage {
527
588
  }
528
589
  });
529
590
  processDocuments();
530
- log.indexing('scip-source: symbols inserted', {
591
+ log.indexing('scip-indexer: symbols inserted', {
531
592
  files: fileIdMap.size,
532
593
  symbols: scipToLoreId.size,
533
594
  });
@@ -656,21 +717,49 @@ export class ScipSourceStage {
656
717
  }
657
718
  if (refKind === 'type') {
658
719
  const typeRefKind = inferTypeRefKind(sourceLines, line, character);
659
- insertTypeRef.run(fileId, callerId, calleeId, calleeName, normalizeTypeName(calleeName), typeRefKind, line, character, method);
660
- typeRefsInserted++;
720
+ // Resolve enrichment metadata for the referenced type
721
+ const refDef = symbolDefinitions.get(occ.symbol);
722
+ const refInfo = symbolInfoMap.get(occ.symbol);
723
+ const refSig = refInfo ? extractSignatureFromDoc(refInfo.documentation[0] ?? '') || null : null;
724
+ const refDefUri = refDef ? pathToFileURL(refDef.filePath).toString() : null;
725
+ try {
726
+ insertTypeRef.run(fileId, callerId, calleeId ?? null, calleeName, normalizeTypeName(calleeName), typeRefKind, line, character, method, refSig, refDefUri, refDef?.filePath ?? null, refDef?.line ?? null, refDef?.character ?? null, layer, generation);
727
+ typeRefsInserted++;
728
+ }
729
+ catch {
730
+ // FK constraint failure — skip this ref
731
+ refsNoCaller++;
732
+ }
661
733
  }
662
734
  else {
663
735
  // refKind === 'call'
664
- insertCallRef.run(callerId, fileId, calleeId, calleeName, line, character, 'direct', method);
665
- refsInserted++;
666
- if (isExternal)
667
- refsExternal++;
736
+ // Skip if callee can't be resolved — FK constraint requires valid callee_id
737
+ if (!calleeId) {
738
+ refsNoCaller++;
739
+ continue;
740
+ }
741
+ // Resolve enrichment metadata for the callee
742
+ const refDef = symbolDefinitions.get(occ.symbol);
743
+ const refInfo = symbolInfoMap.get(occ.symbol);
744
+ const refSig = refInfo ? extractSignatureFromDoc(refInfo.documentation[0] ?? '') || null : null;
745
+ const refReturnType = extractReturnType(refSig);
746
+ const refDefUri = refDef ? pathToFileURL(refDef.filePath).toString() : null;
747
+ try {
748
+ insertCallRef.run(callerId, fileId, calleeId, calleeName, line, character, 'direct', method, refSig, refReturnType, refDefUri, refDef?.filePath ?? null, refDef?.line ?? null, refDef?.character ?? null, layer, generation);
749
+ refsInserted++;
750
+ if (isExternal)
751
+ refsExternal++;
752
+ }
753
+ catch {
754
+ // FK constraint failure — skip this ref
755
+ refsNoCaller++;
756
+ }
668
757
  }
669
758
  }
670
759
  }
671
760
  });
672
761
  processRefs();
673
- log.indexing('scip-source: refs inserted', {
762
+ log.indexing('scip-indexer: refs inserted', {
674
763
  callRefs: refsInserted,
675
764
  typeRefs: typeRefsInserted,
676
765
  external: refsExternal,
@@ -681,6 +770,9 @@ export class ScipSourceStage {
681
770
  // Communicate coverage to downstream stages
682
771
  context.scipSourcedLanguages = coveredLanguages;
683
772
  context.scipSourcedFiles = coveredFiles;
773
+ // Also set scipCoveredLanguages so that LspEnrichmentStage skips
774
+ // languages already fully enriched by this single SCIP pass.
775
+ context.scipCoveredLanguages = coveredLanguages;
684
776
  // Add SCIP-sourced files to context.files so later stages process them
685
777
  for (const doc of scipIndex.documents) {
686
778
  const absPath = resolve(rootDir, doc.relativePath);
@@ -694,62 +786,191 @@ export class ScipSourceStage {
694
786
  // No persistent resources to clean up
695
787
  }
696
788
  // ─── SCIP index loading ──────────────────────────────────────────────────
697
- loadScipIndex(settings, rootDir, staleLanguages = null) {
789
+ async loadScipIndexes(settings, rootDir, staleLanguages = null) {
698
790
  // Try pre-computed index directory first
699
791
  if (settings.indexDir) {
792
+ const precomputed = [];
700
793
  // When staleLanguages is set, prefer per-language index files so
701
794
  // we only load the languages that actually need re-processing.
702
795
  if (staleLanguages) {
703
796
  for (const lang of staleLanguages) {
704
797
  const candidate = join(rootDir, settings.indexDir, `${lang}.scip`);
705
798
  if (existsSync(candidate)) {
706
- return readFileSync(candidate);
799
+ precomputed.push(readFileSync(candidate));
707
800
  }
708
801
  }
709
802
  }
710
- const candidates = [
711
- join(rootDir, settings.indexDir, 'index.scip'),
712
- // Language-specific index files
713
- ...['typescript', 'javascript', 'python', 'java', 'rust', 'c', 'cpp', 'csharp', 'ruby', 'php', 'go', 'dart'].map(lang => join(rootDir, settings.indexDir, `${lang}.scip`)),
714
- ];
715
- for (const candidate of candidates) {
716
- if (existsSync(candidate)) {
717
- return readFileSync(candidate);
803
+ if (precomputed.length === 0) {
804
+ const candidates = [
805
+ join(rootDir, settings.indexDir, 'index.scip'),
806
+ ...['typescript', 'javascript', 'python', 'java', 'rust', 'c', 'cpp', 'csharp', 'ruby', 'php', 'go', 'dart'].map(lang => join(rootDir, settings.indexDir, `${lang}.scip`)),
807
+ ];
808
+ for (const candidate of candidates) {
809
+ if (existsSync(candidate)) {
810
+ precomputed.push(readFileSync(candidate));
811
+ }
718
812
  }
719
813
  }
814
+ if (precomputed.length > 0)
815
+ return precomputed;
720
816
  }
721
817
  // Try running an indexer
722
- const resolvedIndexers = resolveScipIndexerRegistry(settings.indexers);
818
+ let resolvedIndexers = resolveScipIndexerRegistry(settings.indexers);
819
+ const log = getLogger();
820
+ // Check if any indexers are available; if not, try auto-installing
821
+ const requestedLanguages = staleLanguages ?? new Set(Object.keys(resolvedIndexers));
822
+ const hasAvailable = [...requestedLanguages].some((lang) => resolvedIndexers[lang]?.available);
823
+ if (!hasAvailable) {
824
+ const attempted = new Set();
825
+ for (const lang of requestedLanguages) {
826
+ for (const spec of getSpecsForLanguage(lang)) {
827
+ if (attempted.has(spec.command))
828
+ continue;
829
+ attempted.add(spec.command);
830
+ log.indexing(`scip-indexer: auto-installing ${spec.command} for ${lang}...`);
831
+ const result = await installScipIndexer(spec);
832
+ if (result.installed) {
833
+ log.indexing(`scip-indexer: installed ${spec.command} at ${result.path}`);
834
+ }
835
+ else {
836
+ log.indexing(`scip-indexer: could not install ${spec.command}: ${result.error ?? 'unknown'}`);
837
+ }
838
+ }
839
+ }
840
+ // Re-resolve after installation
841
+ resolvedIndexers = resolveScipIndexerRegistry(settings.indexers);
842
+ }
843
+ // Determine which SCIP-supported languages actually exist in the project
844
+ // so we don't waste time running irrelevant indexers (e.g., scip-go on a C project).
845
+ const projectLanguages = staleLanguages ?? detectProjectLanguages(resolve(rootDir));
846
+ // Run all available indexers and merge results — don't stop at the first success.
847
+ // Group by shared command to avoid running the same indexer twice (e.g., scip-java for java/scala/kotlin).
848
+ const commandsRun = new Set();
849
+ const indexBuffers = [];
723
850
  for (const [lang, indexer] of Object.entries(resolvedIndexers)) {
724
851
  if (!indexer.available)
725
852
  continue;
726
- // Per-language staleness: skip indexers for languages that haven't changed.
727
- if (staleLanguages && !staleLanguages.has(lang))
853
+ // Skip languages not present in the project
854
+ if (!projectLanguages.has(lang))
855
+ continue;
856
+ // Don't run the same command twice (e.g., scip-clang for both c and cpp)
857
+ if (commandsRun.has(indexer.command))
728
858
  continue;
859
+ commandsRun.add(indexer.command);
729
860
  try {
730
- const outputPath = join(rootDir, `.lore-scip-${lang}.scip`);
731
- const args = indexer.args.map(a => a.replace(/\{output\}/g, outputPath));
732
- execFileSync(indexer.command, args, {
733
- cwd: rootDir,
734
- timeout: settings.timeoutMs,
735
- stdio: ['ignore', 'pipe', 'pipe'],
736
- });
861
+ const outputPath = resolve(rootDir, `.lore-scip-${lang}.scip`);
862
+ let args = indexer.args.map(a => a.replace(/\{output\}/g, outputPath));
863
+ const cwd = resolve(rootDir);
864
+ // For C/C++: ensure a compile_commands.json exists and pass it to scip-clang
865
+ if ((lang === 'c' || lang === 'cpp') && args.some(a => a.includes('{compdb}'))) {
866
+ const compdb = await ensureCompilationDatabase(rootDir, settings.timeoutMs);
867
+ if (!compdb.path) {
868
+ log.indexing(`scip-indexer: no compile_commands.json for ${lang}, skipping`);
869
+ continue;
870
+ }
871
+ args = args.map(a => a.replace(/\{compdb\}/g, compdb.path));
872
+ }
873
+ // For TypeScript: generate a broad tsconfig so scip-typescript
874
+ // indexes ALL .ts files (including tests), not just those in the
875
+ // project's tsconfig "include" (which typically excludes tests).
876
+ let tempTsconfigPath = null;
877
+ if (lang === 'typescript') {
878
+ tempTsconfigPath = createLoreScipTsconfig(rootDir);
879
+ if (tempTsconfigPath) {
880
+ args.push(tempTsconfigPath);
881
+ }
882
+ }
883
+ // scip-clang needs a longer timeout for large C projects
884
+ const indexerTimeout = (lang === 'c' || lang === 'cpp')
885
+ ? Math.max(settings.timeoutMs, 600_000)
886
+ : settings.timeoutMs;
887
+ const execFileAsync = promisify(execFile);
888
+ const executablePath = indexer.resolvedPath ?? indexer.command;
889
+ try {
890
+ await execFileAsync(executablePath, args, {
891
+ cwd,
892
+ timeout: indexerTimeout,
893
+ });
894
+ }
895
+ finally {
896
+ if (tempTsconfigPath) {
897
+ try {
898
+ fs.unlinkSync(tempTsconfigPath);
899
+ }
900
+ catch { /* best effort */ }
901
+ }
902
+ }
737
903
  // Check for output
738
- for (const candidate of [outputPath, join(rootDir, 'index.scip')]) {
904
+ for (const candidate of [outputPath, resolve(rootDir, 'index.scip')]) {
739
905
  if (existsSync(candidate)) {
740
906
  const data = readFileSync(candidate);
741
907
  try {
742
908
  fs.unlinkSync(candidate);
743
909
  }
744
910
  catch { /* best effort */ }
745
- return data;
911
+ indexBuffers.push(data);
912
+ break;
746
913
  }
747
914
  }
748
915
  }
749
- catch {
916
+ catch (error) {
917
+ const msg = error instanceof Error ? error.message : String(error);
918
+ log.indexing(`scip-indexer: indexer failed for ${lang}: ${msg}`);
750
919
  continue;
751
920
  }
752
921
  }
922
+ return indexBuffers;
923
+ }
924
+ }
925
+ // ─── Temporary tsconfig for broad SCIP indexing ─────────────────────────────
926
+ /** Fields that only affect build output, not type-checking or SCIP indexing. */
927
+ const TSCONFIG_BUILD_ONLY_FIELDS = [
928
+ 'outDir', 'rootDir', 'declaration', 'declarationMap', 'declarationDir',
929
+ 'sourceMap', 'inlineSourceMap', 'inlineSources', 'composite',
930
+ 'tsBuildInfoFile', 'emitDeclarationOnly',
931
+ ];
932
+ /**
933
+ * Generate a temporary tsconfig that includes **all** `.ts`/`.tsx` files
934
+ * in the project, so `scip-typescript` indexes tests and other files
935
+ * excluded by the project's production tsconfig.
936
+ *
937
+ * The file is written to `os.tmpdir()` so the indexed repo is never mutated.
938
+ * Include/exclude globs use absolute paths rooted at `rootDir` so
939
+ * `scip-typescript` resolves source files correctly even though the
940
+ * tsconfig lives elsewhere.
941
+ *
942
+ * Strips build-only compiler options (`outDir`, `rootDir`, `declaration`,
943
+ * etc.) that would conflict with the broad `include` and are irrelevant
944
+ * for SCIP analysis. Preserves all type-checking options (`strict`,
945
+ * `paths`, `baseUrl`, etc.) so SCIP still resolves types correctly.
946
+ *
947
+ * Returns the path to the temp file, or `null` if no tsconfig exists.
948
+ */
949
+ export function createLoreScipTsconfig(rootDir) {
950
+ const log = getLogger();
951
+ const tsconfigPath = join(rootDir, 'tsconfig.json');
952
+ if (!existsSync(tsconfigPath))
953
+ return null;
954
+ try {
955
+ const raw = JSON.parse(readFileSync(tsconfigPath, 'utf8'));
956
+ const compilerOptions = { ...(raw.compilerOptions ?? {}) };
957
+ // Strip build-only fields
958
+ for (const field of TSCONFIG_BUILD_ONLY_FIELDS) {
959
+ delete compilerOptions[field];
960
+ }
961
+ // Use absolute paths so the tsconfig works from tmpdir
962
+ const absRoot = resolve(rootDir);
963
+ const loreTsconfig = {
964
+ compilerOptions,
965
+ include: [join(absRoot, '**/*.ts'), join(absRoot, '**/*.tsx')],
966
+ exclude: (raw.exclude ?? ['node_modules']).map((e) => join(absRoot, e)),
967
+ };
968
+ const outPath = join(tmpdir(), `lore-scip-${crypto.randomUUID()}.json`);
969
+ fs.writeFileSync(outPath, JSON.stringify(loreTsconfig, null, 2));
970
+ log.debug('scip', `generated broad tsconfig for SCIP: ${outPath}`);
971
+ return outPath;
972
+ }
973
+ catch {
753
974
  return null;
754
975
  }
755
976
  }
@@ -770,6 +991,10 @@ function classifyScipReference(scipSymbol) {
770
991
  // Method/function: ends with (). or (+N). (with disambiguator)
771
992
  if (/\(\+?\d*\)\.$/.test(scipSymbol))
772
993
  return 'call';
994
+ // scip-clang C/C++ functions: term descriptors ending with `(hexhash).`
995
+ // E.g., `$ parse_analyze_fixedparams(39d222e79bbfb7c0).`
996
+ if (/\([0-9a-f]{8,}\)\.$/.test(scipSymbol))
997
+ return 'call';
773
998
  // Type: ends with #
774
999
  if (scipSymbol.endsWith('#'))
775
1000
  return 'type';
@@ -803,7 +1028,7 @@ function classifyScipReference(scipSymbol) {
803
1028
  */
804
1029
  function extractImportPathFromSource(line) {
805
1030
  const trimmed = line.trim();
806
- // JS/TS: import ... from 'path' | import 'path' | require('path')
1031
+ // JS/TS: import ... from 'path' | import 'path' | require('path') | import('path')
807
1032
  let m = trimmed.match(/\bfrom\s+['"]([^'"]+)['"]/);
808
1033
  if (m)
809
1034
  return m[1];
@@ -811,6 +1036,9 @@ function extractImportPathFromSource(line) {
811
1036
  if (m)
812
1037
  return m[1];
813
1038
  m = trimmed.match(/^import\s+['"]([^'"]+)['"]/);
1039
+ if (m)
1040
+ return m[1];
1041
+ m = trimmed.match(/\bimport\s*\(\s*['"]([^'"]+)['"]\s*\)/);
814
1042
  if (m)
815
1043
  return m[1];
816
1044
  // C/C++: #include "path" | #include <path>
@@ -869,32 +1097,65 @@ const SCIP_LANG_MAP = {
869
1097
  go: 'go',
870
1098
  dart: 'dart',
871
1099
  };
872
- const EXT_TO_LANG = {
873
- '.ts': 'typescript',
874
- '.tsx': 'typescript',
875
- '.js': 'javascript',
876
- '.jsx': 'javascript',
877
- '.mjs': 'javascript',
878
- '.cjs': 'javascript',
879
- '.py': 'python',
880
- '.java': 'java',
881
- '.scala': 'scala',
882
- '.sc': 'scala',
883
- '.kt': 'kotlin',
884
- '.kts': 'kotlin',
885
- '.rs': 'rust',
886
- '.c': 'c',
887
- '.h': 'c',
888
- '.cpp': 'cpp',
889
- '.cc': 'cpp',
890
- '.cxx': 'cpp',
891
- '.hpp': 'cpp',
892
- '.cs': 'csharp',
893
- '.rb': 'ruby',
894
- '.php': 'php',
895
- '.go': 'go',
896
- '.dart': 'dart',
897
- };
1100
+ /**
1101
+ * Quick scan of the project root to detect which SCIP-supported languages
1102
+ * are present. Checks for telltale file extensions and build files.
1103
+ * Only scans top-level + one directory deep to stay fast.
1104
+ */
1105
+ function detectProjectLanguages(rootDir) {
1106
+ const found = new Set();
1107
+ const langIndicators = {
1108
+ typescript: ['tsconfig.json', 'package.json'],
1109
+ python: ['setup.py', 'pyproject.toml', 'requirements.txt'],
1110
+ java: ['pom.xml', 'build.gradle', 'build.gradle.kts'],
1111
+ rust: ['Cargo.toml'],
1112
+ c: ['Makefile', 'CMakeLists.txt', 'meson.build', 'configure', 'configure.ac'],
1113
+ cpp: ['CMakeLists.txt', 'meson.build'],
1114
+ csharp: ['.csproj', '.sln'],
1115
+ ruby: ['Gemfile'],
1116
+ go: ['go.mod'],
1117
+ php: ['composer.json'],
1118
+ dart: ['pubspec.yaml'],
1119
+ };
1120
+ // Check for language indicator files at the root
1121
+ for (const [lang, indicators] of Object.entries(langIndicators)) {
1122
+ for (const indicator of indicators) {
1123
+ if (existsSync(join(rootDir, indicator))) {
1124
+ found.add(lang);
1125
+ break;
1126
+ }
1127
+ }
1128
+ }
1129
+ // Quick extension scan: read first-level directory entries
1130
+ try {
1131
+ const entries = fs.readdirSync(rootDir, { withFileTypes: true });
1132
+ for (const entry of entries) {
1133
+ if (entry.isFile()) {
1134
+ const ext = entry.name.slice(entry.name.lastIndexOf('.')).toLowerCase();
1135
+ const lang = EXT_TO_LANG[ext];
1136
+ if (lang && SCIP_SUPPORTED_LANGUAGES.has(lang))
1137
+ found.add(lang);
1138
+ }
1139
+ else if (entry.isDirectory() && entry.name !== 'node_modules' && !entry.name.startsWith('.')) {
1140
+ // One level deep
1141
+ try {
1142
+ const subEntries = fs.readdirSync(join(rootDir, entry.name), { withFileTypes: true });
1143
+ for (const sub of subEntries.slice(0, 50)) { // Limit to avoid scanning huge dirs
1144
+ if (sub.isFile()) {
1145
+ const ext = sub.name.slice(sub.name.lastIndexOf('.')).toLowerCase();
1146
+ const lang = EXT_TO_LANG[ext];
1147
+ if (lang && SCIP_SUPPORTED_LANGUAGES.has(lang))
1148
+ found.add(lang);
1149
+ }
1150
+ }
1151
+ }
1152
+ catch { /* ignore permission errors */ }
1153
+ }
1154
+ }
1155
+ }
1156
+ catch { /* ignore */ }
1157
+ return found;
1158
+ }
898
1159
  /**
899
1160
  * Determine the Lore language for a SCIP document.
900
1161
  *
@@ -919,4 +1180,4 @@ function inferLoreLanguage(scipLanguage, relativePath) {
919
1180
  // ─── Test-visible helpers ───────────────────────────────────────────────────
920
1181
  // Exported for unit testing only. Not part of the public API.
921
1182
  export { estimateSymbolEndLine as _estimateSymbolEndLine, inferTypeRefKind as _inferTypeRefKind, extractImportPathFromSource as _extractImportPathFromSource, inferKindFromScipSymbol as _inferKindFromScipSymbol, inferLoreLanguage as _inferLoreLanguage, classifyScipReference as _classifyScipReference, extractNameFromScipSymbol as _extractNameFromScipSymbol, };
922
- //# sourceMappingURL=scip-source.js.map
1183
+ //# sourceMappingURL=scip-indexer.js.map