scip-query 0.4.0 → 0.4.1

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 (91) hide show
  1. package/README.md +1 -1
  2. package/dist/{chunk-HNURMDF4.js → chunk-24LF6IZB.js} +3 -3
  3. package/dist/{chunk-IV6NZ426.js → chunk-3NJSJ7TE.js} +3 -3
  4. package/dist/{chunk-H3FPW5YN.js → chunk-43A4UCS7.js} +3 -3
  5. package/dist/{chunk-A5BGEBM7.js → chunk-5GCORUNV.js} +3 -3
  6. package/dist/{chunk-I2JM34UV.js → chunk-6CH23IAS.js} +4 -4
  7. package/dist/{chunk-5AJJGPZE.js → chunk-6ECR2FLR.js} +3 -3
  8. package/dist/{chunk-2UISVZGQ.js → chunk-6UZU7DFL.js} +3 -3
  9. package/dist/{chunk-NWCJWA36.js → chunk-7BS4CPJX.js} +5 -5
  10. package/dist/{chunk-QCYR4S6T.js → chunk-A6XLXV6W.js} +3 -3
  11. package/dist/{chunk-QGCEAVJD.js → chunk-ALUFWH3U.js} +232 -66
  12. package/dist/{chunk-CQRYLK33.js → chunk-CBIWNZZZ.js} +3 -3
  13. package/dist/{chunk-N2LH3M2P.js → chunk-DUJNJQPO.js} +3 -3
  14. package/dist/{chunk-XH56HXLC.js → chunk-EAGKJFDX.js} +3 -3
  15. package/dist/{chunk-SL674KAW.js → chunk-ELFGD5EW.js} +3 -3
  16. package/dist/{chunk-GIBETK3W.js → chunk-FVJE4MQL.js} +3 -3
  17. package/dist/{chunk-CQUNEJYM.js → chunk-GNAMV3JC.js} +3 -3
  18. package/dist/{chunk-VCOJRQPP.js → chunk-J47VSL6I.js} +3 -3
  19. package/dist/{chunk-7YBLWIXY.js → chunk-J6QXMYAQ.js} +3 -3
  20. package/dist/{chunk-2F2WH5WQ.js → chunk-JHVQB4Y5.js} +12 -12
  21. package/dist/{chunk-X3J4VPWM.js → chunk-JKXHHV4B.js} +2 -2
  22. package/dist/{chunk-4ZT7UGWW.js → chunk-KG4OFQEN.js} +3 -3
  23. package/dist/{chunk-SRELHCMG.js → chunk-KLNKDX6A.js} +4 -4
  24. package/dist/{chunk-LOVDB4C6.js → chunk-KYPXKV64.js} +3 -3
  25. package/dist/{chunk-UTRKBUCB.js → chunk-NXUIWD6K.js} +3 -3
  26. package/dist/{chunk-VU7FDTWV.js → chunk-OXX3QF24.js} +2 -2
  27. package/dist/{chunk-7HK5ZLOE.js → chunk-PCU455MX.js} +2 -2
  28. package/dist/{chunk-A4GWYETB.js → chunk-POLELLNM.js} +3 -3
  29. package/dist/{chunk-AS7N27JK.js → chunk-PU2254N2.js} +3 -3
  30. package/dist/{chunk-WNPF2I25.js → chunk-QMXSLHZP.js} +2 -2
  31. package/dist/{chunk-PGHN5UTM.js → chunk-R7HPHMRZ.js} +3 -3
  32. package/dist/{chunk-VUBLUTMU.js → chunk-RE7POFGI.js} +2 -2
  33. package/dist/{chunk-KDCQJTYW.js → chunk-RJ5GULL6.js} +2 -2
  34. package/dist/{chunk-HRDPUTIQ.js → chunk-RL74LF47.js} +2 -2
  35. package/dist/{chunk-5RKYZSQ6.js → chunk-SVLUJSY7.js} +3 -3
  36. package/dist/{chunk-D4I3ZMN5.js → chunk-SYQR4QGK.js} +3 -3
  37. package/dist/{chunk-QIXNAB5K.js → chunk-TO3L4YNK.js} +1 -2
  38. package/dist/{chunk-P42KQKJZ.js → chunk-TWVXFKJA.js} +4 -4
  39. package/dist/{chunk-MA3B3IUT.js → chunk-UJWI5CBB.js} +3 -3
  40. package/dist/{chunk-E7J7Q7UW.js → chunk-VKBOLNYN.js} +3 -3
  41. package/dist/{chunk-RIEA5DOB.js → chunk-VY2L4TP6.js} +3 -3
  42. package/dist/{chunk-A7YY7IDA.js → chunk-W46L2BXT.js} +2 -2
  43. package/dist/{chunk-VISMEWYP.js → chunk-XUVPQDXW.js} +4 -4
  44. package/dist/{chunk-ZU2AQQB5.js → chunk-Z5VSUOEE.js} +2 -2
  45. package/dist/{chunk-EOHPASDV.js → chunk-ZVZAIIB5.js} +3 -3
  46. package/dist/cli.js +254 -88
  47. package/dist/index.js +47 -46
  48. package/dist/queries/affected.js +3 -3
  49. package/dist/queries/bottlenecks.js +3 -3
  50. package/dist/queries/by-kind.js +3 -3
  51. package/dist/queries/call-graph.js +3 -3
  52. package/dist/queries/change-surface.js +3 -3
  53. package/dist/queries/code.js +3 -3
  54. package/dist/queries/complexity-hotspots.js +3 -3
  55. package/dist/queries/complexity.js +3 -3
  56. package/dist/queries/convergence.js +3 -3
  57. package/dist/queries/coupling.js +3 -3
  58. package/dist/queries/cycles.js +3 -3
  59. package/dist/queries/dataflow.js +3 -3
  60. package/dist/queries/dead.js +4 -4
  61. package/dist/queries/deep-chains.js +3 -3
  62. package/dist/queries/deps.js +3 -3
  63. package/dist/queries/diff-impact.js +2 -2
  64. package/dist/queries/drift.js +3 -3
  65. package/dist/queries/extract-candidates.js +3 -3
  66. package/dist/queries/fan.js +3 -3
  67. package/dist/queries/health.js +14 -14
  68. package/dist/queries/hierarchy.js +3 -3
  69. package/dist/queries/hotspots.js +3 -3
  70. package/dist/queries/imports.js +3 -3
  71. package/dist/queries/index.js +44 -44
  72. package/dist/queries/isolated.js +4 -4
  73. package/dist/queries/members.js +3 -3
  74. package/dist/queries/methods.js +3 -3
  75. package/dist/queries/outline.js +3 -3
  76. package/dist/queries/passthrough-candidates.js +3 -3
  77. package/dist/queries/redundant-reexports.js +4 -4
  78. package/dist/queries/refs.js +3 -3
  79. package/dist/queries/similar-chains.js +3 -3
  80. package/dist/queries/similar-files.js +3 -3
  81. package/dist/queries/similar-signatures.js +3 -3
  82. package/dist/queries/similar.js +3 -3
  83. package/dist/queries/slice.js +3 -3
  84. package/dist/queries/stale-abstractions.js +3 -3
  85. package/dist/queries/surface.js +3 -3
  86. package/dist/queries/symbols.js +3 -3
  87. package/dist/queries/system.js +3 -3
  88. package/dist/queries/trace.js +3 -3
  89. package/dist/queries/wrapper-candidates.js +3 -3
  90. package/dist/reindex-worker.js +3 -2
  91. package/package.json +5 -1
package/dist/cli.js CHANGED
@@ -1048,7 +1048,8 @@ async function reindex(opts) {
1048
1048
  const msg = err instanceof Error ? err.message : String(err);
1049
1049
  throw new Error(
1050
1050
  `Failed to index ${lang} with ${resolvedBinary}: ${msg}
1051
- Make sure ${binaryLabel} is installed and available on PATH.`
1051
+ Make sure ${binaryLabel} is installed and available on PATH.`,
1052
+ { cause: err }
1052
1053
  );
1053
1054
  }
1054
1055
  moveDefaultOutputIfNeeded(config, projectRoot, outputScip);
@@ -1065,7 +1066,7 @@ Make sure ${binaryLabel} is installed and available on PATH.`
1065
1066
  });
1066
1067
  } catch (err) {
1067
1068
  const msg = err instanceof Error ? err.message : String(err);
1068
- throw new Error(`Failed to convert SCIP index to SQLite: ${msg}`);
1069
+ throw new Error(`Failed to convert SCIP index to SQLite: ${msg}`, { cause: err });
1069
1070
  }
1070
1071
  const durationMs = Date.now() - start;
1071
1072
  onStatus(`Done in ${(durationMs / 1e3).toFixed(1)}s`);
@@ -1328,7 +1329,7 @@ import { basename as basename2 } from "path";
1328
1329
  // src/source-analysis.ts
1329
1330
  import {
1330
1331
  existsSync as existsSync8,
1331
- readFileSync as readFileSync4
1332
+ readFileSync as readFileSync3
1332
1333
  } from "fs";
1333
1334
  import {
1334
1335
  basename,
@@ -1365,7 +1366,7 @@ function getSourceImports(db, relativePath) {
1365
1366
  cache.set(normalized, []);
1366
1367
  return [];
1367
1368
  }
1368
- const source = readFileSync4(fullPath, "utf-8");
1369
+ const source = readFileSync3(fullPath, "utf-8");
1369
1370
  const parsed = isPythonSourcePath(normalized) ? parsePythonImports(db, normalized, source) : isJavaScriptSourcePath(normalized) ? parseJavaScriptImports(db, normalized, source) : isJvmSourcePath(normalized) ? parseJvmImports(db, normalized, source) : isRustSourcePath(normalized) ? parseRustImports(db, normalized, source) : isRubySourcePath(normalized) ? parseRubyImports(db, normalized, source) : isCLikeSourcePath(normalized) ? parseCLikeImports(db, normalized, source) : isDotNetSourcePath(normalized) ? parseDotNetImports(db, normalized, source) : isDartSourcePath(normalized) ? parseDartImports(db, normalized, source) : isPhpSourcePath(normalized) ? parsePhpImports(db, normalized, source) : [];
1370
1371
  cache.set(normalized, parsed);
1371
1372
  return parsed;
@@ -1382,7 +1383,7 @@ function getSourceExports(db, relativePath) {
1382
1383
  cache.set(normalized, []);
1383
1384
  return [];
1384
1385
  }
1385
- const source = readFileSync4(fullPath, "utf-8");
1386
+ const source = readFileSync3(fullPath, "utf-8");
1386
1387
  const parsed = isDartSourcePath(normalized) ? parseDartExports(db, normalized, source) : isRustSourcePath(normalized) ? parseRustExports(db, normalized, source) : [];
1387
1388
  cache.set(normalized, parsed);
1388
1389
  return parsed;
@@ -2424,7 +2425,7 @@ function getSourceText(db, relativePath) {
2424
2425
  cache.set(normalized, "");
2425
2426
  return "";
2426
2427
  }
2427
- const source = readFileSync4(fullPath, "utf-8");
2428
+ const source = readFileSync3(fullPath, "utf-8");
2428
2429
  cache.set(normalized, source);
2429
2430
  return source;
2430
2431
  }
@@ -2526,7 +2527,6 @@ function parseDescriptors(input) {
2526
2527
  const closingTick = input.indexOf("`", i + 1);
2527
2528
  if (closingTick === -1) {
2528
2529
  name = input.slice(i + 1);
2529
- i = input.length;
2530
2530
  descriptors.push({ name, suffix: "term" });
2531
2531
  break;
2532
2532
  }
@@ -2707,6 +2707,10 @@ function buildFileDepGraph(db, scope) {
2707
2707
  return graph;
2708
2708
  }
2709
2709
  function findFirstSymbolMatch(db, symbolPattern) {
2710
+ const exact = findExactSymbolMatch(db, symbolPattern.trim());
2711
+ if (exact) {
2712
+ return exact;
2713
+ }
2710
2714
  const fileLineMatch = symbolPattern.match(/^(.+):(\d+)-(\d+)$/);
2711
2715
  if (fileLineMatch) {
2712
2716
  const [, filePath, startStr, endStr] = fileLineMatch;
@@ -2744,19 +2748,16 @@ function findFirstSymbolMatch(db, symbolPattern) {
2744
2748
  );
2745
2749
  }
2746
2750
  if (row && !db.isIgnored(row.relative_path)) {
2747
- return {
2748
- symbolId: row.id,
2749
- symbol: row.symbol,
2750
- documentId: row.document_id,
2751
- startLine: row.start_line,
2752
- endLine: row.end_line,
2753
- relativePath: row.relative_path
2754
- };
2751
+ return hydrateSymbolMatch(db, row);
2755
2752
  }
2756
2753
  }
2757
2754
  const cleaned = normalizeLookupPattern(symbolPattern);
2758
2755
  const tokens = lookupTokens(symbolPattern);
2759
2756
  const candidates = getSymbolLookupCandidates(db, tokens);
2757
+ const direct = findDirectSymbolCandidate(candidates, symbolPattern, cleaned);
2758
+ if (direct && !db.isIgnored(direct.relative_path)) {
2759
+ return hydrateSymbolMatch(db, direct);
2760
+ }
2760
2761
  let best = null;
2761
2762
  for (const row of candidates) {
2762
2763
  if (db.isIgnored(row.relative_path)) continue;
@@ -2767,14 +2768,7 @@ function findFirstSymbolMatch(db, symbolPattern) {
2767
2768
  }
2768
2769
  }
2769
2770
  if (best) {
2770
- return {
2771
- symbolId: best.row.id,
2772
- symbol: best.row.symbol,
2773
- documentId: best.row.document_id,
2774
- startLine: best.row.start_line,
2775
- endLine: best.row.end_line,
2776
- relativePath: best.row.relative_path
2777
- };
2771
+ return hydrateSymbolMatch(db, best.row);
2778
2772
  }
2779
2773
  return null;
2780
2774
  }
@@ -2793,14 +2787,7 @@ function findExactSymbolMatch(db, symbol) {
2793
2787
  if (!row || db.isIgnored(row.relative_path)) {
2794
2788
  return null;
2795
2789
  }
2796
- return {
2797
- symbolId: row.id,
2798
- symbol: row.symbol,
2799
- documentId: row.document_id,
2800
- startLine: row.start_line,
2801
- endLine: row.end_line,
2802
- relativePath: row.relative_path
2803
- };
2790
+ return hydrateSymbolMatch(db, row);
2804
2791
  }
2805
2792
  function resolveIndexedFile(db, filePattern) {
2806
2793
  return resolveDocumentCandidates(db, filePattern, { allowMultiple: false })[0]?.relativePath ?? null;
@@ -2941,27 +2928,10 @@ function getCalleeRowsForSymbol(db, symbol, opts = {}) {
2941
2928
  });
2942
2929
  }
2943
2930
  const sourceFallback = getSourceBackedCalleeRows(db, symbol, opts.limit);
2944
- if (sourceFallback.length === 0) {
2945
- return primary;
2946
- }
2947
- if (primary.length === 0) {
2931
+ if (sourceFallback.length > 0) {
2948
2932
  return applyLimit(sourceFallback, opts.limit);
2949
2933
  }
2950
- const merged = [...sourceFallback];
2951
- const seen = new Set(merged.map((row) => `${row.symbol}|${row.file}`));
2952
- const preferredFallbackLeaves = new Set(
2953
- sourceFallback.filter((row) => !isLikelyTestPath(row.file)).map((row) => leafName(row.symbol))
2954
- );
2955
- for (const row of primary) {
2956
- if (isLikelyTestPath(row.file) && preferredFallbackLeaves.has(leafName(row.symbol))) {
2957
- continue;
2958
- }
2959
- const key = `${row.symbol}|${row.file}`;
2960
- if (seen.has(key)) continue;
2961
- seen.add(key);
2962
- merged.push(row);
2963
- }
2964
- return applyLimit(merged, opts.limit);
2934
+ return primary;
2965
2935
  }
2966
2936
  function getCallerRowsForSymbol(db, symbol, opts = {}) {
2967
2937
  const match = getFullSymbolMatch(db, symbol);
@@ -2994,11 +2964,13 @@ function getCallerRowsForSymbol(db, symbol, opts = {}) {
2994
2964
  ...getGenericSourceCallerRows(db, match, opts.limit)
2995
2965
  ]);
2996
2966
  if (sourceFallback.length === 0) {
2997
- return primary;
2967
+ return dedupeCallerRows(primary);
2998
2968
  }
2999
2969
  const merged = [...sourceFallback];
2970
+ const fallbackFiles = new Set(sourceFallback.map((row) => row.file));
3000
2971
  const seen = new Set(merged.map((row) => `${row.symbol}|${row.file}`));
3001
2972
  for (const row of primary) {
2973
+ if (fallbackFiles.has(row.file)) continue;
3002
2974
  const key = `${row.symbol}|${row.file}`;
3003
2975
  if (seen.has(key)) continue;
3004
2976
  if (isFunctionLikeSymbol(row.symbol) || merged.length === 0) {
@@ -3379,14 +3351,7 @@ function getFullSymbolMatch(db, symbol) {
3379
3351
  if (!row) {
3380
3352
  return null;
3381
3353
  }
3382
- return {
3383
- symbolId: row.id,
3384
- documentId: row.document_id,
3385
- startLine: row.start_line,
3386
- endLine: row.end_line,
3387
- symbol: row.symbol,
3388
- relativePath: row.relative_path
3389
- };
3354
+ return hydrateSymbolMatch(db, row);
3390
3355
  }
3391
3356
  function getDefinitionsForFile(db, relativePath) {
3392
3357
  let cache = FILE_DEFINITION_CACHE.get(db);
@@ -3406,6 +3371,7 @@ function getDefinitionsForFile(db, relativePath) {
3406
3371
  der.start_line,
3407
3372
  der.end_line,
3408
3373
  d.relative_path,
3374
+ gs.display_name,
3409
3375
  gs.kind,
3410
3376
  gs.documentation,
3411
3377
  gs.enclosing_symbol
@@ -3425,6 +3391,7 @@ function getDefinitionsForFile(db, relativePath) {
3425
3391
  MIN(c.start_line) AS start_line,
3426
3392
  MAX(c.end_line) AS end_line,
3427
3393
  d.relative_path,
3394
+ gs.display_name,
3428
3395
  gs.kind,
3429
3396
  gs.documentation,
3430
3397
  gs.enclosing_symbol
@@ -3439,23 +3406,222 @@ function getDefinitionsForFile(db, relativePath) {
3439
3406
  ORDER BY start_line, end_line`,
3440
3407
  relativePath
3441
3408
  );
3442
- const definitions = (primary.length > 0 ? primary : fallback).map((row) => ({
3409
+ const definitions = correctDefinitionRangesFromSource(
3410
+ db,
3411
+ relativePath,
3412
+ (primary.length > 0 ? primary : fallback).map((row) => ({
3413
+ symbolId: row.id,
3414
+ symbol: row.symbol,
3415
+ documentId: row.document_id,
3416
+ startLine: row.start_line,
3417
+ endLine: row.end_line,
3418
+ relativePath: row.relative_path,
3419
+ leaf: leafName(row.symbol),
3420
+ parentTypeName: parentTypeName(row.symbol),
3421
+ isFunctionLike: isFunctionLikeSymbol(row.symbol),
3422
+ isTypeLike: leafSuffix(row.symbol) === "type",
3423
+ kind: row.kind ?? null,
3424
+ documentation: row.documentation ?? null,
3425
+ enclosingSymbol: row.enclosing_symbol ?? null
3426
+ }))
3427
+ );
3428
+ cache.set(relativePath, definitions);
3429
+ return definitions;
3430
+ }
3431
+ function hydrateSymbolMatch(db, row) {
3432
+ const corrected = getDefinitionsForFile(db, row.relative_path).find((definition) => definition.symbolId === row.id);
3433
+ if (corrected) {
3434
+ return {
3435
+ symbolId: corrected.symbolId,
3436
+ symbol: corrected.symbol,
3437
+ documentId: corrected.documentId,
3438
+ startLine: corrected.startLine,
3439
+ endLine: corrected.endLine,
3440
+ relativePath: corrected.relativePath
3441
+ };
3442
+ }
3443
+ return {
3443
3444
  symbolId: row.id,
3444
3445
  symbol: row.symbol,
3445
3446
  documentId: row.document_id,
3446
3447
  startLine: row.start_line,
3447
3448
  endLine: row.end_line,
3448
- relativePath: row.relative_path,
3449
- leaf: leafName(row.symbol),
3450
- parentTypeName: parentTypeName(row.symbol),
3451
- isFunctionLike: isFunctionLikeSymbol(row.symbol),
3452
- isTypeLike: leafSuffix(row.symbol) === "type",
3453
- kind: row.kind,
3454
- documentation: row.documentation,
3455
- enclosingSymbol: row.enclosing_symbol
3456
- }));
3457
- cache.set(relativePath, definitions);
3458
- return definitions;
3449
+ relativePath: row.relative_path
3450
+ };
3451
+ }
3452
+ function findDirectSymbolCandidate(candidates, symbolPattern, cleanedPattern) {
3453
+ const trimmed = symbolPattern.trim();
3454
+ const directMatches = candidates.filter((row) => {
3455
+ const short = shortenSymbol(row.symbol);
3456
+ const display = (row.display_name ?? "").trim();
3457
+ return row.symbol === trimmed || short === trimmed || short === cleanedPattern || display === trimmed || display === cleanedPattern || `${display}()` === trimmed || row.relative_path === trimmed;
3458
+ });
3459
+ if (directMatches.length === 0) {
3460
+ return null;
3461
+ }
3462
+ directMatches.sort(
3463
+ (left, right) => left.end_line - left.start_line - (right.end_line - right.start_line) || left.relative_path.localeCompare(right.relative_path) || left.symbol.localeCompare(right.symbol)
3464
+ );
3465
+ return directMatches[0] ?? null;
3466
+ }
3467
+ function correctDefinitionRangesFromSource(db, relativePath, definitions) {
3468
+ const source = getSourceText(db, relativePath);
3469
+ if (!source) {
3470
+ return definitions;
3471
+ }
3472
+ const lines = source.split(/\r?\n/);
3473
+ const correctedStarts = /* @__PURE__ */ new Map();
3474
+ for (const definition of definitions) {
3475
+ correctedStarts.set(
3476
+ definition.symbolId,
3477
+ resolveCallableDefinitionStartLine(lines, definition)
3478
+ );
3479
+ }
3480
+ const correctedRanges = /* @__PURE__ */ new Map();
3481
+ const callableDefinitions = definitions.filter((definition) => isCallableDefinition(definition.symbol)).map((definition) => ({
3482
+ definition,
3483
+ startLine: correctedStarts.get(definition.symbolId) ?? definition.startLine
3484
+ })).sort(
3485
+ (left, right) => left.startLine - right.startLine || left.definition.startLine - right.definition.startLine || left.definition.symbol.localeCompare(right.definition.symbol)
3486
+ );
3487
+ for (let index = 0; index < callableDefinitions.length; index += 1) {
3488
+ const current = callableDefinitions[index];
3489
+ const next = callableDefinitions[index + 1];
3490
+ const maxEndLine = next ? Math.max(current.startLine, next.startLine - 1) : lines.length - 1;
3491
+ correctedRanges.set(current.definition.symbolId, {
3492
+ startLine: current.startLine,
3493
+ endLine: resolveCallableDefinitionEndLine(
3494
+ lines,
3495
+ current.definition,
3496
+ current.startLine,
3497
+ maxEndLine
3498
+ )
3499
+ });
3500
+ }
3501
+ return definitions.map((definition) => {
3502
+ const corrected = correctedRanges.get(definition.symbolId);
3503
+ if (!corrected) {
3504
+ return definition;
3505
+ }
3506
+ return {
3507
+ ...definition,
3508
+ startLine: corrected.startLine,
3509
+ endLine: corrected.endLine
3510
+ };
3511
+ });
3512
+ }
3513
+ function resolveCallableDefinitionStartLine(lines, definition) {
3514
+ if (!isCallableDefinition(definition.symbol)) {
3515
+ return definition.startLine;
3516
+ }
3517
+ const escapedLeaf = escapeRegex2(definition.leaf);
3518
+ const strongPatterns = [
3519
+ new RegExp(`\\b(?:function|def|fn)\\s+${escapedLeaf}\\b`),
3520
+ new RegExp(`\\b${escapedLeaf}\\b\\s*[:=]\\s*(?:async\\s*)?(?:function\\b|\\()`)
3521
+ ];
3522
+ const fallbackPatterns = [
3523
+ new RegExp(`\\b${escapedLeaf}\\b\\s*\\(`)
3524
+ ];
3525
+ return findNearestMatchingLine(
3526
+ lines,
3527
+ [...strongPatterns, ...fallbackPatterns],
3528
+ definition.startLine,
3529
+ definition.endLine
3530
+ );
3531
+ }
3532
+ function findNearestMatchingLine(lines, patterns, preferredStartLine, preferredEndLine) {
3533
+ const windowStart = Math.max(0, preferredStartLine - 40);
3534
+ const windowEnd = Math.min(lines.length - 1, Math.max(preferredEndLine + 40, preferredStartLine + 5));
3535
+ const windowMatch = matchNearestLine(lines, patterns, preferredStartLine, windowStart, windowEnd);
3536
+ if (windowMatch !== null) {
3537
+ return windowMatch;
3538
+ }
3539
+ const fullMatch = matchNearestLine(lines, patterns, preferredStartLine, 0, lines.length - 1);
3540
+ return fullMatch ?? Math.max(0, Math.min(preferredStartLine, lines.length - 1));
3541
+ }
3542
+ function matchNearestLine(lines, patterns, preferredLine, startLine, endLine) {
3543
+ let best = null;
3544
+ for (let lineIndex = startLine; lineIndex <= endLine; lineIndex += 1) {
3545
+ const line = lines[lineIndex] ?? "";
3546
+ if (!patterns.some((pattern) => pattern.test(line))) continue;
3547
+ const distance = Math.abs(lineIndex - preferredLine);
3548
+ if (!best || distance < best.distance) {
3549
+ best = { line: lineIndex, distance };
3550
+ }
3551
+ }
3552
+ return best?.line ?? null;
3553
+ }
3554
+ function resolveCallableDefinitionEndLine(lines, definition, startLine, maxEndLine) {
3555
+ const boundedEndLine = Math.max(startLine, Math.min(lines.length - 1, maxEndLine));
3556
+ const fallbackEndLine = Math.max(startLine, Math.min(boundedEndLine, definition.endLine));
3557
+ let braceDepth = 0;
3558
+ let parenDepth = 0;
3559
+ let sawOpeningBrace = false;
3560
+ for (let lineIndex = startLine; lineIndex <= boundedEndLine; lineIndex += 1) {
3561
+ const masked = maskStructuralLine(lines[lineIndex] ?? "");
3562
+ for (const char of masked) {
3563
+ if (char === "{") {
3564
+ braceDepth += 1;
3565
+ sawOpeningBrace = true;
3566
+ } else if (char === "}") {
3567
+ braceDepth = Math.max(0, braceDepth - 1);
3568
+ } else if (char === "(") {
3569
+ parenDepth += 1;
3570
+ } else if (char === ")") {
3571
+ parenDepth = Math.max(0, parenDepth - 1);
3572
+ }
3573
+ }
3574
+ if (sawOpeningBrace && braceDepth === 0) {
3575
+ return lineIndex;
3576
+ }
3577
+ if (!sawOpeningBrace && parenDepth === 0 && lineIndex >= fallbackEndLine) {
3578
+ return lineIndex;
3579
+ }
3580
+ }
3581
+ return fallbackEndLine;
3582
+ }
3583
+ function maskStructuralLine(line) {
3584
+ let masked = "";
3585
+ let quote = null;
3586
+ let escaping = false;
3587
+ for (let index = 0; index < line.length; index += 1) {
3588
+ const char = line[index];
3589
+ const next = line[index + 1];
3590
+ if (!quote && char === "/" && next === "/") {
3591
+ masked += " ".repeat(line.length - index);
3592
+ break;
3593
+ }
3594
+ if (quote) {
3595
+ if (escaping) {
3596
+ escaping = false;
3597
+ masked += " ";
3598
+ continue;
3599
+ }
3600
+ if (char === "\\") {
3601
+ escaping = true;
3602
+ masked += " ";
3603
+ continue;
3604
+ }
3605
+ if (char === quote) {
3606
+ quote = null;
3607
+ }
3608
+ masked += " ";
3609
+ continue;
3610
+ }
3611
+ if (char === '"' || char === "'" || char === "`") {
3612
+ quote = char;
3613
+ masked += " ";
3614
+ continue;
3615
+ }
3616
+ masked += char;
3617
+ }
3618
+ return masked;
3619
+ }
3620
+ function isCallableDefinition(symbol) {
3621
+ return symbol.includes("().");
3622
+ }
3623
+ function escapeRegex2(value) {
3624
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3459
3625
  }
3460
3626
  function getAllDefinitions(db, opts = {}) {
3461
3627
  const { scope } = opts;
@@ -5526,7 +5692,7 @@ function similar(db, symbolPattern, opts = {}) {
5526
5692
  const results = [];
5527
5693
  for (const candidate of candidates) {
5528
5694
  if (candidate.callees.size < 3) continue;
5529
- const { similarity, significantShared, trivialShared } = weightedSimilarity(
5695
+ const { similarity, significantShared } = weightedSimilarity(
5530
5696
  target.callees,
5531
5697
  candidate.callees,
5532
5698
  idfWeights
@@ -5763,11 +5929,11 @@ function definitionSnippet(db, relativePath, startLine, endLine, leaf) {
5763
5929
  return lines.slice(startLine, endLine + 1).join("\n");
5764
5930
  }
5765
5931
  const markerPatterns = [
5766
- new RegExp(`\\bdef\\s+${escapeRegex2(leaf)}\\b`),
5767
- new RegExp(`\\bfun\\s+${escapeRegex2(leaf)}\\b`),
5768
- new RegExp(`\\bfn\\s+${escapeRegex2(leaf)}\\b`),
5769
- new RegExp(`\\bfunction\\s+${escapeRegex2(leaf)}\\b`),
5770
- new RegExp(`\\b${escapeRegex2(leaf)}\\s*\\(`)
5932
+ new RegExp(`\\bdef\\s+${escapeRegex3(leaf)}\\b`),
5933
+ new RegExp(`\\bfun\\s+${escapeRegex3(leaf)}\\b`),
5934
+ new RegExp(`\\bfn\\s+${escapeRegex3(leaf)}\\b`),
5935
+ new RegExp(`\\bfunction\\s+${escapeRegex3(leaf)}\\b`),
5936
+ new RegExp(`\\b${escapeRegex3(leaf)}\\s*\\(`)
5771
5937
  ];
5772
5938
  const definitionStart = lines.findIndex((line) => markerPatterns.some((pattern) => pattern.test(line)));
5773
5939
  if (definitionStart >= 0) {
@@ -5829,7 +5995,7 @@ function splitIdentifier(value) {
5829
5995
  function looksLikeDefinitionBoundary(line) {
5830
5996
  return /^\s*(?:def|fun|fn|function|class|trait|module|object|enum|interface|public|private|protected)\b/.test(line);
5831
5997
  }
5832
- function escapeRegex2(value) {
5998
+ function escapeRegex3(value) {
5833
5999
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5834
6000
  }
5835
6001
 
@@ -7229,7 +7395,7 @@ function convergence(db, symbolPatternA, symbolPatternB) {
7229
7395
  }
7230
7396
 
7231
7397
  // src/queries/code.ts
7232
- import { readFileSync as readFileSync5 } from "fs";
7398
+ import { readFileSync as readFileSync4 } from "fs";
7233
7399
  import { join as join9 } from "path";
7234
7400
  function code(db, symbolPattern, opts = {}) {
7235
7401
  const { context = 0 } = opts;
@@ -7246,7 +7412,7 @@ function code(db, symbolPattern, opts = {}) {
7246
7412
  const filePath = join9(db.config.projectRoot, match.relativePath);
7247
7413
  let fileContent;
7248
7414
  try {
7249
- fileContent = readFileSync5(filePath, "utf-8");
7415
+ fileContent = readFileSync4(filePath, "utf-8");
7250
7416
  } catch {
7251
7417
  return null;
7252
7418
  }
@@ -7276,7 +7442,7 @@ function readFileRange(db, filePath, startLine, endLine, context) {
7276
7442
  const fullPath = join9(db.config.projectRoot, doc.relative_path);
7277
7443
  let fileContent;
7278
7444
  try {
7279
- fileContent = readFileSync5(fullPath, "utf-8");
7445
+ fileContent = readFileSync4(fullPath, "utf-8");
7280
7446
  } catch {
7281
7447
  return null;
7282
7448
  }
@@ -7296,7 +7462,7 @@ function readFileRange(db, filePath, startLine, endLine, context) {
7296
7462
  }
7297
7463
 
7298
7464
  // src/queries/complexity.ts
7299
- import { readFileSync as readFileSync6 } from "fs";
7465
+ import { readFileSync as readFileSync5 } from "fs";
7300
7466
  import { join as join10 } from "path";
7301
7467
  function complexity(db, symbolPattern) {
7302
7468
  const match = findFirstSymbolMatch(db, symbolPattern);
@@ -7309,7 +7475,7 @@ function complexity(db, symbolPattern) {
7309
7475
  const filePath = join10(db.config.projectRoot, match.relativePath);
7310
7476
  let source = "";
7311
7477
  try {
7312
- const lines = readFileSync6(filePath, "utf-8").split("\n");
7478
+ const lines = readFileSync5(filePath, "utf-8").split("\n");
7313
7479
  source = lines.slice(match.startLine, match.endLine + 1).join("\n");
7314
7480
  } catch {
7315
7481
  }
@@ -7820,17 +7986,17 @@ function normalizeSignature(raw) {
7820
7986
  }
7821
7987
  function normalizeSourceSignature(raw, leaf) {
7822
7988
  if (!raw || !raw.trim()) return null;
7823
- let declaration = raw.replace(/\s+/g, " ").trim();
7989
+ const declaration = raw.replace(/\s+/g, " ").trim();
7824
7990
  const parenIdx = declaration.indexOf("(");
7825
7991
  if (parenIdx === -1) return null;
7826
7992
  let prefix = declaration.slice(0, parenIdx);
7827
- const leafPattern = new RegExp(`\\b${escapeRegex3(leaf)}\\b`, "i");
7993
+ const leafPattern = new RegExp(`\\b${escapeRegex4(leaf)}\\b`, "i");
7828
7994
  const leafMatch = leafPattern.exec(prefix);
7829
7995
  if (leafMatch && typeof leafMatch.index === "number") {
7830
7996
  prefix = prefix.slice(0, leafMatch.index);
7831
7997
  }
7832
7998
  prefix = prefix.replace(/\b(public|private|protected|internal|final|static|abstract|sealed|virtual|override|async|suspend|inline|constexpr|consteval|constinit|const|pub|fn|function|def|sub|friend|shared|readonly|new|open|partial|export)\b/gi, " ").replace(/\s+/g, " ").trim();
7833
- let suffix = declaration.slice(parenIdx).replace(/\s*\{[\s\S]*$/, "").replace(/\s*=>[\s\S]*$/, "").replace(/\)\s*=\s*[\s\S]*$/, ")").replace(/\s+throws\s+[^={]+$/i, "").replace(/\s+where\s+.+$/i, "").replace(/\s+/g, " ").trim();
7999
+ const suffix = declaration.slice(parenIdx).replace(/\s*\{[\s\S]*$/, "").replace(/\s*=>[\s\S]*$/, "").replace(/\)\s*=\s*[\s\S]*$/, ")").replace(/\s+throws\s+[^={]+$/i, "").replace(/\s+where\s+.+$/i, "").replace(/\s+/g, " ").trim();
7834
8000
  if (!suffix.startsWith("(")) {
7835
8001
  return null;
7836
8002
  }
@@ -7838,7 +8004,7 @@ function normalizeSourceSignature(raw, leaf) {
7838
8004
  return normalized.length >= 3 ? normalized : null;
7839
8005
  }
7840
8006
  function declarationStartLines(lines, startLine, endLine, leaf) {
7841
- const escapedLeaf = escapeRegex3(leaf);
8007
+ const escapedLeaf = escapeRegex4(leaf);
7842
8008
  const callablePattern = new RegExp(`\\b${escapedLeaf}\\b\\s*\\(`, "i");
7843
8009
  const rubyPattern = new RegExp(`\\bdef\\s+${escapedLeaf}\\b`, "i");
7844
8010
  const candidates = [];
@@ -7869,7 +8035,7 @@ function parenBalance(value) {
7869
8035
  }
7870
8036
  return balance;
7871
8037
  }
7872
- function escapeRegex3(value) {
8038
+ function escapeRegex4(value) {
7873
8039
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7874
8040
  }
7875
8041